erted tiny" data-position="right center">  message/apps.py
  • 33 0
      message/migrations/0001_initial.py
  • 19 0
      message/migrations/0002_auto_20170816_1604.py
  • 0 0
      message/migrations/__init__.py
  • 41 0
      message/models.py
  • 19 0
      message/msg_views.py
  • 7 0
      message/tests.py
  • 7 0
      message/views.py
  • 9 0
      pep8.sh
  • 15 0
      requirements.txt
  • 0 0
      support/__init__.py
  • 25 0
      support/admin.py
  • 8 0
      support/apps.py
  • 66 0
      support/migrations/0001_initial.py
  • 25 0
      support/migrations/0002_auto_20170816_1509.py
  • 20 0
      support/migrations/0003_machinesupportprebookinfo_handle_status.py
  • 20 0
      support/migrations/0004_auto_20170816_1604.py
  • 0 0
      support/migrations/__init__.py
  • 94 0
      support/models.py
  • 7 0
      support/tests.py
  • 57 0
      support/views.py
  • 0 0
      utils/__init__.py
  • 0 0
      utils/error/__init__.py
  • 43 0
      utils/error/errno_utils.py
  • 18 0
      utils/error/response_utils.py
  • 0 0
      utils/redis/__init__.py
  • 6 0
      utils/redis/connect.py
  • 1 0
      utils/redis/rkeys.py
  • 7 0
      utils/url_utils.py
  • + 29 - 0
    .editorconfig

    @@ -0,0 +1,29 @@
    1
    +# EditorConfig is awesome: http://EditorConfig.org
    2
    +
    3
    +# top-most EditorConfig file
    4
    +root = true
    5
    +
    6
    +# Unix-style newlines with a newline ending every file
    7
    +[*]
    8
    +end_of_line = lf
    9
    +insert_final_newline = false
    10
    +
    11
    +# 4 space indentation
    12
    +[*.py]
    13
    +indent_style = space
    14
    +indent_size = 4
    15
    +
    16
    +# Tab indentation (no size specified)
    17
    +[*.js]
    18
    +indent_style = space
    19
    +indent_size = 4
    20
    +
    21
    +# Tab indentation (no size specified)
    22
    +[*.html]
    23
    +indent_style = space
    24
    +indent_size = 4
    25
    +
    26
    +# Matches the exact files either package.json or .travis.yml
    27
    +[{package.json,.travis.yml}]
    28
    +indent_style = space
    29
    +indent_size = 2

    + 19 - 0
    .gitignore

    @@ -0,0 +1,19 @@
    1
    +*.db
    2
    +*.pyc
    3
    +*.swp
    4
    +*.idea
    5
    +*.sqlite3
    6
    +*.DS_Store
    7
    +*.python-version
    8
    +
    9
    +build
    10
    +
    11
    +# Compiled python modules.
    12
    +*.pyc
    13
    +
    14
    +# Setuptools distribution folder.
    15
    +/dist/
    16
    +
    17
    +# Python egg metadata, regenerated from source files by setuptools.
    18
    +/*.egg-info
    19
    +/*.egg

    + 8 - 0
    .isort.cfg

    @@ -0,0 +1,8 @@
    1
    +# See the menu of settings available here:
    2
    +#   https://github.com/timothycrosley/isort/wiki/isort-Settings
    3
    +
    4
    +[settings]
    5
    +indent='    '
    6
    +line_length=120
    7
    +lines_after_imports=2
    8
    +skip=migrations

    + 0 - 0
    account/__init__.py


    + 15 - 0
    account/admin.py

    @@ -0,0 +1,15 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from djadmin import ReadonlyModelAdmin
    4
    +from django.contrib import admin
    5
    +
    6
    +from account.models import UserInfo
    7
    +
    8
    +
    9
    +class UserInfoAdmin(ReadonlyModelAdmin, admin.ModelAdmin):
    10
    +    list_display = ('user_id', 'unionid', 'openid', 'name', 'sex', 'nickname', 'phone', 'country', 'province', 'city', 'location', 'status', 'created_at', 'updated_at')
    11
    +    list_filter = ('sex', 'status')
    12
    +    actions = None
    13
    +
    14
    +
    15
    +admin.site.register(UserInfo, UserInfoAdmin)

    + 8 - 0
    account/apps.py

    @@ -0,0 +1,8 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.apps import AppConfig
    5
    +
    6
    +
    7
    +class AccountConfig(AppConfig):
    8
    +    name = 'account'

    + 43 - 0
    account/migrations/0001_initial.py

    @@ -0,0 +1,43 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 07:03
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations, models
    6
    +import shortuuidfield.fields
    7
    +
    8
    +
    9
    +class Migration(migrations.Migration):
    10
    +
    11
    +    initial = True
    12
    +
    13
    +    dependencies = [
    14
    +    ]
    15
    +
    16
    +    operations = [
    17
    +        migrations.CreateModel(
    18
    +            name='UserInfo',
    19
    +            fields=[
    20
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    21
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    22
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    23
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    24
    +                ('user_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=22, unique=True)),
    25
    +                ('unionid', models.CharField(blank=True, db_index=True, help_text='\u5fae\u4fe1 Unionid', max_length=255, null=True, unique=True, verbose_name='unionid')),
    26
    +                ('openid', models.CharField(blank=True, db_index=True, help_text='\u5fae\u4fe1 Openid', max_length=255, null=True, unique=True, verbose_name='openid')),
    27
    +                ('name', models.CharField(blank=True, help_text='\u7528\u6237\u59d3\u540d', max_length=255, null=True, verbose_name='name')),
    28
    +                ('sex', models.IntegerField(choices=[(1, '\u7537'), (0, '\u5973')], default=1, help_text='\u7528\u6237\u6027\u522b', verbose_name='sex')),
    29
    +                ('nickname', models.CharField(blank=True, help_text='\u7528\u6237\u6635\u79f0', max_length=255, null=True, verbose_name='nickname')),
    30
    +                ('avatar', models.CharField(blank=True, help_text='\u7528\u6237\u5934\u50cf', max_length=255, null=True, verbose_name='avatar')),
    31
    +                ('phone', models.CharField(blank=True, db_index=True, help_text='\u7528\u6237\u7535\u8bdd', max_length=255, null=True, unique=True, verbose_name='phone')),
    32
    +                ('country', models.CharField(blank=True, help_text='\u7528\u6237\u56fd\u5bb6', max_length=255, null=True, verbose_name='country')),
    33
    +                ('province', models.CharField(blank=True, help_text='\u7528\u6237\u7701\u4efd', max_length=255, null=True, verbose_name='province')),
    34
    +                ('city', models.CharField(blank=True, help_text='\u7528\u6237\u57ce\u5e02', max_length=255, null=True, verbose_name='city')),
    35
    +                ('location', models.CharField(blank=True, help_text='\u7528\u6237\u5730\u5740', max_length=255, null=True, verbose_name='location')),
    36
    +                ('user_status', models.IntegerField(choices=[(0, '\u672a\u9a8c\u8bc1'), (1, '\u5df2\u6fc0\u6d3b'), (2, '\u5df2\u7981\u7528'), (3, '\u5df2\u5220\u9664')], default=0, verbose_name='user_status')),
    37
    +            ],
    38
    +            options={
    39
    +                'verbose_name': 'userinfo',
    40
    +                'verbose_name_plural': 'userinfo',
    41
    +            },
    42
    +        ),
    43
    +    ]

    + 0 - 0
    account/migrations/__init__.py


    + 62 - 0
    account/models.py

    @@ -0,0 +1,62 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.db import models
    4
    +from django.utils.translation import ugettext_lazy as _
    5
    +from shortuuidfield import ShortUUIDField
    6
    +
    7
    +from manual.basemodels import CreateUpdateMixin
    8
    +
    9
    +
    10
    +class UserInfo(CreateUpdateMixin):
    11
    +    UNVERIFIED = 0
    12
    +    ACTIVATED = 1
    13
    +    DISABLED = 2
    14
    +    DELETED = 3
    15
    +
    16
    +    USER_STATUS = (
    17
    +        (UNVERIFIED, u'未验证'),
    18
    +        (ACTIVATED, u'已激活'),
    19
    +        (DISABLED, u'已禁用'),
    20
    +        (DELETED, u'已删除'),
    21
    +    )
    22
    +
    23
    +    MALE = 1
    24
    +    FEMALE = 0
    25
    +
    26
    +    SEX_TYPE = (
    27
    +        (MALE, u'男'),
    28
    +        (FEMALE, u'女'),
    29
    +    )
    30
    +
    31
    +    user_id = ShortUUIDField(_(u'user_id'), max_length=255, help_text=u'用户唯一标识', db_index=True, unique=True)
    32
    +
    33
    +    # 微信授权用户
    34
    +    unionid = models.CharField(_(u'unionid'), max_length=255, blank=True, null=True, help_text=u'微信 Unionid', db_index=True, unique=True)
    35
    +    openid = models.CharField(_(u'openid'), max_length=255, blank=True, null=True, help_text=u'微信 Openid', db_index=True, unique=True)
    36
    +    # 用户基本信息
    37
    +    name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'用户姓名')
    38
    +    sex = models.IntegerField(_(u'sex'), choices=SEX_TYPE, default=MALE, help_text=u'用户性别')
    39
    +    nickname = models.CharField(_(u'nickname'), max_length=255, blank=True, null=True, help_text=u'用户昵称')
    40
    +    avatar = models.CharField(_(u'avatar'), max_length=255, blank=True, null=True, help_text=u'用户头像')
    41
    +    phone = models.CharField(_(u'phone'), max_length=255, blank=True, null=True, help_text=u'用户电话', db_index=True, unique=True)
    42
    +    country = models.CharField(_(u'country'), max_length=255, blank=True, null=True, help_text=u'用户国家')
    43
    +    province = models.CharField(_(u'province'), max_length=255, blank=True, null=True, help_text=u'用户省份')
    44
    +    city = models.CharField(_(u'city'), max_length=255, blank=True, null=True, help_text=u'用户城市')
    45
    +    location = models.CharField(_(u'location'), max_length=255, blank=True, null=True, help_text=u'用户地址')
    46
    +
    47
    +    user_status = models.IntegerField(_(u'user_status'), choices=USER_STATUS, default=UNVERIFIED)
    48
    +
    49
    +    class Meta:
    50
    +        verbose_name = _(u'userinfo')
    51
    +        verbose_name_plural = _(u'userinfo')
    52
    +
    53
    +    def __unicode__(self):
    54
    +        return unicode(self.pk)
    55
    +
    56
    +    @property
    57
    +    def data(self):
    58
    +        return {
    59
    +            'user_id': self.user_id,
    60
    +            'nickname': self.nickname,
    61
    +            'avatar': self.avatar,
    62
    +        }

    + 29 - 0
    account/oauth_views.py

    @@ -0,0 +1,29 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.db import transaction
    4
    +from logit import logit
    5
    +
    6
    +from account.models import UserInfo
    7
    +from utils.error.response_utils import response
    8
    +
    9
    +
    10
    +@logit
    11
    +@transaction.atomic
    12
    +def user_wx_authorize_api(request):
    13
    +    # Get or Create User
    14
    +    user, created = UserInfo.objects.select_for_update().get_or_create(unionid=request.POST.get('unionid', ''))
    15
    +
    16
    +    # Set User Key's Value
    17
    +    user.openid = request.POST.get('openid', '')
    18
    +    user.sex = request.POST.get('sex', 0)
    19
    +    user.nickname = request.POST.get('nickname', '') or request.POST.get('screen_name', '')
    20
    +    user.avatar = request.POST.get('headimgurl', '') or request.POST.get('profile_image_url', '')
    21
    +    user.country = request.POST.get('country', '')
    22
    +    user.province = request.POST.get('province', '')
    23
    +    user.city = request.POST.get('city', '')
    24
    +    user.user_status = UserInfo.ACTIVATED
    25
    +    user.save()
    26
    +
    27
    +    return response(200, 'User Login Success', u'用户端登录成功', {
    28
    +        'userinfo': user.data,
    29
    +    })

    + 7 - 0
    account/tests.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.test import TestCase
    5
    +
    6
    +
    7
    +# Create your tests here.

    + 7 - 0
    account/views.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.shortcuts import render
    5
    +
    6
    +
    7
    +# Create your views here.

    + 0 - 0
    api/__init__.py


    + 7 - 0
    api/admin.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.contrib import admin
    5
    +
    6
    +
    7
    +# Register your models here.

    + 8 - 0
    api/apps.py

    @@ -0,0 +1,8 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.apps import AppConfig
    5
    +
    6
    +
    7
    +class ApiConfig(AppConfig):
    8
    +    name = 'api'

    + 0 - 0
    api/migrations/__init__.py


    + 7 - 0
    api/models.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.db import models
    5
    +
    6
    +
    7
    +# Create your models here.

    + 7 - 0
    api/tests.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.test import TestCase
    5
    +
    6
    +
    7
    +# Create your tests here.

    + 36 - 0
    api/urls.py

    @@ -0,0 +1,36 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.conf.urls import url
    4
    +
    5
    +from account import oauth_views
    6
    +from config import hot_views
    7
    +from intro import fav_views, intro_views
    8
    +from message import msg_views
    9
    +from support import views as support_views
    10
    +
    11
    +
    12
    +urlpatterns = [
    13
    +    url(r'^wx/authorize$', oauth_views.user_wx_authorize_api, name='user_wx_authorize_api'),  # 微信用户授权
    14
    +]
    15
    +
    16
    +urlpatterns += [
    17
    +    url(r'^intro/list$', intro_views.intro_list_api, name='intro_list_api'),  # 说明书列表
    18
    +    url(r'^intro/query$', intro_views.intro_query_api, name='intro_query_api'),  # 说明书搜索
    19
    +
    20
    +    url(r'^intro/fav$', fav_views.intro_fav_api, name='intro_fav_api'),  # 说明书收藏
    21
    +    url(r'^intro/fav/list$', fav_views.intro_fav_list_api, name='intro_fav_list_api'),  # 说明书收藏列表
    22
    +]
    23
    +
    24
    +urlpatterns += [
    25
    +    url(r'^msg/list$', msg_views.msg_list_api, name='msg_list_api'),  # 消息列表
    26
    +]
    27
    +
    28
    +urlpatterns += [
    29
    +    url(r'^hot/recommend/list$', hot_views.hot_recommend_list_api, name='hot_recommend_list_api'),  # 热门推荐列表
    30
    +    url(r'^hot/query/list$', hot_views.hot_query_list_api, name='hot_query_list_api'),  # 热门搜索列表
    31
    +]
    32
    +
    33
    +urlpatterns += [
    34
    +    url(r'^support/info$', support_views.support_info_api, name='support_info_api'),  # 支持预约信息
    35
    +    url(r'^support/prebook$', support_views.support_prebook_submit_api, name='support_prebook_submit_api'),  # 支持预约提交
    36
    +]

    + 4 - 0
    api/views.py

    @@ -0,0 +1,4 @@
    1
    +from django.shortcuts import render
    2
    +
    3
    +
    4
    +# Create your views here.

    + 9 - 0
    check.sh

    @@ -0,0 +1,9 @@
    1
    +#!/bin/bash
    2
    +
    3
    +echo '>> iSort'
    4
    +./isort.sh
    5
    +echo
    6
    +
    7
    +echo '>> PEP8'
    8
    +./pep8.sh
    9
    +echo

    + 0 - 0
    config/__init__.py


    + 19 - 0
    config/admin.py

    @@ -0,0 +1,19 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.contrib import admin
    4
    +
    5
    +from config.models import HotQueryInfo, HotRecommendInfo
    6
    +
    7
    +
    8
    +class HotQueryInfoAdmin(admin.ModelAdmin):
    9
    +    list_display = ('query', 'status', 'created_at', 'updated_at')
    10
    +    list_filter = ('status', )
    11
    +
    12
    +
    13
    +class HotRecommendInfoAdmin(admin.ModelAdmin):
    14
    +    list_display = ('title', 'image', 'status', 'created_at', 'updated_at')
    15
    +    list_filter = ('status', )
    16
    +
    17
    +
    18
    +admin.site.register(HotQueryInfo, HotQueryInfoAdmin)
    19
    +admin.site.register(HotRecommendInfo, HotRecommendInfoAdmin)

    + 8 - 0
    config/apps.py

    @@ -0,0 +1,8 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.apps import AppConfig
    5
    +
    6
    +
    7
    +class ConfigConfig(AppConfig):
    8
    +    name = 'config'

    + 27 - 0
    config/hot_views.py

    @@ -0,0 +1,27 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from logit import logit
    5
    +
    6
    +from config.models import HotQueryInfo, HotRecommendInfo
    7
    +from utils.error.response_utils import response
    8
    +
    9
    +
    10
    +@logit
    11
    +def hot_recommend_list_api(request):
    12
    +    hots = HotRecommendInfo.objects.filter(status=True)
    13
    +    hots = [hot.data for hot in hots]
    14
    +
    15
    +    return response(200, 'Get Hot Recommend List Success', u'获取热门推荐列表成功', {
    16
    +        'hots': hots,
    17
    +    })
    18
    +
    19
    +
    20
    +@logit
    21
    +def hot_query_list_api(request):
    22
    +    querys = HotQueryInfo.objects.filter(status=True)
    23
    +    querys = [query.data for query in querys]
    24
    +
    25
    +    return response(200, 'Get Hot Query List Success', u'获取热门搜索列表成功', {
    26
    +        'querys': querys,
    27
    +    })

    + 46 - 0
    config/migrations/0001_initial.py

    @@ -0,0 +1,46 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 07:03
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +import config.models
    6
    +from django.db import migrations, models
    7
    +
    8
    +
    9
    +class Migration(migrations.Migration):
    10
    +
    11
    +    initial = True
    12
    +
    13
    +    dependencies = [
    14
    +    ]
    15
    +
    16
    +    operations = [
    17
    +        migrations.CreateModel(
    18
    +            name='HotQueryInfo',
    19
    +            fields=[
    20
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    21
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    22
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    23
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    24
    +                ('query', models.CharField(blank=True, help_text='\u641c\u7d22\u5173\u952e\u8bcd', max_length=255, null=True, unique=True, verbose_name='query')),
    25
    +            ],
    26
    +            options={
    27
    +                'verbose_name': '\u70ed\u95e8\u641c\u7d22',
    28
    +                'verbose_name_plural': '\u70ed\u95e8\u641c\u7d22',
    29
    +            },
    30
    +        ),
    31
    +        migrations.CreateModel(
    32
    +            name='HotRecommendInfo',
    33
    +            fields=[
    34
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    35
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    36
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    37
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    38
    +                ('title', models.CharField(blank=True, help_text='\u70ed\u95e8\u6807\u9898', max_length=255, null=True, unique=True, verbose_name='title')),
    39
    +                ('image', models.ImageField(blank=True, help_text='\u70ed\u95e8\u56fe\u7247', null=True, upload_to=config.models.upload_path, verbose_name='image')),
    40
    +            ],
    41
    +            options={
    42
    +                'verbose_name': '\u70ed\u95e8\u63a8\u8350',
    43
    +                'verbose_name_plural': '\u70ed\u95e8\u63a8\u8350',
    44
    +            },
    45
    +        ),
    46
    +    ]

    + 0 - 0
    config/migrations/__init__.py


    + 58 - 0
    config/models.py

    @@ -0,0 +1,58 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +import os
    4
    +
    5
    +from django.db import models
    6
    +from django.utils.translation import ugettext_lazy as _
    7
    +from TimeConvert import TimeConvert as tc
    8
    +
    9
    +from manual.basemodels import CreateUpdateMixin
    10
    +from utils.url_utils import upload_file_url
    11
    +
    12
    +
    13
    +def upload_path(instance, old_filename):
    14
    +    return 'file/{ym}/{stamp}{ext}'.format(
    15
    +        ym=tc.local_string(format='%Y%m'),
    16
    +        stamp=tc.local_timestamp(ms=True),
    17
    +        ext=os.path.splitext(old_filename)[1].lower(),
    18
    +    )
    19
    +
    20
    +
    21
    +class HotQueryInfo(CreateUpdateMixin):
    22
    +    query = models.CharField(_(u'query'), max_length=255, blank=True, null=True, help_text=u'搜索关键词', unique=True)
    23
    +
    24
    +    class Meta:
    25
    +        verbose_name = _(u'热门搜索')
    26
    +        verbose_name_plural = _(u'热门搜索')
    27
    +
    28
    +    def __unicode__(self):
    29
    +        return unicode(self.pk)
    30
    +
    31
    +    @property
    32
    +    def data(self):
    33
    +        return {
    34
    +            'query': self.query,
    35
    +        }
    36
    +
    37
    +
    38
    +class HotRecommendInfo(CreateUpdateMixin):
    39
    +    title = models.CharField(_(u'title'), max_length=255, blank=True, null=True, help_text=u'热门标题', unique=True)
    40
    +    image = models.ImageField(_(u'image'), upload_to=upload_path, blank=True, null=True, help_text=u'热门图片')
    41
    +
    42
    +    class Meta:
    43
    +        verbose_name = _(u'热门推荐')
    44
    +        verbose_name_plural = _(u'热门推荐')
    45
    +
    46
    +    def __unicode__(self):
    47
    +        return unicode(self.pk)
    48
    +
    49
    +    @property
    50
    +    def image_url(self):
    51
    +        return upload_file_url(self.image)
    52
    +
    53
    +    @property
    54
    +    def data(self):
    55
    +        return {
    56
    +            'title': self.title,
    57
    +            'image_url': self.image_url,
    58
    +        }

    + 7 - 0
    config/tests.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.test import TestCase
    5
    +
    6
    +
    7
    +# Create your tests here.

    + 7 - 0
    config/views.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.shortcuts import render
    5
    +
    6
    +
    7
    +# Create your views here.

    + 0 - 0
    intro/__init__.py


    + 41 - 0
    intro/admin.py

    @@ -0,0 +1,41 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.contrib import admin
    4
    +
    5
    +from intro.models import IntroCatalogInfo, IntroCategoryInfo, IntroContentInfo, IntroFavoriteInfo, IntroNameInfo
    6
    +
    7
    +
    8
    +class IntroCategoryInfoAdmin(admin.ModelAdmin):
    9
    +    list_display = ('category', 'position', 'status', 'created_at', 'updated_at')
    10
    +    list_filter = ('status', )
    11
    +    search_fields = ('category', )
    12
    +
    13
    +
    14
    +class IntroNameInfoAdmin(admin.ModelAdmin):
    15
    +    list_display = ('name', 'category', 'position', 'status', 'created_at', 'updated_at')
    16
    +    list_filter = ('category', 'status')
    17
    +    search_fields = ('name', 'category__category')
    18
    +
    19
    +
    20
    +class IntroCatalogInfoAdmin(admin.ModelAdmin):
    21
    +    list_display = ('catalog', 'name', 'position', 'status', 'created_at', 'updated_at')
    22
    +    list_filter = ('name', 'status')
    23
    +    search_fields = ('catalog', 'name__name')
    24
    +
    25
    +
    26
    +class IntroContentInfoAdmin(admin.ModelAdmin):
    27
    +    list_display = ('title', 'content', 'pdf', 'catalog', 'position', 'status', 'created_at', 'updated_at')
    28
    +    list_filter = ('catalog', 'status')
    29
    +    search_fields = ('title', 'content', 'catalog__catalog')
    30
    +
    31
    +
    32
    +class IntroFavoriteInfoAdmin(admin.ModelAdmin):
    33
    +    list_display = ('user_id', 'content', 'status', 'created_at', 'updated_at')
    34
    +    list_filter = ('status', )
    35
    +
    36
    +
    37
    +admin.site.register(IntroCategoryInfo, IntroCategoryInfoAdmin)
    38
    +admin.site.register(IntroNameInfo, IntroNameInfoAdmin)
    39
    +admin.site.register(IntroCatalogInfo, IntroCatalogInfoAdmin)
    40
    +admin.site.register(IntroContentInfo, IntroContentInfoAdmin)
    41
    +admin.site.register(IntroFavoriteInfo, IntroFavoriteInfoAdmin)

    + 8 - 0
    intro/apps.py

    @@ -0,0 +1,8 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.apps import AppConfig
    5
    +
    6
    +
    7
    +class IntroConfig(AppConfig):
    8
    +    name = 'intro'

    + 45 - 0
    intro/fav_views.py

    @@ -0,0 +1,45 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from logit import logit
    5
    +from paginator import pagination
    6
    +
    7
    +from account.models import UserInfo
    8
    +from intro.models import IntroFavoriteInfo
    9
    +from utils.error.errno_utils import ProfileStatusCode
    10
    +from utils.error.response_utils import response
    11
    +
    12
    +
    13
    +@logit
    14
    +def intro_fav_api(request):
    15
    +    user_id = request.POST.get('user_id', '')
    16
    +    pk = int(request.POST.get('pk', 0))
    17
    +
    18
    +    try:
    19
    +        UserInfo.objects.get(user_id=user_id)
    20
    +    except UserInfo.DoesNotExist:
    21
    +        return response(ProfileStatusCode.PROFILE_NOT_FOUND)
    22
    +
    23
    +    IntroFavoriteInfo.objects.get_or_create(
    24
    +        user_id=user_id,
    25
    +        content=pk,
    26
    +    )
    27
    +
    28
    +    return response(200, 'Intro Fav Success', u'说明书收藏成功')
    29
    +
    30
    +
    31
    +@logit
    32
    +def intro_fav_list_api(request):
    33
    +    user_id = request.POST.get('user_id', '')
    34
    +    page = int(request.POST.get('page', 1))
    35
    +    num = int(request.POST.get('num', 10))
    36
    +
    37
    +    favs = IntroFavoriteInfo.objects.filter(user_id=user_id, status=True)
    38
    +    favs, left = pagination(favs, page, num)
    39
    +    favs = [fav.data for fav in favs]
    40
    +    favs = [fav for fav in favs if fav]
    41
    +
    42
    +    return response(200, 'Get Intro Fav List Success', u'获取说明书收藏列表成功', {
    43
    +        'favs': favs,
    44
    +        'left': left,
    45
    +    })

    + 29 - 0
    intro/intro_views.py

    @@ -0,0 +1,29 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from logit import logit
    5
    +
    6
    +from intro.models import IntroCategoryInfo, IntroContentInfo
    7
    +from utils.error.response_utils import response
    8
    +
    9
    +
    10
    +@logit
    11
    +def intro_list_api(request):
    12
    +    cates = IntroCategoryInfo.objects.filter(status=True).order_by('position')
    13
    +    cates = [cate.data for cate in cates]
    14
    +
    15
    +    return response(200, 'Get Intro List Success', u'获取说明书列表成功', {
    16
    +        'cates': cates,
    17
    +    })
    18
    +
    19
    +
    20
    +@logit
    21
    +def intro_query_api(request):
    22
    +    query = request.POST.get('query', '')
    23
    +
    24
    +    cates = IntroContentInfo.objects.filter(content__contains=query, status=True).order_by('position')
    25
    +    cates = [cate.data2 for cate in cates]
    26
    +
    27
    +    return response(200, 'Intro Query Success', u'说明书检索成功', {
    28
    +        'cates': cates,
    29
    +    })

    + 102 - 0
    intro/migrations/0001_initial.py

    @@ -0,0 +1,102 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 07:03
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations, models
    6
    +import django.db.models.deletion
    7
    +import intro.models
    8
    +
    9
    +
    10
    +class Migration(migrations.Migration):
    11
    +
    12
    +    initial = True
    13
    +
    14
    +    dependencies = [
    15
    +    ]
    16
    +
    17
    +    operations = [
    18
    +        migrations.CreateModel(
    19
    +            name='IntroCatalogInfo',
    20
    +            fields=[
    21
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    22
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    23
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    24
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    25
    +                ('catalog', models.CharField(blank=True, help_text='\u76ee\u5f55', max_length=255, null=True, verbose_name='catalog')),
    26
    +                ('position', models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position')),
    27
    +            ],
    28
    +            options={
    29
    +                'verbose_name': '\u8bf4\u660e\u4e66\u76ee\u5f55',
    30
    +                'verbose_name_plural': '\u8bf4\u660e\u4e66\u76ee\u5f55',
    31
    +            },
    32
    +        ),
    33
    +        migrations.CreateModel(
    34
    +            name='IntroCategoryInfo',
    35
    +            fields=[
    36
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    37
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    38
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    39
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    40
    +                ('category', models.CharField(blank=True, help_text='\u5206\u7c7b', max_length=255, null=True, unique=True, verbose_name='category')),
    41
    +                ('position', models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position')),
    42
    +            ],
    43
    +            options={
    44
    +                'verbose_name': '\u8bf4\u660e\u4e66\u5206\u7c7b',
    45
    +                'verbose_name_plural': '\u8bf4\u660e\u4e66\u5206\u7c7b',
    46
    +            },
    47
    +        ),
    48
    +        migrations.CreateModel(
    49
    +            name='IntroContentInfo',
    50
    +            fields=[
    51
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    52
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    53
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    54
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    55
    +                ('title', models.CharField(blank=True, help_text='\u6807\u9898', max_length=255, null=True, verbose_name='title')),
    56
    +                ('content', models.CharField(blank=True, help_text='\u5185\u5bb9', max_length=255, null=True, verbose_name='content')),
    57
    +                ('pdf', models.FileField(blank=True, help_text='PDF \u6587\u4ef6', null=True, upload_to=intro.models.upload_path, verbose_name='pdf')),
    58
    +                ('position', models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position')),
    59
    +                ('catalog', models.ForeignKey(blank=True, help_text='\u76ee\u5f55', null=True, on_delete=django.db.models.deletion.CASCADE, to='intro.IntroCatalogInfo', verbose_name='catalog')),
    60
    +            ],
    61
    +            options={
    62
    +                'verbose_name': '\u8bf4\u660e\u4e66\u5185\u5bb9',
    63
    +                'verbose_name_plural': '\u8bf4\u660e\u4e66\u5185\u5bb9',
    64
    +            },
    65
    +        ),
    66
    +        migrations.CreateModel(
    67
    +            name='IntroFavoriteInfo',
    68
    +            fields=[
    69
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    70
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    71
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    72
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    73
    +                ('user_id', models.CharField(blank=True, db_index=True, help_text='\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=255, null=True, verbose_name='user_id')),
    74
    +                ('content', models.IntegerField(default=0, help_text='\u5185\u5bb9', verbose_name='content')),
    75
    +            ],
    76
    +            options={
    77
    +                'verbose_name': 'introfavoriteinfo',
    78
    +                'verbose_name_plural': 'introfavoriteinfo',
    79
    +            },
    80
    +        ),
    81
    +        migrations.CreateModel(
    82
    +            name='IntroNameInfo',
    83
    +            fields=[
    84
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    85
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    86
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    87
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    88
    +                ('name', models.CharField(blank=True, help_text='\u540d\u79f0', max_length=255, null=True, unique=True, verbose_name='name')),
    89
    +                ('position', models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position')),
    90
    +                ('category', models.ForeignKey(blank=True, help_text='\u5206\u7c7b', null=True, on_delete=django.db.models.deletion.CASCADE, to='intro.IntroCategoryInfo', verbose_name='category')),
    91
    +            ],
    92
    +            options={
    93
    +                'verbose_name': '\u8bf4\u660e\u4e66\u540d\u79f0',
    94
    +                'verbose_name_plural': '\u8bf4\u660e\u4e66\u540d\u79f0',
    95
    +            },
    96
    +        ),
    97
    +        migrations.AddField(
    98
    +            model_name='introcataloginfo',
    99
    +            name='name',
    100
    +            field=models.ForeignKey(blank=True, help_text='\u540d\u79f0', null=True, on_delete=django.db.models.deletion.CASCADE, to='intro.IntroNameInfo', verbose_name='name'),
    101
    +        ),
    102
    +    ]

    + 0 - 0
    intro/migrations/__init__.py


    + 167 - 0
    intro/models.py

    @@ -0,0 +1,167 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +import os
    4
    +
    5
    +from django.db import models
    6
    +from django.utils.translation import ugettext_lazy as _
    7
    +from TimeConvert import TimeConvert as tc
    8
    +
    9
    +from manual.basemodels import CreateUpdateMixin
    10
    +from utils.url_utils import upload_file_url
    11
    +
    12
    +
    13
    +def upload_path(instance, old_filename):
    14
    +    return 'file/{ym}/{stamp}{ext}'.format(
    15
    +        ym=tc.local_string(format='%Y%m'),
    16
    +        stamp=tc.local_timestamp(ms=True),
    17
    +        ext=os.path.splitext(old_filename)[1].lower(),
    18
    +    )
    19
    +
    20
    +
    21
    +class IntroCategoryInfo(CreateUpdateMixin):
    22
    +    category = models.CharField(_(u'category'), max_length=255, blank=True, null=True, help_text=u'分类', unique=True)
    23
    +    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
    24
    +
    25
    +    class Meta:
    26
    +        verbose_name = _(u'说明书分类')
    27
    +        verbose_name_plural = _(u'说明书分类')
    28
    +
    29
    +    def __unicode__(self):
    30
    +        return unicode(self.category)
    31
    +
    32
    +    @property
    33
    +    def data(self):
    34
    +        names = IntroNameInfo.objects.filter(category=self.pk, status=True).order_by('position')
    35
    +        names = [name.data for name in names]
    36
    +        return {
    37
    +            'pk': self.pk,
    38
    +            'category': self.category,
    39
    +            'names': names,
    40
    +        }
    41
    +
    42
    +    @property
    43
    +    def data2(self):
    44
    +        return {
    45
    +            'pk': self.pk,
    46
    +            'category': self.category,
    47
    +        }
    48
    +
    49
    +
    50
    +class IntroNameInfo(CreateUpdateMixin):
    51
    +    name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'名称', unique=True)
    52
    +    category = models.ForeignKey(IntroCategoryInfo, verbose_name=_(u'category'), blank=True, null=True, help_text=u'分类')
    53
    +    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
    54
    +
    55
    +    class Meta:
    56
    +        verbose_name = _(u'说明书名称')
    57
    +        verbose_name_plural = _(u'说明书名称')
    58
    +
    59
    +    def __unicode__(self):
    60
    +        return unicode(self.name)
    61
    +
    62
    +    @property
    63
    +    def data(self):
    64
    +        catalogs = IntroCatalogInfo.objects.filter(name=self.pk, status=True).order_by('position')
    65
    +        catalogs = [catalog.data for catalog in catalogs]
    66
    +        return {
    67
    +            'pk': self.pk,
    68
    +            'name': self.name,
    69
    +            'catalogs': catalogs,
    70
    +        }
    71
    +
    72
    +    @property
    73
    +    def data2(self):
    74
    +        return {
    75
    +            'pk': self.pk,
    76
    +            'name': self.name,
    77
    +            'category': self.category.data2,
    78
    +        }
    79
    +
    80
    +
    81
    +class IntroCatalogInfo(CreateUpdateMixin):
    82
    +    catalog = models.CharField(_(u'catalog'), max_length=255, blank=True, null=True, help_text=u'目录')
    83
    +    name = models.ForeignKey(IntroNameInfo, verbose_name=_(u'name'), blank=True, null=True, help_text=u'名称')
    84
    +    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
    85
    +
    86
    +    class Meta:
    87
    +        verbose_name = _(u'说明书目录')
    88
    +        verbose_name_plural = _(u'说明书目录')
    89
    +
    90
    +    def __unicode__(self):
    91
    +        return unicode(self.catalog)
    92
    +
    93
    +    @property
    94
    +    def data(self):
    95
    +        contents = IntroContentInfo.objects.filter(catalog=self.pk, status=True).order_by('position')
    96
    +        contents = [content.data for content in contents]
    97
    +        return {
    98
    +            'pk': self.pk,
    99
    +            'catalog': self.catalog,
    100
    +            'contents': contents,
    101
    +        }
    102
    +
    103
    +    @property
    104
    +    def data2(self):
    105
    +        return {
    106
    +            'pk': self.pk,
    107
    +            'catalog': self.catalog,
    108
    +            'name': self.name.data2,
    109
    +        }
    110
    +
    111
    +
    112
    +class IntroContentInfo(CreateUpdateMixin):
    113
    +    title = models.CharField(_(u'title'), max_length=255, blank=True, null=True, help_text=u'标题')
    114
    +    content = models.CharField(_(u'content'), max_length=255, blank=True, null=True, help_text=u'内容')
    115
    +    pdf = models.FileField(_(u'pdf'), upload_to=upload_path, blank=True, null=True, help_text=u'PDF 文件')
    116
    +    catalog = models.ForeignKey(IntroCatalogInfo, verbose_name=_(u'catalog'), blank=True, null=True, help_text=u'目录')
    117
    +    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
    118
    +
    119
    +    class Meta:
    120
    +        verbose_name = _(u'说明书内容')
    121
    +        verbose_name_plural = _(u'说明书内容')
    122
    +
    123
    +    def __unicode__(self):
    124
    +        return unicode(self.title)
    125
    +
    126
    +    @property
    127
    +    def pdf_url(self):
    128
    +        return upload_file_url(self.pdf)
    129
    +
    130
    +    @property
    131
    +    def data(self):
    132
    +        return {
    133
    +            'pk': self.pk,
    134
    +            'title': self.title,
    135
    +            'content': self.content,
    136
    +            'pdf_url': self.pdf_url,
    137
    +        }
    138
    +
    139
    +    @property
    140
    +    def data2(self):
    141
    +        return {
    142
    +            'pk': self.pk,
    143
    +            'title': self.title,
    144
    +            'content': self.content,
    145
    +            'pdf_url': self.pdf_url,
    146
    +            'catalog': self.catalog.data2,
    147
    +        }
    148
    +
    149
    +
    150
    +class IntroFavoriteInfo(CreateUpdateMixin):
    151
    +    user_id = models.CharField(_(u'user_id'), max_length=255, blank=True, null=True, help_text=u'用户唯一标识', db_index=True)
    152
    +    content = models.IntegerField(_(u'content'), default=0, help_text=u'内容')
    153
    +
    154
    +    class Meta:
    155
    +        verbose_name = _(u'introfavoriteinfo')
    156
    +        verbose_name_plural = _(u'introfavoriteinfo')
    157
    +
    158
    +    def __unicode__(self):
    159
    +        return unicode(self.pk)
    160
    +
    161
    +    @property
    162
    +    def data(self):
    163
    +        try:
    164
    +            icon = IntroContentInfo.objects.get(pk=self.content)
    165
    +        except IntroContentInfo.DoesNotExist:
    166
    +            icon = None
    167
    +        return icon and icon.data2

    + 7 - 0
    intro/tests.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.test import TestCase
    5
    +
    6
    +
    7
    +# Create your tests here.

    + 7 - 0
    intro/views.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.shortcuts import render
    5
    +
    6
    +
    7
    +# Create your views here.

    + 3 - 0
    isort.sh

    @@ -0,0 +1,3 @@
    1
    +#!/bin/bash
    2
    +
    3
    +isort -rc -sp . .

    + 23 - 0
    manage.py

    @@ -0,0 +1,23 @@
    1
    +#!/usr/bin/env python
    2
    +import os
    3
    +import sys
    4
    +
    5
    +
    6
    +if __name__ == "__main__":
    7
    +    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "manual.settings")
    8
    +    try:
    9
    +        from django.core.management import execute_from_command_line
    10
    +    except ImportError:
    11
    +        # The above import may fail for some other reason. Ensure that the
    12
    +        # issue is really that Django is missing to avoid masking other
    13
    +        # exceptions on Python 2.
    14
    +        try:
    15
    +            import django
    16
    +        except ImportError:
    17
    +            raise ImportError(
    18
    +                "Couldn't import Django. Are you sure it's installed and "
    19
    +                "available on your PYTHONPATH environment variable? Did you "
    20
    +                "forget to activate a virtual environment?"
    21
    +            )
    22
    +        raise
    23
    +    execute_from_command_line(sys.argv)

    + 0 - 0
    manual/__init__.py


    + 26 - 0
    manual/basemodels.py

    @@ -0,0 +1,26 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.db import models
    4
    +from django.utils.translation import ugettext_lazy as _
    5
    +
    6
    +
    7
    +class CreateUpdateMixin(models.Model):
    8
    +    status = models.BooleanField(_(u'status'), default=True, help_text=_(u'状态'), db_index=True)
    9
    +    created_at = models.DateTimeField(_(u'created_at'), auto_now_add=True, editable=True, help_text=_(u'创建时间'))
    10
    +    updated_at = models.DateTimeField(_(u'updated_at'), auto_now=True, editable=True, help_text=_(u'更新时间'))
    11
    +
    12
    +    class Meta:
    13
    +        abstract = True
    14
    +
    15
    +
    16
    +class SexChoicesMixin(models.Model):
    17
    +    MALE = 1
    18
    +    FEMALE = 0
    19
    +
    20
    +    SEX_TYPE = (
    21
    +        (MALE, u'男'),
    22
    +        (FEMALE, u'女'),
    23
    +    )
    24
    +
    25
    +    class Meta:
    26
    +        abstract = True

    + 27 - 0
    manual/deploy.bak/manual.ini

    @@ -0,0 +1,27 @@
    1
    +# manual_uwsgi.ini file
    2
    +[uwsgi]
    3
    +
    4
    +# Django-related settings
    5
    +# the base directory (full path)
    6
    +chdir           = /home/diors/work/manual
    7
    +# Django's wsgi file
    8
    +module          = manual.wsgi
    9
    +# the virtualenv (full path)
    10
    +# home            = /path/to/virtualenv
    11
    +
    12
    +# process-related settings
    13
    +# master
    14
    +master          = true
    15
    +# maximum number of worker processes
    16
    +processes       = 10
    17
    +# the socket (use the full path to be safe
    18
    +socket          = /home/paiai/work/manual/manual/deploy/manual.sock
    19
    +# ... with appropriate permissions - may be needed
    20
    +chmod-socket    = 777
    21
    +# clear environment on exit
    22
    +vacuum          = true
    23
    +
    24
    +# 11: Resource temporarily unavailable
    25
    +reload-mercy    = 64
    26
    +max-requests    = 8192
    27
    +listen          = 4096

    + 40 - 0
    manual/deploy.bak/manual_nginx.conf

    @@ -0,0 +1,40 @@
    1
    +# manual_nginx.conf
    2
    +
    3
    +# the upstream component nginx needs to connect to
    4
    +upstream manual {
    5
    +    # server unix:///home/paiai/work/manual/manual/deploy/manual.sock; # for a file socket
    6
    +    server 127.0.0.1:8888; # for a web port socket (we'll use this first)
    7
    +}
    8
    +
    9
    +# configuration of the server
    10
    +server {
    11
    +    # the port your site will be served on
    12
    +    listen      80;
    13
    +    # the domain name it will serve for
    14
    +    server_name .a.com; # substitute your machine's IP address or FQDN
    15
    +    charset     utf-8;
    16
    +
    17
    +    # max upload size
    18
    +    client_max_body_size 75M;   # adjust to taste
    19
    +
    20
    +    # JS接口安全域名 & 业务域名 验证
    21
    +    location /xxx.txt {
    22
    +        alias /home/paiai/work/manual/docs/we/xxx.txt;
    23
    +    }
    24
    +
    25
    +    # Django media
    26
    +    location /media  {
    27
    +        alias /home/paiai/work/manual/media;  # your Django project's media files - amend as required
    28
    +    }
    29
    +
    30
    +    location /static {
    31
    +        alias /home/paiai/work/manual/collect_static; # your Django project's static files - amend as required
    32
    +    }
    33
    +
    34
    +    # Finally, send all non-media requests to the Django server.
    35
    +    location / {
    36
    +        # uwsgi_pass  manual;
    37
    +        proxy_pass  http://manual;
    38
    +        include     /home/paiai/work/manual/manual/deploy/uwsgi_params; # the uwsgi_params file you installed
    39
    +    }
    40
    +}

    + 10 - 0
    manual/deploy.bak/manual_supervisor.ini

    @@ -0,0 +1,10 @@
    1
    +[program:manual]
    2
    +command=/home/paiai/env/bin/uwsgi --ini /home/paiai/work/manual/manual/deploy/manual.ini
    3
    +autostart=true
    4
    +autorestart=true
    5
    +startretries=3
    6
    +exitcodes=0,1,2
    7
    +stopsignal=QUIT
    8
    +stdout_logfile=/var/log/supervisor_manual_access.log
    9
    +stderr_logfile=/var/log/supervisor_manual_error.log
    10
    +user=diors

    + 15 - 0
    manual/deploy.bak/uwsgi_params

    @@ -0,0 +1,15 @@
    1
    +uwsgi_param	QUERY_STRING		$query_string;
    2
    +uwsgi_param	REQUEST_METHOD		$request_method;
    3
    +uwsgi_param	CONTENT_TYPE		$content_type;
    4
    +uwsgi_param	CONTENT_LENGTH		$content_length;
    5
    +
    6
    +uwsgi_param	REQUEST_URI		$request_uri;
    7
    +uwsgi_param	PATH_INFO		$document_uri;
    8
    +uwsgi_param	DOCUMENT_ROOT		$document_root;
    9
    +uwsgi_param	SERVER_PROTOCOL		$server_protocol;
    10
    +uwsgi_param	UWSGI_SCHEME		$scheme;
    11
    +
    12
    +uwsgi_param	REMOTE_ADDR		$remote_addr;
    13
    +uwsgi_param	REMOTE_PORT		$remote_port;
    14
    +uwsgi_param	SERVER_PORT		$server_port;
    15
    +uwsgi_param	SERVER_NAME		$server_name;

    + 16 - 0
    manual/func_settings.py

    @@ -0,0 +1,16 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +import redis_extensions as redis
    4
    +
    5
    +
    6
    +def redis_conf(conf):
    7
    +    return {
    8
    +        'host': conf.get('HOST', 'localhost'),
    9
    +        'port': conf.get('PORT', 6379),
    10
    +        'password': '{0}:{1}'.format(conf.get('USER', ''), conf.get('PASSWORD', '')) if conf.get('USER') else '',
    11
    +        'db': conf.get('db', 0),
    12
    +    }
    13
    +
    14
    +
    15
    +def redis_connect(conf):
    16
    +    return redis.StrictRedisExtensions(connection_pool=redis.ConnectionPool(**redis_conf(conf)))

    + 43 - 0
    manual/local_settings.py

    @@ -0,0 +1,43 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +import os
    4
    +
    5
    +
    6
    +# DEBUG = False
    7
    +
    8
    +ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
    9
    +
    10
    +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    11
    +PROJ_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
    12
    +
    13
    +TEMPLATES = [
    14
    +    {
    15
    +        'BACKEND': 'django.template.backends.django.DjangoTemplates',
    16
    +        'DIRS': [os.path.join(BASE_DIR, 'templates')],
    17
    +        # 'APP_DIRS': True,
    18
    +        'OPTIONS': {
    19
    +            'context_processors': [
    20
    +                'django.template.context_processors.debug',
    21
    +                'django.template.context_processors.request',
    22
    +                'django.contrib.auth.context_processors.auth',
    23
    +                'django.contrib.messages.context_processors.messages',
    24
    +            ],
    25
    +            'loaders': [
    26
    +                'django.template.loaders.filesystem.Loader',
    27
    +                'django.template.loaders.app_directories.Loader',
    28
    +            ],
    29
    +        },
    30
    +    },
    31
    +]
    32
    +
    33
    +# DOMAIN
    34
    +DOMAIN = 'http://a.com'
    35
    +
    36
    +# 邮件设置
    37
    +# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
    38
    +SERVER_EMAIL = 'error.notify@exmail.com'
    39
    +EMAIL_HOST_USER = 'error.notify@exmail.com'
    40
    +EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
    41
    +DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
    42
    +ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
    43
    +EMAIL_SUBJECT_PREFIX = u'[Templet] '

    + 254 - 0
    manual/settings.py

    @@ -0,0 +1,254 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +"""
    4
    +Django settings for manual project.
    5
    +
    6
    +Generated by 'django-admin startproject' using Django 1.11.3.
    7
    +
    8
    +For more information on this file, see
    9
    +https://docs.djangoproject.com/en/1.11/topics/settings/
    10
    +
    11
    +For the full list of settings and their values, see
    12
    +https://docs.djangoproject.com/en/1.11/ref/settings/
    13
    +"""
    14
    +
    15
    +import os
    16
    +
    17
    +
    18
    +# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
    19
    +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    20
    +PROJ_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
    21
    +
    22
    +
    23
    +# Quick-start development settings - unsuitable for production
    24
    +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
    25
    +
    26
    +# SECURITY WARNING: keep the secret key used in production secret!
    27
    +SECRET_KEY = '0=hpv21&am(7(k5ab!^zjvvl=ntj)^i@7)87t47uzumt_5rq$+'
    28
    +
    29
    +# SECURITY WARNING: don't run with debug turned on in production!
    30
    +DEBUG = True
    31
    +
    32
    +ALLOWED_HOSTS = []
    33
    +
    34
    +
    35
    +# Application definition
    36
    +
    37
    +INSTALLED_APPS = [
    38
    +    'django.contrib.admin',
    39
    +    'django.contrib.auth',
    40
    +    'django.contrib.contenttypes',
    41
    +    'django.contrib.sessions',
    42
    +    'django.contrib.messages',
    43
    +    'django.contrib.staticfiles',
    44
    +    'django_uniapi',
    45
    +    'django_we',
    46
    +    'account',
    47
    +    'api',
    48
    +    'config',
    49
    +    'intro',
    50
    +    'message',
    51
    +    'support',
    52
    +]
    53
    +
    54
    +MIDDLEWARE = [
    55
    +    'django.middleware.security.SecurityMiddleware',
    56
    +    'django.contrib.sessions.middleware.SessionMiddleware',
    57
    +    'django.middleware.common.CommonMiddleware',
    58
    +    # 'django.middleware.csrf.CsrfViewMiddleware',
    59
    +    'django.contrib.auth.middleware.AuthenticationMiddleware',
    60
    +    'django.contrib.messages.middleware.MessageMiddleware',
    61
    +    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    62
    +    'detect.middleware.UserAgentDetectionMiddleware',
    63
    +]
    64
    +
    65
    +ROOT_URLCONF = 'manual.urls'
    66
    +
    67
    +TEMPLATES = [
    68
    +    {
    69
    +        'BACKEND': 'django.template.backends.django.DjangoTemplates',
    70
    +        'DIRS': [os.path.join(BASE_DIR, 'templates')],
    71
    +        # 'APP_DIRS': True,
    72
    +        'OPTIONS': {
    73
    +            'context_processors': [
    74
    +                'django.template.context_processors.debug',
    75
    +                'django.template.context_processors.request',
    76
    +                'django.contrib.auth.context_processors.auth',
    77
    +                'django.contrib.messages.context_processors.messages',
    78
    +            ],
    79
    +            'loaders': [
    80
    +                ('django.template.loaders.cached.Loader', [
    81
    +                    'django.template.loaders.filesystem.Loader',
    82
    +                    'django.template.loaders.app_directories.Loader',
    83
    +                ]),
    84
    +            ],
    85
    +        },
    86
    +    },
    87
    +]
    88
    +
    89
    +WSGI_APPLICATION = 'manual.wsgi.application'
    90
    +
    91
    +
    92
    +# Database
    93
    +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
    94
    +
    95
    +DATABASES = {
    96
    +    'default': {
    97
    +        'ENGINE': 'django.db.backends.mysql',
    98
    +        'NAME': 'manual',
    99
    +        'USER': 'root',
    100
    +        'PASSWORD': '',
    101
    +        'CONN_MAX_AGE': 600,
    102
    +        'OPTIONS': {
    103
    +            # Utf8mb4 for Emoji
    104
    +            #
    105
    +            # Nickname
    106
    +            #
    107
    +            # account.WechatInfo ==> nickname
    108
    +            #   ALTER TABLE account_wechatinfo MODIFY COLUMN nickname VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
    109
    +            'charset': 'utf8mb4',
    110
    +        },
    111
    +    }
    112
    +}
    113
    +
    114
    +
    115
    +# Password validation
    116
    +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
    117
    +
    118
    +AUTH_PASSWORD_VALIDATORS = [
    119
    +    {
    120
    +        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    121
    +    },
    122
    +    {
    123
    +        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    124
    +    },
    125
    +    {
    126
    +        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    127
    +    },
    128
    +    {
    129
    +        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    130
    +    },
    131
    +]
    132
    +
    133
    +
    134
    +# Internationalization
    135
    +# https://docs.djangoproject.com/en/1.11/topics/i18n/
    136
    +
    137
    +LANGUAGE_CODE = 'zh-Hans'
    138
    +
    139
    +TIME_ZONE = 'Asia/Shanghai'
    140
    +
    141
    +USE_I18N = True
    142
    +
    143
    +USE_L10N = True
    144
    +
    145
    +USE_TZ = True
    146
    +
    147
    +
    148
    +# Static files (CSS, JavaScript, Images)
    149
    +# https://docs.djangoproject.com/en/1.11/howto/static-files/
    150
    +
    151
    +STATICFILES_DIRS = (
    152
    +    os.path.join(PROJ_DIR, 'static').replace('\\', '/'),
    153
    +)
    154
    +
    155
    +STATIC_ROOT = os.path.join(BASE_DIR, 'collect_static').replace('\\', '/')
    156
    +
    157
    +STATIC_URL = '/static/'
    158
    +
    159
    +STATICFILES_FINDERS = (
    160
    +    'django.contrib.staticfiles.finders.FileSystemFinder',
    161
    +    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    162
    +    # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
    163
    +)
    164
    +
    165
    +MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
    166
    +
    167
    +MEDIA_URL = '/media/'
    168
    +
    169
    +# DOMAIN
    170
    +DOMAIN = 'http://a.com'
    171
    +
    172
    +# Redis 设置
    173
    +REDIS = {
    174
    +    'default': {
    175
    +        'HOST': '127.0.0.1',
    176
    +        'PORT': 6379,
    177
    +        'USER': '',
    178
    +        'PASSWORD': '',
    179
    +        'db': 0,
    180
    +    }
    181
    +}
    182
    +
    183
    +# 微信设置
    184
    +WECHAT = {
    185
    +    'JSAPI': {
    186
    +        'token': '5201314',
    187
    +        'appID': '',
    188
    +        'appsecret': '',
    189
    +        'mchID': '',
    190
    +        'apiKey': '',
    191
    +        'mch_cert': '',
    192
    +        'mch_key': '',
    193
    +        'redpack': {
    194
    +
    195
    +        }
    196
    +    },
    197
    +}
    198
    +
    199
    +# 邮件设置
    200
    +# https://docs.djangoproject.com/en/1.11/howto/error-reporting/#email-reports
    201
    +# When DEBUG is False, Django will email the users listed in the ADMINS setting
    202
    +# whenever your code raises an unhandled exception and results in an internal server error (HTTP status code 500).
    203
    +# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
    204
    +# Email address that error messages come from.
    205
    +SERVER_EMAIL = 'error.notify@exmail.com'
    206
    +# The email backend to use. For possible shortcuts see django.core.mail.
    207
    +# The default is to use the SMTP backend.
    208
    +# Third-party backends can be specified by providing a Python path
    209
    +# to a module that defines an EmailBackend class.
    210
    +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    211
    +# Host for sending email.
    212
    +EMAIL_HOST = 'smtp.exmail.qq.com'
    213
    +# Port for sending email.
    214
    +EMAIL_PORT = 25
    215
    +# Optional SMTP authentication information for EMAIL_HOST.
    216
    +EMAIL_HOST_USER = 'error.notify@exmail.com'
    217
    +EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
    218
    +EMAIL_USE_TLS = False
    219
    +EMAIL_USE_SSL = False
    220
    +EMAIL_SSL_CERTFILE = None
    221
    +EMAIL_SSL_KEYFILE = None
    222
    +EMAIL_TIMEOUT = None
    223
    +# Default email address to use for various automated correspondence from
    224
    +# the site managers.
    225
    +DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
    226
    +# People who get code error notifications.
    227
    +# In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')]
    228
    +ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
    229
    +# Not-necessarily-technical managers of the site. They get broken link
    230
    +# notifications and other various emails.
    231
    +MANAGERS = ADMINS
    232
    +# Subject-line prefix for email messages send with django.core.mail.mail_admins
    233
    +# or ...mail_managers.  Make sure to include the trailing space.
    234
    +EMAIL_SUBJECT_PREFIX = u'[Templet] '
    235
    +
    236
    +# Admin Settings
    237
    +DISABLE_ACTION = False
    238
    +
    239
    +try:
    240
    +    from local_settings import *
    241
    +except ImportError:
    242
    +    pass
    243
    +
    244
    +# 依赖 local_settings 中的配置
    245
    +# 微信授权设置
    246
    +WECHAT_BASE_REDIRECT_URI = '{0}/we/base_redirect'.format(DOMAIN)
    247
    +WECHAT_USERINFO_REDIRECT_URI = '{0}/we/userinfo_redirect'.format(DOMAIN)
    248
    +WECHAT_OAUTH2_REDIRECT_URI = '{0}/we/oauth2?scope={{0}}redirect_url={{1}}'.format(DOMAIN)
    249
    +
    250
    +try:
    251
    +    from func_settings import redis_connect
    252
    +    REDIS_CACHE = redis_connect(REDIS.get('default', {}))
    253
    +except ImportError:
    254
    +    REDIS_CACHE = None

    + 331 - 0
    manual/static/templet/js/jswe.js

    @@ -0,0 +1,331 @@
    1
    +!(function(e, t) {
    2
    +    var config = {
    3
    +        wxconfig: 'http://api.pai.ai/wx/jsapi_signature',
    4
    +        callback: 'callback'
    5
    +    }, wxData = {
    6
    +        debug: false,
    7
    +        imgUrl: '',
    8
    +        link: '',
    9
    +        desc: '',
    10
    +        title: '',
    11
    +        timeLine: ''
    12
    +    }, wxConfig = {
    13
    +        hide: false,
    14
    +        close: false
    15
    +    }, jsApiList = [
    16
    +        'checkJsApi',
    17
    +        'onMenuShareTimeline',
    18
    +        'onMenuShareAppMessage',
    19
    +        'onMenuShareQQ',
    20
    +        'onMenuShareWeibo',
    21
    +        'hideMenuItems',
    22
    +        'showMenuItems',
    23
    +        'hideAllNonBaseMenuItem',
    24
    +        'showAllNonBaseMenuItem',
    25
    +        'translateVoice',
    26
    +        'startRecord',
    27
    +        'stopRecord',
    28
    +        'onRecordEnd',
    29
    +        'playVoice',
    30
    +        'pauseVoice',
    31
    +        'stopVoice',
    32
    +        'uploadVoice',
    33
    +        'downloadVoice',
    34
    +        'chooseImage',
    35
    +        'previewImage',
    36
    +        'uploadImage',
    37
    +        'downloadImage',
    38
    +        'getNetworkType',
    39
    +        'openLocation',
    40
    +        'getLocation',
    41
    +        'hideOptionMenu',
    42
    +        'showOptionMenu',
    43
    +        'closeWindow',
    44
    +        'scanQRCode',
    45
    +        'chooseWXPay',
    46
    +        'openEnterpriseRedPacket',
    47
    +        'openProductSpecificView',
    48
    +        'addCard',
    49
    +        'chooseCard',
    50
    +        'openCard'
    51
    +    ], wxApiFun
    52
    +
    53
    +    function isOpenOnPC() {  // 判断当前网页是否在 PC 浏览器中打开
    54
    +        var ua = navigator.userAgent
    55
    +        return /windows nt/i.test(ua) || /macintosh/i.test(ua) || /linux x86_64/i.test(ua)
    56
    +    }
    57
    +
    58
    +    function isOpenInWeixin() {  // 判断当前网页是否在微信内置浏览器中打开
    59
    +        return /micromessenger/i.test(navigator.userAgent)
    60
    +    }
    61
    +
    62
    +    function getWeixinVersion() {
    63
    +        var ua = navigator.userAgent,
    64
    +            mt = ua.match(/micromessenger\/([\d.]+)/i)
    65
    +        return (mt ? mt[1] : '')
    66
    +    }
    67
    +
    68
    +    // This function checks whether Wechat is the appointed version or not
    69
    +    // Cmp: http://jsperf.com/regexp-test-vs-indexof-ignore-upper-and-lower
    70
    +    function isWeixinVersion(version) {
    71
    +        // return new RegExp('micromessenger/' + version , 'i').test(navigator.userAgent)
    72
    +        return navigator.userAgent.toLowerCase().indexOf('micromessenger/' + version) != -1
    73
    +    }
    74
    +
    75
    +    function hideOptionMenu() {
    76
    +        wxConfig.hide = true
    77
    +        fixedWxData()
    78
    +    }
    79
    +
    80
    +    function showOptionMenu() {
    81
    +        wxConfig.hide = false
    82
    +        fixedWxData()
    83
    +    }
    84
    +
    85
    +    function closeWindow() {
    86
    +        wxConfig.close = true
    87
    +        fixedWxData()
    88
    +    }
    89
    +
    90
    +    function wxReady(data) {
    91
    +        data = typeof data === 'object' ? data : JSON.parse(data)
    92
    +        wx.config({
    93
    +            debug: wxData.debug,
    94
    +            appId: data.appId,
    95
    +            timestamp: data.timestamp,
    96
    +            nonceStr: data.nonceStr,
    97
    +            signature: data.signature,
    98
    +            jsApiList: jsApiList
    99
    +        })
    100
    +
    101
    +        var callbacks = {
    102
    +            trigger: function (res) {
    103
    +                // alert('用户点击发送给朋友')
    104
    +                if (JSWE.wxTrigger) {JSWE.wxTrigger(res)}
    105
    +            },
    106
    +            success: function (res) {
    107
    +                // alert('已分享')
    108
    +                if (JSWE.wxSuccess) {JSWE.wxSuccess(res)}
    109
    +            },
    110
    +            cancel: function (res) {
    111
    +                // alert('已取消')
    112
    +                if (JSWE.wxCancel) {JSWE.wxCancel(res)}
    113
    +            },
    114
    +            fail: function (res) {
    115
    +                // alert(JSON.stringify(res))
    116
    +                if (JSWE.wxFail) {JSWE.wxFail(res)}
    117
    +            }
    118
    +        }, shareInfo = function(flag) {
    119
    +            var _share = {
    120
    +                title: flag ? wxData.title : (wxData.timeLine || wxData.desc),
    121
    +                link: wxData.link,
    122
    +                imgUrl: wxData.imgUrl,
    123
    +                trigger: callbacks.trigger,
    124
    +                success: callbacks.success,
    125
    +                cancel: callbacks.cancel,
    126
    +                fail: callbacks.fail
    127
    +            }
    128
    +            if (flag) _share.desc = wxData.desc
    129
    +            return _share
    130
    +        }, wxShareApi = function() {
    131
    +            // 2. 分享接口
    132
    +            // 2.1 监听“分享给朋友”,按钮点击、自定义分享内容及分享结果接口
    133
    +            wx.onMenuShareAppMessage(shareInfo(1))
    134
    +            // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口
    135
    +            wx.onMenuShareTimeline(shareInfo(0))
    136
    +            // 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口
    137
    +            wx.onMenuShareQQ(shareInfo(1))
    138
    +            // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口
    139
    +            wx.onMenuShareWeibo(shareInfo(1))
    140
    +        }, wxMenuApi = function () {
    141
    +            // 8. 界面操作接口
    142
    +            // 8.1 隐藏右上角菜单
    143
    +            // 8.2 显示右上角菜单
    144
    +            if (wxConfig.hide) {wx.hideOptionMenu()} else {wx.showOptionMenu()}
    145
    +            // 8.7 关闭当前窗口
    146
    +            if (wxConfig.close) {wx.closeWindow()}
    147
    +        }, wxApi = function () {
    148
    +            wxShareApi()
    149
    +            wxMenuApi()
    150
    +        }
    151
    +
    152
    +        wx.ready(wxApi)
    153
    +
    154
    +        return wxApiFun = wxApi
    155
    +    }
    156
    +
    157
    +    if (isOpenInWeixin() || isOpenOnPC()) {
    158
    +        if ('undefined' !== typeof JSWE_CONF_UPDATE) JSWE_CONF_UPDATE(config)
    159
    +        $.ajax({
    160
    +            url: config.wxconfig,
    161
    +            type: 'get',
    162
    +            dataType: 'jsonp',
    163
    +            jsonpCallback: config.callback,
    164
    +            data: {
    165
    +                url: window.location.href.split('#')[0]
    166
    +            },
    167
    +            success: wxReady
    168
    +        })
    169
    +    }
    170
    +
    171
    +    function initWxData(data, flag) {
    172
    +        for(var d in data) {if (d in wxData) wxData[d] = data[d]}
    173
    +        if (flag) fixedWxData()
    174
    +    }
    175
    +
    176
    +    function changeWxData(key, value, flag) {
    177
    +        if (key in falDwxDataata) {wxData[key] = value}
    178
    +        if (flag) fixedWxData()
    179
    +    }
    180
    +
    181
    +    function fixedWxData() {
    182
    +        if ('undefined' !== typeof wxApiFun) wxApiFun()
    183
    +    }
    184
    +
    185
    +    // 5 图片接口
    186
    +    // 5.1 拍照、本地选图
    187
    +    var images = {
    188
    +        localIds: [],
    189
    +        serverIds: []
    190
    +    };
    191
    +    // function chooseImage(count, directUpload, isShowProgressTips) {
    192
    +    function chooseImage(choose_params) {
    193
    +        if ('undefined' === typeof choose_params) choose_params = {}
    194
    +        wx.chooseImage({
    195
    +            count: choose_params.count || 9, // 默认9
    196
    +            sizeType: choose_params.sizeType || ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
    197
    +            sourceType: choose_params.sourceType || ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
    198
    +            success: function (res) {
    199
    +                images.localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
    200
    +                // 判断是否直接上传
    201
    +                if (choose_params.directUpload) {setTimeout(uploadImages({localIds: images.localIds, isShowProgressTips: choose_params.isShowProgressTips || 1}), 100)}
    202
    +                // 拍照、本地选图成功后的回调函数
    203
    +                if (JSWE.wxChooseImageSuccess) {JSWE.wxChooseImageSuccess(res)}
    204
    +            }
    205
    +        });
    206
    +    }
    207
    +
    208
    +    // 5.2 图片预览
    209
    +    function previewImage(preview_params) {
    210
    +        wx.previewImage({
    211
    +            current: preview_params.current, // 当前显示图片的链接,不填则默认为 urls 的第一张
    212
    +            urls: preview_params.urls // 需要预览的图片链接列表
    213
    +        });
    214
    +    }
    215
    +
    216
    +    // 5.3 上传图片
    217
    +    // function uploadImage(localId, isShowProgressTips) {
    218
    +    function uploadImage(upload_params) {
    219
    +        // 上传图片为异步处理,重复上传同一图片,返回的serverId也是不同的
    220
    +        wx.uploadImage({
    221
    +            localId: upload_params.localId, // 需要上传的图片的本地ID,由chooseImage接口获得
    222
    +            isShowProgressTips: upload_params.isShowProgressTips || 1, // 默认为1,显示进度提示
    223
    +            success: function (res) {
    224
    +                images.serverIds.push(res.serverId); // 返回图片的服务器端ID
    225
    +                // 上传图片成功后的回调函数
    226
    +                if (JSWE.wxUploadImageSuccess) {JSWE.wxUploadImageSuccess(res)}
    227
    +            }
    228
    +        });
    229
    +    }
    230
    +
    231
    +    // function uploadImages(localIds, isShowProgressTips) {
    232
    +    function uploadImages(upload_params) {
    233
    +        var localIds = upload_params.localIds, isShowProgressTips = upload_params.isShowProgressTips || 1
    234
    +        images.serverIds = [];
    235
    +        for (var idx in localIds) {uploadImage({localId: localIds[idx], isShowProgressTips: isShowProgressTips})}
    236
    +    }
    237
    +
    238
    +    // 9 微信原生接口
    239
    +    // 9.1.1 扫描二维码并返回结果
    240
    +    // 9.1.2 扫描二维码并返回结果
    241
    +    function scanQRCode(scan_params) {
    242
    +        if ('undefined' === typeof scan_params) scan_params = {}
    243
    +        wx.scanQRCode({
    244
    +            needResult: scan_params.needResult || 0,  // 默认为0,0扫描结果由微信处理,1直接返回扫描结果
    245
    +            scanType: scan_params.scanType || ['qrCode', 'barCode'],  // 可以指定扫二维码还是一维码,默认二者都有
    246
    +            success: function (res) {  // 当 needResult 为 1 时,扫码返回的结果
    247
    +                if (JSWE.wxScanQRCodeSuccess) {JSWE.wxScanQRCodeSuccess(res)}
    248
    +            }
    249
    +        });
    250
    +    }
    251
    +
    252
    +    // QRCode & BarCode is different
    253
    +    function parseScanQRCodeResultStr(resultStr) {
    254
    +        var strs = resultStr.split(',')
    255
    +        return strs[strs.length - 1]
    256
    +    }
    257
    +
    258
    +    // 10 微信支付接口
    259
    +    // 10.1 发起一个支付请求
    260
    +    function chooseWXPay(wxpay_params) {
    261
    +        wx.chooseWXPay({
    262
    +            timestamp: wxpay_params.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
    263
    +            nonceStr: wxpay_params.nonceStr, // 支付签名随机串,不长于 32 位
    264
    +            package: wxpay_params.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
    265
    +            signType: wxpay_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
    266
    +            paySign: wxpay_params.paySign, // 支付签名
    267
    +            success: function (res) {
    268
    +                // 支付成功后的回调函数
    269
    +                if (JSWE.wxPaySuccess) {JSWE.wxPaySuccess(res)}
    270
    +            }
    271
    +        })
    272
    +    }
    273
    +
    274
    +    // xx 微信原生企业红包接口
    275
    +    // xx.1 发起一个发送原生企业红包请求
    276
    +    function openEnterpriseRedPacket(wxredpack_params) {
    277
    +        wx.openEnterpriseRedPacket({
    278
    +            timeStamp: wxredpack_params.timeStamp, // 红包签名时间戳,注意原生企业红包接口timeStamp字段名需大写其中的S字符,而支付接口timeStamp字段名无需大写其中的S字符。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
    279
    +            nonceStr: wxredpack_params.nonceStr, // 红包签名随机串,不长于 32 位
    280
    +            package: encodeURIComponent(wxredpack_params.package), // 发放红包接口返回的prepay_id参数值,提交格式如:prepay_id=***)
    281
    +            signType: wxredpack_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
    282
    +            paySign: wxredpack_params.paySign, // 红包签名
    283
    +            success: function (res) {
    284
    +                // 发送原生企业红包成功后的回调函数
    285
    +                if (JSWE.wxEnterpriseRedPacketSuccess) {JSWE.wxEnterpriseRedPacketSuccess(res)}
    286
    +            }
    287
    +        })
    288
    +    }
    289
    +
    290
    +    var v = {
    291
    +        version: '1.0.5',
    292
    +
    293
    +        // Basic Vars
    294
    +        config: config,
    295
    +        wxData: wxData,
    296
    +        jsApiList: jsApiList,
    297
    +
    298
    +        // Weixin Function
    299
    +        isOpenInWeixin: isOpenInWeixin,
    300
    +        getWeixinVersion: getWeixinVersion,
    301
    +        isWeixinVersion: isWeixinVersion,
    302
    +
    303
    +        // Menu Function
    304
    +        hideOptionMenu: hideOptionMenu,
    305
    +        showOptionMenu: showOptionMenu,
    306
    +        closeWindow: closeWindow,
    307
    +
    308
    +        // Share Function
    309
    +        initWxData: initWxData,
    310
    +        changeWxData: changeWxData,
    311
    +        fixedWxData: fixedWxData,
    312
    +
    313
    +        // Image Function
    314
    +        images: images,
    315
    +        chooseImage: chooseImage,
    316
    +        previewImage: previewImage,
    317
    +        uploadImage: uploadImage,
    318
    +        uploadImages: uploadImages,
    319
    +
    320
    +        // Scan Function
    321
    +        scanQRCode: scanQRCode,
    322
    +        parseScanQRCodeResultStr: parseScanQRCodeResultStr,
    323
    +
    324
    +        // Pay Function
    325
    +        chooseWXPay: chooseWXPay,
    326
    +
    327
    +        // EnterpriseRedPacket Function
    328
    +        openEnterpriseRedPacket: openEnterpriseRedPacket
    329
    +    }
    330
    +    e.JSWE = e.V = v
    331
    +})(window)

    + 32 - 0
    manual/urls.py

    @@ -0,0 +1,32 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +"""manual URL Configuration
    4
    +
    5
    +The `urlpatterns` list routes URLs to views. For more information please see:
    6
    +    https://docs.djangoproject.com/en/1.11/topics/http/urls/
    7
    +Examples:
    8
    +Function views
    9
    +    1. Add an import:  from my_app import views
    10
    +    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
    11
    +Class-based views
    12
    +    1. Add an import:  from other_app.views import Home
    13
    +    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
    14
    +Including another URLconf
    15
    +    1. Import the include() function: from django.conf.urls import url, include
    16
    +    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
    17
    +"""
    18
    +from django.conf import settings
    19
    +from django.conf.urls import include, url
    20
    +from django.conf.urls.static import static
    21
    +from django.contrib import admin
    22
    +
    23
    +
    24
    +urlpatterns = [
    25
    +    url(r'^admin/', admin.site.urls),
    26
    +    url(r'^api/', include('api.urls', namespace='api')),
    27
    +    url(r'^uniapi/', include('django_uniapi.urls', namespace='uniapi')),
    28
    +    url(r'^we/', include('django_we.urls', namespace='wechat')),
    29
    +]
    30
    +
    31
    +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    32
    +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

    + 17 - 0
    manual/wsgi.py

    @@ -0,0 +1,17 @@
    1
    +"""
    2
    +WSGI config for manual project.
    3
    +
    4
    +It exposes the WSGI callable as a module-level variable named ``application``.
    5
    +
    6
    +For more information on this file, see
    7
    +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
    8
    +"""
    9
    +
    10
    +import os
    11
    +
    12
    +from django.core.wsgi import get_wsgi_application
    13
    +
    14
    +
    15
    +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "manual.settings")
    16
    +
    17
    +application = get_wsgi_application()

    BIN
    media/file/201708/1502869267.33.png


    BIN
    media/file/201708/1502869283.66.png


    BIN
    media/file/201708/1502870309.11.png


    BIN
    media/file/201708/1502870443.78.png


    + 0 - 0
    message/__init__.py


    + 13 - 0
    message/admin.py

    @@ -0,0 +1,13 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.contrib import admin
    4
    +
    5
    +from message.models import MessageInfo
    6
    +
    7
    +
    8
    +class MessageInfoAdmin(admin.ModelAdmin):
    9
    +    list_display = ('msg_image', 'msg_image_airtime', 'msg_image_deadline', 'status', 'created_at', 'updated_at')
    10
    +    list_filter = ('status', )
    11
    +
    12
    +
    13
    +admin.site.register(MessageInfo, MessageInfoAdmin)

    + 8 - 0
    message/apps.py

    @@ -0,0 +1,8 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.apps import AppConfig
    5
    +
    6
    +
    7
    +class MessageConfig(AppConfig):
    8
    +    name = 'message'

    + 33 - 0
    message/migrations/0001_initial.py

    @@ -0,0 +1,33 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 07:03
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations, models
    6
    +import message.models
    7
    +
    8
    +
    9
    +class Migration(migrations.Migration):
    10
    +
    11
    +    initial = True
    12
    +
    13
    +    dependencies = [
    14
    +    ]
    15
    +
    16
    +    operations = [
    17
    +        migrations.CreateModel(
    18
    +            name='MessageInfo',
    19
    +            fields=[
    20
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    21
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    22
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    23
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    24
    +                ('msg_image', models.ImageField(blank=True, help_text='\u6d88\u606f\u56fe\u7247', null=True, upload_to=message.models.upload_path, verbose_name='msg_image')),
    25
    +                ('msg_image_airtime', models.DateTimeField(blank=True, help_text='\u6d88\u606f\u56fe\u7247\u5f00\u59cb\u65e5\u671f', null=True, verbose_name='msg_image_airtime')),
    26
    +                ('msg_image_deadline', models.DateTimeField(blank=True, help_text='\u6d88\u606f\u56fe\u7247\u622a\u6b62\u65e5\u671f', null=True, verbose_name='msg_image_deadline')),
    27
    +            ],
    28
    +            options={
    29
    +                'verbose_name': 'messageinfo',
    30
    +                'verbose_name_plural': 'messageinfo',
    31
    +            },
    32
    +        ),
    33
    +    ]

    + 19 - 0
    message/migrations/0002_auto_20170816_1604.py

    @@ -0,0 +1,19 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 08:04
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations
    6
    +
    7
    +
    8
    +class Migration(migrations.Migration):
    9
    +
    10
    +    dependencies = [
    11
    +        ('message', '0001_initial'),
    12
    +    ]
    13
    +
    14
    +    operations = [
    15
    +        migrations.AlterModelOptions(
    16
    +            name='messageinfo',
    17
    +            options={'verbose_name': '\u56fe\u7247\u6d88\u606f', 'verbose_name_plural': '\u56fe\u7247\u6d88\u606f'},
    18
    +        ),
    19
    +    ]

    + 0 - 0
    message/migrations/__init__.py


    + 41 - 0
    message/models.py

    @@ -0,0 +1,41 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +import os
    4
    +
    5
    +from django.db import models
    6
    +from django.utils.translation import ugettext_lazy as _
    7
    +from TimeConvert import TimeConvert as tc
    8
    +
    9
    +from manual.basemodels import CreateUpdateMixin
    10
    +from utils.url_utils import upload_file_url
    11
    +
    12
    +
    13
    +def upload_path(instance, old_filename):
    14
    +    return 'file/{ym}/{stamp}{ext}'.format(
    15
    +        ym=tc.local_string(format='%Y%m'),
    16
    +        stamp=tc.local_timestamp(ms=True),
    17
    +        ext=os.path.splitext(old_filename)[1].lower(),
    18
    +    )
    19
    +
    20
    +
    21
    +class MessageInfo(CreateUpdateMixin):
    22
    +    msg_image = models.ImageField(_(u'msg_image'), upload_to=upload_path, blank=True, null=True, help_text=u'消息图片')
    23
    +    msg_image_airtime = models.DateTimeField(_(u'msg_image_airtime'), blank=True, null=True, help_text=u'消息图片开始日期')
    24
    +    msg_image_deadline = models.DateTimeField(_(u'msg_image_deadline'), blank=True, null=True, help_text=u'消息图片截止日期')
    25
    +
    26
    +    class Meta:
    27
    +        verbose_name = _(u'图片消息')
    28
    +        verbose_name_plural = _(u'图片消息')
    29
    +
    30
    +    def __unicode__(self):
    31
    +        return unicode(self.pk)
    32
    +
    33
    +    @property
    34
    +    def msg_image_url(self):
    35
    +        return upload_file_url(self.msg_image)
    36
    +
    37
    +    @property
    38
    +    def data(self):
    39
    +        return {
    40
    +            'msg_image_url': self.msg_image_url,
    41
    +        }

    + 19 - 0
    message/msg_views.py

    @@ -0,0 +1,19 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from logit import logit
    5
    +from TimeConvert import TimeConvert as tc
    6
    +
    7
    +from message.models import MessageInfo
    8
    +from utils.error.response_utils import response
    9
    +
    10
    +
    11
    +@logit
    12
    +def msg_list_api(request):
    13
    +    curdt = tc.utc_datetime()
    14
    +    msgs = MessageInfo.objects.filter(msg_image_airtime__lte=curdt, msg_image_deadline__gt=curdt, status=True)
    15
    +    msgs = [msg.data for msg in msgs]
    16
    +
    17
    +    return response(200, 'Get Message List Success', u'获取消息列表成功', {
    18
    +        'msgs': msgs,
    19
    +    })

    + 7 - 0
    message/tests.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.test import TestCase
    5
    +
    6
    +
    7
    +# Create your tests here.

    + 7 - 0
    message/views.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.shortcuts import render
    5
    +
    6
    +
    7
    +# Create your views here.

    + 9 - 0
    pep8.sh

    @@ -0,0 +1,9 @@
    1
    +#!/bin/bash
    2
    +
    3
    +# Ignoring autogenerated files
    4
    +#  -- Migration directories
    5
    +# Ignoring error codes
    6
    +#  -- E128 continuation line under-indented for visual indent
    7
    +#  -- E501 line too long
    8
    +
    9
    +pep8 --exclude=migrations --ignore=E128,E501 .

    + 15 - 0
    requirements.txt

    @@ -0,0 +1,15 @@
    1
    +-e git+https://github.com/andymccurdy/redis-py.git#egg=redis-py
    2
    +Django==1.11.3
    3
    +MySQL-python==1.2.5
    4
    +StatusCode==1.0.0
    5
    +TimeConvert==1.4.1
    6
    +django-admin==1.0.4
    7
    +django-detect==1.0.5
    8
    +django-json-response==1.1.5
    9
    +django-logit==1.0.6
    10
    +django-paginator2==1.0.3
    11
    +django-shortuuidfield==0.1.3
    12
    +django-uniapi==1.0.0
    13
    +django-we==1.0.7
    14
    +hiredis==0.2.0
    15
    +redis-extensions==1.1.1

    + 0 - 0
    support/__init__.py


    + 25 - 0
    support/admin.py

    @@ -0,0 +1,25 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.contrib import admin
    4
    +
    5
    +from support.models import MachineBackInfo, MachineBodyInfo, MachineSupportPrebookInfo
    6
    +
    7
    +
    8
    +class MachineBodyInfoAdmin(admin.ModelAdmin):
    9
    +    list_display = ('body', 'status', 'created_at', 'updated_at')
    10
    +    list_filter = ('status', )
    11
    +
    12
    +
    13
    +class MachineBackInfoAdmin(admin.ModelAdmin):
    14
    +    list_display = ('back', 'status', 'created_at', 'updated_at')
    15
    +    list_filter = ('status', )
    16
    +
    17
    +
    18
    +class MachineSupportPrebookInfoAdmin(admin.ModelAdmin):
    19
    +    list_display = ('user_id', 'name', 'sex', 'phone', 'weekday', 'timeslice', 'body', 'back', 'handle_status', 'status', 'created_at', 'updated_at')
    20
    +    list_filter = ('weekday', 'timeslice', 'body', 'back', 'handle_status', 'status')
    21
    +
    22
    +
    23
    +admin.site.register(MachineBodyInfo, MachineBodyInfoAdmin)
    24
    +admin.site.register(MachineBackInfo, MachineBackInfoAdmin)
    25
    +admin.site.register(MachineSupportPrebookInfo, MachineSupportPrebookInfoAdmin)

    + 8 - 0
    support/apps.py

    @@ -0,0 +1,8 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.apps import AppConfig
    5
    +
    6
    +
    7
    +class SupportConfig(AppConfig):
    8
    +    name = 'support'

    + 66 - 0
    support/migrations/0001_initial.py

    @@ -0,0 +1,66 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 07:03
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations, models
    6
    +import django.db.models.deletion
    7
    +
    8
    +
    9
    +class Migration(migrations.Migration):
    10
    +
    11
    +    initial = True
    12
    +
    13
    +    dependencies = [
    14
    +    ]
    15
    +
    16
    +    operations = [
    17
    +        migrations.CreateModel(
    18
    +            name='MachineBackInfo',
    19
    +            fields=[
    20
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    21
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    22
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    23
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    24
    +                ('back', models.CharField(blank=True, help_text='\u673a\u80cc', max_length=255, null=True, unique=True, verbose_name='back')),
    25
    +            ],
    26
    +            options={
    27
    +                'verbose_name': '\u673a\u80cc\u914d\u7f6e',
    28
    +                'verbose_name_plural': '\u673a\u80cc\u914d\u7f6e',
    29
    +            },
    30
    +        ),
    31
    +        migrations.CreateModel(
    32
    +            name='MachineBodyInfo',
    33
    +            fields=[
    34
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    35
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    36
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    37
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    38
    +                ('body', models.CharField(blank=True, help_text='\u673a\u8eab', max_length=255, null=True, unique=True, verbose_name='body')),
    39
    +            ],
    40
    +            options={
    41
    +                'verbose_name': '\u673a\u8eab\u914d\u7f6e',
    42
    +                'verbose_name_plural': '\u673a\u8eab\u914d\u7f6e',
    43
    +            },
    44
    +        ),
    45
    +        migrations.CreateModel(
    46
    +            name='MachineSupportPrebookInfo',
    47
    +            fields=[
    48
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    49
    +                ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
    50
    +                ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
    51
    +                ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
    52
    +                ('user_id', models.CharField(blank=True, db_index=True, help_text='\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=255, null=True, verbose_name='user_id')),
    53
    +                ('name', models.CharField(blank=True, help_text='\u7528\u6237\u59d3\u540d', max_length=255, null=True, verbose_name='name')),
    54
    +                ('sex', models.IntegerField(choices=[(1, '\u7537'), (0, '\u5973')], default=1, help_text='\u7528\u6237\u6027\u522b', verbose_name='sex')),
    55
    +                ('phone', models.CharField(blank=True, db_index=True, help_text='\u7528\u6237\u7535\u8bdd', max_length=255, null=True, verbose_name='phone')),
    56
    +                ('weekday', models.IntegerField(db_index=True, default=0, help_text='\u5468\u51e0\uff1a\u5468\u65e5\u4e3a0', verbose_name='weekday')),
    57
    +                ('timeslice', models.IntegerField(choices=[(0, '09:00 - 12:00'), (0, '12:00 - 14:00'), (0, '14:00 - 18:00'), (0, '18:00 - 21:00')], db_index=True, default=0, help_text='\u65f6\u95f4\u6bb5', verbose_name='timeslice')),
    58
    +                ('back', models.ForeignKey(blank=True, help_text='\u673a\u80cc', null=True, on_delete=django.db.models.deletion.CASCADE, to='support.MachineBackInfo', verbose_name='back')),
    59
    +                ('body', models.ForeignKey(blank=True, help_text='\u673a\u8eab', null=True, on_delete=django.db.models.deletion.CASCADE, to='support.MachineBodyInfo', verbose_name='body')),
    60
    +            ],
    61
    +            options={
    62
    +                'verbose_name': 'machinesupportprebookinfo',
    63
    +                'verbose_name_plural': 'machinesupportprebookinfo',
    64
    +            },
    65
    +        ),
    66
    +    ]

    + 25 - 0
    support/migrations/0002_auto_20170816_1509.py

    @@ -0,0 +1,25 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 07:09
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations, models
    6
    +
    7
    +
    8
    +class Migration(migrations.Migration):
    9
    +
    10
    +    dependencies = [
    11
    +        ('support', '0001_initial'),
    12
    +    ]
    13
    +
    14
    +    operations = [
    15
    +        migrations.AddField(
    16
    +            model_name='machinebackinfo',
    17
    +            name='position',
    18
    +            field=models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position'),
    19
    +        ),
    20
    +        migrations.AddField(
    21
    +            model_name='machinebodyinfo',
    22
    +            name='position',
    23
    +            field=models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position'),
    24
    +        ),
    25
    +    ]

    + 20 - 0
    support/migrations/0003_machinesupportprebookinfo_handle_status.py

    @@ -0,0 +1,20 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 07:34
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations, models
    6
    +
    7
    +
    8
    +class Migration(migrations.Migration):
    9
    +
    10
    +    dependencies = [
    11
    +        ('support', '0002_auto_20170816_1509'),
    12
    +    ]
    13
    +
    14
    +    operations = [
    15
    +        migrations.AddField(
    16
    +            model_name='machinesupportprebookinfo',
    17
    +            name='handle_status',
    18
    +            field=models.IntegerField(choices=[(0, '\u672a\u5904\u7406'), (1, '\u5df2\u8054\u7cfb'), (10, '\u5df2\u5904\u7406')], default=0, help_text='\u5904\u7406\u72b6\u6001', verbose_name='handle_status'),
    19
    +        ),
    20
    +    ]

    + 20 - 0
    support/migrations/0004_auto_20170816_1604.py

    @@ -0,0 +1,20 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +# Generated by Django 1.11.3 on 2017-08-16 08:04
    3
    +from __future__ import unicode_literals
    4
    +
    5
    +from django.db import migrations, models
    6
    +
    7
    +
    8
    +class Migration(migrations.Migration):
    9
    +
    10
    +    dependencies = [
    11
    +        ('support', '0003_machinesupportprebookinfo_handle_status'),
    12
    +    ]
    13
    +
    14
    +    operations = [
    15
    +        migrations.AlterField(
    16
    +            model_name='machinesupportprebookinfo',
    17
    +            name='timeslice',
    18
    +            field=models.IntegerField(choices=[(0, '09:00 - 12:00'), (1, '12:00 - 14:00'), (2, '14:00 - 18:00'), (3, '18:00 - 21:00')], db_index=True, default=0, help_text='\u65f6\u95f4\u6bb5', verbose_name='timeslice'),
    19
    +        ),
    20
    +    ]

    + 0 - 0
    support/migrations/__init__.py


    + 94 - 0
    support/models.py

    @@ -0,0 +1,94 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.db import models
    4
    +from django.utils.translation import ugettext_lazy as _
    5
    +
    6
    +from manual.basemodels import CreateUpdateMixin
    7
    +
    8
    +
    9
    +class MachineBodyInfo(CreateUpdateMixin):
    10
    +    body = models.CharField(_(u'body'), max_length=255, blank=True, null=True, help_text=u'机身', unique=True)
    11
    +    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
    12
    +
    13
    +    class Meta:
    14
    +        verbose_name = _(u'机身配置')
    15
    +        verbose_name_plural = _(u'机身配置')
    16
    +
    17
    +    def __unicode__(self):
    18
    +        return unicode(self.body)
    19
    +
    20
    +    @property
    21
    +    def data(self):
    22
    +        return {
    23
    +            'pk': self.pk,
    24
    +            'body': self.body,
    25
    +        }
    26
    +
    27
    +
    28
    +class MachineBackInfo(CreateUpdateMixin):
    29
    +    back = models.CharField(_(u'back'), max_length=255, blank=True, null=True, help_text=u'机背', unique=True)
    30
    +    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
    31
    +
    32
    +    class Meta:
    33
    +        verbose_name = _(u'机背配置')
    34
    +        verbose_name_plural = _(u'机背配置')
    35
    +
    36
    +    def __unicode__(self):
    37
    +        return unicode(self.back)
    38
    +
    39
    +    @property
    40
    +    def data(self):
    41
    +        return {
    42
    +            'pk': self.pk,
    43
    +            'back': self.back,
    44
    +        }
    45
    +
    46
    +
    47
    +class MachineSupportPrebookInfo(CreateUpdateMixin):
    48
    +    MALE = 1
    49
    +    FEMALE = 0
    50
    +
    51
    +    SEX_TYPE = (
    52
    +        (MALE, u'男'),
    53
    +        (FEMALE, u'女'),
    54
    +    )
    55
    +
    56
    +    SLICE0 = 0
    57
    +    SLICE1 = 1
    58
    +    SLICE2 = 2
    59
    +    SLICE3 = 3
    60
    +
    61
    +    TIME_SLICE = (
    62
    +        (SLICE0, u'09:00 - 12:00'),
    63
    +        (SLICE1, u'12:00 - 14:00'),
    64
    +        (SLICE2, u'14:00 - 18:00'),
    65
    +        (SLICE3, u'18:00 - 21:00'),
    66
    +    )
    67
    +
    68
    +    NOT_HANDLE = 0
    69
    +    HAS_CONTACTED = 1
    70
    +    HAS_HANDLED = 10
    71
    +
    72
    +    HANDLE_STATUS = (
    73
    +        (NOT_HANDLE, u'未处理'),
    74
    +        (HAS_CONTACTED, u'已联系'),
    75
    +        (HAS_HANDLED, u'已处理'),
    76
    +    )
    77
    +
    78
    +    user_id = models.CharField(_(u'user_id'), max_length=255, blank=True, null=True, help_text=u'用户唯一标识', db_index=True)
    79
    +    name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'用户姓名')
    80
    +    sex = models.IntegerField(_(u'sex'), choices=SEX_TYPE, default=MALE, help_text=u'用户性别')
    81
    +    phone = models.CharField(_(u'phone'), max_length=255, blank=True, null=True, help_text=u'用户电话', db_index=True)
    82
    +    weekday = models.IntegerField(_(u'weekday'), default=0, help_text=u'周几:周日为0', db_index=True)
    83
    +    timeslice = models.IntegerField(_(u'timeslice'), default=SLICE0, choices=TIME_SLICE, help_text=u'时间段', db_index=True)
    84
    +    body = models.ForeignKey(MachineBodyInfo, verbose_name=_(u'body'), blank=True, null=True, help_text=u'机身')
    85
    +    back = models.ForeignKey(MachineBackInfo, verbose_name=_(u'back'), blank=True, null=True, help_text=u'机背')
    86
    +
    87
    +    handle_status = models.IntegerField(_(u'handle_status'), choices=HANDLE_STATUS, default=NOT_HANDLE, help_text=u'处理状态')
    88
    +
    89
    +    class Meta:
    90
    +        verbose_name = _(u'machinesupportprebookinfo')
    91
    +        verbose_name_plural = _(u'machinesupportprebookinfo')
    92
    +
    93
    +    def __unicode__(self):
    94
    +        return unicode(self.pk)

    + 7 - 0
    support/tests.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from django.test import TestCase
    5
    +
    6
    +
    7
    +# Create your tests here.

    + 57 - 0
    support/views.py

    @@ -0,0 +1,57 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +from __future__ import unicode_literals
    3
    +
    4
    +from logit import logit
    5
    +
    6
    +from support.models import MachineBackInfo, MachineBodyInfo, MachineSupportPrebookInfo
    7
    +from utils.error.errno_utils import MachineStatusCode
    8
    +from utils.error.response_utils import response
    9
    +
    10
    +
    11
    +@logit
    12
    +def support_info_api(request):
    13
    +    bodys = MachineBodyInfo.objects.filter(status=True).order_by('position')
    14
    +    bodys = [body.data for body in bodys]
    15
    +
    16
    +    backs = MachineBackInfo.objects.filter(status=True).order_by('position')
    17
    +    backs = [back.data for back in backs]
    18
    +
    19
    +    return response(200, 'Get Support Info Success', u'获取支持信息成功', {
    20
    +        'bodys': bodys,
    21
    +        'backs': backs,
    22
    +    })
    23
    +
    24
    +
    25
    +@logit
    26
    +def support_prebook_submit_api(request):
    27
    +    user_id = request.POST.get('user_id', '')
    28
    +    name = request.POST.get('name', '')
    29
    +    sex = int(request.POST.get('sex', 0))
    30
    +    phone = request.POST.get('phone', '')
    31
    +    weekday = int(request.POST.get('weekday', 0))
    32
    +    timeslice = int(request.POST.get('timeslice', 0))
    33
    +    body = int(request.POST.get('body', 0))
    34
    +    back = int(request.POST.get('back', 0))
    35
    +
    36
    +    try:
    37
    +        body = MachineBodyInfo.objects.get(pk=body)
    38
    +    except MachineBodyInfo.DoesNotExist:
    39
    +        return response(MachineStatusCode.MACHINE_BODY_NOT_FOUND)
    40
    +
    41
    +    try:
    42
    +        back = MachineBackInfo.objects.get(pk=back)
    43
    +    except MachineBackInfo.DoesNotExist:
    44
    +        return response(MachineStatusCode.MACHINE_BACK_NOT_FOUND)
    45
    +
    46
    +    MachineSupportPrebookInfo.objects.create(
    47
    +        user_id=user_id,
    48
    +        name=name,
    49
    +        sex=sex,
    50
    +        phone=phone,
    51
    +        weekday=weekday,
    52
    +        timeslice=timeslice,
    53
    +        body=body,
    54
    +        back=back,
    55
    +    )
    56
    +
    57
    +    return response(200, 'Submit Support Info Success', u'提交支持信息成功')

    + 0 - 0
    utils/__init__.py


    + 0 - 0
    utils/error/__init__.py


    + 43 - 0
    utils/error/errno_utils.py

    @@ -0,0 +1,43 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from StatusCode import BaseStatusCode, StatusCodeField
    4
    +
    5
    +
    6
    +class ProfileStatusCode(BaseStatusCode):
    7
    +    """ 用户相关错误码 4001xx """
    8
    +    PROFILE_NOT_FOUND = StatusCodeField(400101, 'Profile Not Found', description=u'用户不存在')
    9
    +    # 手机号
    10
    +    PHONE_ALREADY_EXISTS = StatusCodeField(400105, 'Phone Already Exists', description=u'手机号已经存在')
    11
    +
    12
    +
    13
    +class MachineStatusCode(BaseStatusCode):
    14
    +    """ 机器相关错误码 4021xx """
    15
    +    MACHINE_BODY_NOT_FOUND = StatusCodeField(402101, 'Machine Body Not Found', description=u'机身不存在')
    16
    +    MACHINE_BACK_NOT_FOUND = StatusCodeField(402102, 'Machine Back Not Found', description=u'机背不存在')
    17
    +
    18
    +
    19
    +class OrderStatusCode(BaseStatusCode):
    20
    +    """ 订单/支付相关错误码 4040xx """
    21
    +    UNIFIED_ORDER_FAIL = StatusCodeField(404000, 'Unified Order Fail', description=u'统一下单失败')
    22
    +    ORDER_NOT_FOUND = StatusCodeField(404001, 'Order Not Found', description=u'订单不存在')
    23
    +    # 订单支付状态
    24
    +    ORDER_NOT_PAY = StatusCodeField(404011, 'Order Not Pay', description=u'订单未支付')
    25
    +    ORDER_PAYING = StatusCodeField(404012, 'Order Paying', description=u'订单支付中')
    26
    +    ORDER_PAY_FAIL = StatusCodeField(404013, 'Order Pay Fail', description=u'微信支付失败')
    27
    +    # 通知校验状态
    28
    +    SIGN_CHECK_FAIL = StatusCodeField(404090, 'Sign Check Fail', description=u'签名校验失败')
    29
    +    FEE_CHECK_FAIL = StatusCodeField(404091, 'FEE Check Fail', description=u'金额校验失败')
    30
    +
    31
    +
    32
    +class PayStatusCode(BaseStatusCode):
    33
    +    """ 支付相关错误码 4041xx """
    34
    +
    35
    +
    36
    +class WithdrawStatusCode(BaseStatusCode):
    37
    +    """ 提现相关错误码 4042xx """
    38
    +    BALANCE_INSUFFICIENT = StatusCodeField(404200, 'Balance Insufficient', description=u'提现金额不足')
    39
    +
    40
    +
    41
    +class TokenStatusCode(BaseStatusCode):
    42
    +    """ 票据相关错误码 4090xx """
    43
    +    TOKEN_NOT_FOUND = StatusCodeField(409901, 'Token Not Found', description=u'票据不存在')

    + 18 - 0
    utils/error/response_utils.py

    @@ -0,0 +1,18 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.http import JsonResponse
    4
    +from StatusCode import StatusCodeField
    5
    +
    6
    +
    7
    +def response_data(status_code=200, message=None, description=None, data={}, **kwargs):
    8
    +    return dict({
    9
    +        'status': status_code,
    10
    +        'message': message,
    11
    +        'description': description,
    12
    +        'data': data,
    13
    +    }, **kwargs)
    14
    +
    15
    +
    16
    +def response(status_code=200, message=None, description=None, data={}, **kwargs):
    17
    +    message, description = (message or status_code.message, description or status_code.description) if isinstance(status_code, StatusCodeField) else (message, description)
    18
    +    return JsonResponse(response_data(status_code, message, description, data, **kwargs), safe=False)

    + 0 - 0
    utils/redis/__init__.py


    + 6 - 0
    utils/redis/connect.py

    @@ -0,0 +1,6 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.conf import settings
    4
    +
    5
    +
    6
    +r = settings.REDIS_CACHE

    + 1 - 0
    utils/redis/rkeys.py

    @@ -0,0 +1 @@
    1
    +# -*- coding: utf-8 -*-

    + 7 - 0
    utils/url_utils.py

    @@ -0,0 +1,7 @@
    1
    +# -*- coding: utf-8 -*-
    2
    +
    3
    +from django.conf import settings
    4
    +
    5
    +
    6
    +def upload_file_url(file_path):
    7
    +    return file_path and ('{}{}'.format(settings.DOMAIN, file_path.url)) or ''

    pai2 - Gogs: Go Git Service

    拍爱

    Brightcells: 212e3994d1 MP verify file 8 lat temu
    ..
    wx 212e3994d1 MP verify file 8 lat temu