s-num-new"> 30
+                'verbose_name_plural': 'encrypttextinfo',
31
+            },
32
+        ),
33
+    ]

+ 0 - 0
encrypt/migrations/__init__.py


+ 19 - 0
encrypt/models.py

@@ -0,0 +1,19 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.db import models
4
+from django.utils.translation import ugettext_lazy as _
5
+from models_ext.basemodels import BaseModelMixin
6
+from shortuuidfield import ShortUUIDField
7
+
8
+
9
+class EncryptTextInfo(BaseModelMixin):
10
+    ciphertext = ShortUUIDField(_(u'ciphertext'), max_length=32, blank=True, null=True, help_text=u'密文', db_index=True)
11
+    plaintext = models.TextField(_(u'plaintext'), blank=True, null=True, help_text=_(u'明文'))
12
+    plaintext_md5 = models.CharField(_(u'plaintext'), max_length=32, blank=True, null=True, help_text=_(u'明文MD5'))
13
+
14
+    class Meta:
15
+        verbose_name = _(u'encrypttextinfo')
16
+        verbose_name_plural = _(u'encrypttextinfo')
17
+
18
+    def __unicode__(self):
19
+        return u'{0.pk}'.format(self)

+ 7 - 0
encrypt/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
encrypt/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 . .

+ 0 - 0
kodo/__init__.py


+ 54 - 0
kodo/decorators.py

@@ -0,0 +1,54 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from functools import wraps
4
+
5
+from django.conf import settings
6
+from django.shortcuts import redirect
7
+from furl import furl
8
+from pywe_oauth import get_oauth_redirect_url
9
+from pywe_sign import check_signature
10
+
11
+from utils.error.errno_utils import SignatureStatusCode
12
+from utils.error.response_utils import response
13
+from utils.redis.connect import r
14
+
15
+
16
+def check_token(func=None, entry=None):
17
+    def decorator(func):
18
+        @wraps(func)
19
+        def returned_wrapper(request, *args, **kwargs):
20
+            if not settings.DEBUG and request.wechat:
21
+                vtoken = request.GET.get('vtoken', '') or request.POST.get('vtoken', '')
22
+                token_check_key = request.GET.get(settings.TOKEN_CHECK_KEY, '') or request.POST.get(settings.TOKEN_CHECK_KEY, '')
23
+                if not r.token_exists(token_check_key, vtoken):
24
+                    # 3rd OAuth
25
+                    # return redirect(settings.WECHAT_OAUTH2_REDIRECT_URL)
26
+                    # Current OAuth
27
+                    redirect_url = furl(entry or settings.WECHAT_OAUTH2_REDIRECT_ENTRY).add({}).url
28
+                    return redirect(get_oauth_redirect_url(settings.WECHAT_OAUTH2_REDIRECT_URI, 'snsapi_userinfo', redirect_url))
29
+            return func(request, *args, **kwargs)
30
+        return returned_wrapper
31
+
32
+    if not func:
33
+        def foo(func):
34
+            return decorator(func)
35
+        return foo
36
+
37
+    return decorator(func)
38
+
39
+
40
+def check_sign(func=None, method='POST'):
41
+    def decorator(func):
42
+        @wraps(func)
43
+        def returned_wrapper(request, *args, **kwargs):
44
+            if not settings.DEBUG and not check_signature(getattr(request, method).dict(), settings.PARAMS_SIGN_KEY):
45
+                return response(SignatureStatusCode.SIGNATURE_ERROR)
46
+            return func(request, *args, **kwargs)
47
+        return returned_wrapper
48
+
49
+    if not func:
50
+        def foo(func):
51
+            return decorator(func)
52
+        return foo
53
+
54
+    return decorator(func)

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

@@ -0,0 +1,27 @@
1
+# kodo_uwsgi.ini file
2
+[uwsgi]
3
+
4
+# Django-related settings
5
+# the base directory (full path)
6
+chdir           = /home/paiai/work/kodo
7
+# Django's wsgi file
8
+module          = kodo.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/kodo/kodo/deploy/kodo.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
kodo/deploy.bak/kodo_nginx.conf

@@ -0,0 +1,40 @@
1
+# kodo_nginx.conf
2
+
3
+# the upstream component nginx needs to connect to
4
+upstream kodo {
5
+    # server unix:///home/paiai/work/kodo/kodo/deploy/kodo.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/kodo/docs/we/xxx.txt;
23
+    }
24
+
25
+    # Django media
26
+    location /media  {
27
+        alias /home/paiai/work/kodo/media;  # your Django project's media files - amend as required
28
+    }
29
+
30
+    location /static {
31
+        alias /home/paiai/work/kodo/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  kodo;
37
+        proxy_pass  http://kodo;
38
+        include     /home/paiai/work/kodo/kodo/deploy/uwsgi_params; # the uwsgi_params file you installed
39
+    }
40
+}

+ 10 - 0
kodo/deploy.bak/kodo_supervisor.ini

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

+ 15 - 0
kodo/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
kodo/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)))

+ 17 - 0
kodo/local_settings_bak.py

@@ -0,0 +1,17 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+# DEBUG = False
4
+
5
+ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'kodo']
6
+
7
+# DOMAIN
8
+DOMAIN = 'http://a.com'
9
+
10
+# 邮件设置
11
+# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
12
+SERVER_EMAIL = 'error.notify@exmail.com'
13
+EMAIL_HOST_USER = 'error.notify@exmail.com'
14
+EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
15
+DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
16
+ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
17
+EMAIL_SUBJECT_PREFIX = u'[Kodo] '

+ 27 - 0
kodo/local_settings_dev_bak.py

@@ -0,0 +1,27 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import os
4
+
5
+
6
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7
+PROJ_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
8
+
9
+TEMPLATES = [
10
+    {
11
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
12
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
13
+        # 'APP_DIRS': True,
14
+        'OPTIONS': {
15
+            'context_processors': [
16
+                'django.template.context_processors.debug',
17
+                'django.template.context_processors.request',
18
+                'django.contrib.auth.context_processors.auth',
19
+                'django.contrib.messages.context_processors.messages',
20
+            ],
21
+            'loaders': [
22
+                'django.template.loaders.filesystem.Loader',
23
+                'django.template.loaders.app_directories.Loader',
24
+            ],
25
+        },
26
+    },
27
+]

+ 44 - 0
kodo/oauth_settings.py

@@ -0,0 +1,44 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def DJANGO_WE_CFG_FUNC(request, state=None):
5
+    """ WeChat CFG Callback Func """
6
+
7
+
8
+def DJANGO_WE_QUOTE_STATE_FUNC(request, state):
9
+    """ WeChat Quote Callback Func """
10
+    from utils.redis.connect import r
11
+    return r.quote(state)
12
+
13
+
14
+def DJANGO_WE_UNQUOTE_STATE_FUNC(request, state):
15
+    """ WeChat UnQuote Callback Func """
16
+    from utils.redis.connect import r
17
+    return r.unquote(state) or state
18
+
19
+
20
+def DJANGO_WE_BASE_FUNC(code, state, access_info=None):
21
+    """ WeChat Base Redirect Callback Func """
22
+
23
+
24
+def DJANGO_WE_USERINFO_FUNC(code, state, access_info=None, userinfo=None):
25
+    """ WeChat Userinfo Redirect Callback Func """
26
+    from django.conf import settings
27
+    from utils.redis.connect import r
28
+    from utils.user.userinfo_save import userinfo_save
29
+
30
+    # Save profile or something else
31
+    user = userinfo_save(userinfo)
32
+
33
+    token_check_key = getattr(user, settings.TOKEN_CHECK_KEY)
34
+
35
+    return {
36
+        settings.TOKEN_CHECK_KEY: token_check_key,
37
+        'vtoken': r.token(token_check_key, ex=False, buf=False),
38
+    }
39
+
40
+
41
+def DJANGO_WE_SHARE_FUNC(request, state=None):
42
+    """ WeChat Share Callback Func """
43
+    # from django.conf import settings
44
+    # return settings.WECHAT_OAUTH2_REDIRECT_URL

+ 321 - 0
kodo/settings.py

@@ -0,0 +1,321 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+"""
4
+Django settings for kodo 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_short_url',
45
+    'django_uniapi',
46
+    'django_we',
47
+    'api',
48
+    'encrypt',
49
+    'mch',
50
+]
51
+
52
+MIDDLEWARE = [
53
+    'django.middleware.security.SecurityMiddleware',
54
+    'django.contrib.sessions.middleware.SessionMiddleware',
55
+    'django.middleware.common.CommonMiddleware',
56
+    # 'django.middleware.csrf.CsrfViewMiddleware',
57
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
58
+    'django.contrib.messages.middleware.MessageMiddleware',
59
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
60
+    'detect.middleware.UserAgentDetectionMiddleware',
61
+]
62
+
63
+ROOT_URLCONF = 'kodo.urls'
64
+
65
+TEMPLATES = [
66
+    {
67
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
68
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
69
+        # 'APP_DIRS': True,
70
+        'OPTIONS': {
71
+            'context_processors': [
72
+                'django.template.context_processors.debug',
73
+                'django.template.context_processors.request',
74
+                'django.contrib.auth.context_processors.auth',
75
+                'django.contrib.messages.context_processors.messages',
76
+            ],
77
+            'loaders': [
78
+                ('django.template.loaders.cached.Loader', [
79
+                    'django.template.loaders.filesystem.Loader',
80
+                    'django.template.loaders.app_directories.Loader',
81
+                ]),
82
+            ],
83
+        },
84
+    },
85
+]
86
+
87
+WSGI_APPLICATION = 'kodo.wsgi.application'
88
+
89
+
90
+# Database
91
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
92
+
93
+DATABASES = {
94
+    'default': {
95
+        'ENGINE': 'django.db.backends.mysql',
96
+        'NAME': 'kodo',
97
+        'USER': 'root',
98
+        'PASSWORD': '',
99
+        'HOST': '127.0.0.1',
100
+        'PORT': 3306,
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
+# File 设置
170
+FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880  # InMemoryUploadedFile 文件最大值,设置为 5 MB
171
+FILE_UPLOAD_PERMISSIONS = 0o644  # TemporaryUploadedFile 文件权限设置
172
+
173
+# DOMAIN
174
+DOMAIN = 'http://a.com'
175
+
176
+# Redis 设置
177
+REDIS = {
178
+    'default': {
179
+        'HOST': '127.0.0.1',
180
+        'PORT': 6379,
181
+        'USER': '',
182
+        'PASSWORD': '',
183
+        'db': 0,
184
+    }
185
+}
186
+
187
+# 微信设置
188
+WECHAT = {
189
+    'JSAPI': {
190
+        'token': '5201314',
191
+        'appID': '',
192
+        'appsecret': '',
193
+        'mchID': '',
194
+        'apiKey': '',
195
+        'mch_cert': '',
196
+        'mch_key': '',
197
+        'redpack': {
198
+
199
+        }
200
+    },
201
+}
202
+
203
+# 微信唯一标识
204
+# Choices: 'unionid' or 'openid'
205
+#
206
+# models.py
207
+#   'unique_identifier': self.unionid if settings.WECHAT_UNIQUE_IDENTIFICATION == 'unionid' else self.openid,
208
+# views.py
209
+#   unique_identifier = request.POST.get(settings.WECHAT_UNIQUE_IDENTIFICATION, '')
210
+#   profile = Profile.objects.get(**{settings.WECHAT_UNIQUE_IDENTIFICATION: unique_identifier})
211
+WECHAT_UNIQUE_IDENTIFICATION = 'unionid'
212
+
213
+# Token 错误重授权设置
214
+TOKEN_CHECK_KEY = ''
215
+# TOKEN_CHECK_KEY = 'user_id'
216
+WECHAT_OAUTH2_REDIRECT_ENTRY = ''
217
+WECHAT_OAUTH2_REDIRECT_URL = ''
218
+
219
+# 邮件设置
220
+# https://docs.djangoproject.com/en/1.11/howto/error-reporting/#email-reports
221
+# When DEBUG is False, Django will email the users listed in the ADMINS setting
222
+# whenever your code raises an unhandled exception and results in an internal server error (HTTP status code 500).
223
+# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
224
+# Email address that error messages come from.
225
+SERVER_EMAIL = 'error.notify@exmail.com'
226
+# The email backend to use. For possible shortcuts see django.core.mail.
227
+# The default is to use the SMTP backend.
228
+# Third-party backends can be specified by providing a Python path
229
+# to a module that defines an EmailBackend class.
230
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
231
+# Host for sending email.
232
+EMAIL_HOST = 'smtp.exmail.qq.com'
233
+# Port for sending email.
234
+EMAIL_PORT = 25
235
+# Optional SMTP authentication information for EMAIL_HOST.
236
+EMAIL_HOST_USER = 'error.notify@exmail.com'
237
+EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
238
+EMAIL_USE_TLS = False
239
+EMAIL_USE_SSL = False
240
+EMAIL_SSL_CERTFILE = None
241
+EMAIL_SSL_KEYFILE = None
242
+EMAIL_TIMEOUT = None
243
+# Default email address to use for various automated correspondence from
244
+# the site managers.
245
+DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
246
+# People who get code error notifications.
247
+# In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')]
248
+ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
249
+# Not-necessarily-technical managers of the site. They get broken link
250
+# notifications and other various emails.
251
+MANAGERS = ADMINS
252
+# Subject-line prefix for email messages send with django.core.mail.mail_admins
253
+# or ...mail_managers.  Make sure to include the trailing space.
254
+EMAIL_SUBJECT_PREFIX = u'[Kodo] '
255
+
256
+# Admin Settings
257
+DISABLE_ACTION = False
258
+
259
+# 开发调试相关配置
260
+if DEBUG:
261
+    try:
262
+        from local_settings_dev import *
263
+    except ImportError:
264
+        pass
265
+
266
+try:
267
+    from local_settings import *
268
+except ImportError:
269
+    pass
270
+
271
+try:
272
+    from oauth_settings import *
273
+except ImportError:
274
+    pass
275
+
276
+# 依赖 local_settings 中的配置
277
+# 微信授权设置
278
+# WECHAT_OAUTH2_REDIRECT_URI = '{0}/we/oauth2?scope={{0}}&redirect_url={{1}}'.format(DOMAIN)
279
+# Shorten URL
280
+# ``o`` is short for oauth2
281
+# ``r`` is short for redirect_url
282
+WECHAT_OAUTH2_REDIRECT_URI = '{0}/we/o?scope={{0}}&r={{1}}'.format(DOMAIN)
283
+WECHAT_OAUTH2_USERINFO_REDIRECT_URI = '{0}/we/o?r={{0}}'.format(DOMAIN)  # Scope default snsapi_userinfo
284
+WECHAT_BASE_REDIRECT_URI = '{0}/we/base_redirect'.format(DOMAIN)
285
+WECHAT_USERINFO_REDIRECT_URI = '{0}/we/userinfo_redirect'.format(DOMAIN)
286
+WECHAT_DIRECT_BASE_REDIRECT_URI = '{0}/we/direct_base_redirect'.format(DOMAIN)
287
+WECHAT_DIRECT_USERINFO_REDIRECT_URI = '{0}/we/direct_userinfo_redirect'.format(DOMAIN)
288
+
289
+try:
290
+    from func_settings import redis_connect
291
+    REDIS_CACHE = redis_connect(REDIS.get('default', {}))
292
+except ImportError:
293
+    REDIS_CACHE = None
294
+
295
+# LOGGER 设置
296
+LOGGING = {
297
+    'version': 1,
298
+    'disable_existing_loggers': False,
299
+    'formatters': {
300
+        'verbose': {
301
+            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
302
+        },
303
+        'simple': {
304
+            'format': '%(levelname)s %(message)s'
305
+        },
306
+    },
307
+    'handlers': {
308
+        'console': {
309
+            'level': 'DEBUG',
310
+            'class': 'logging.StreamHandler',
311
+            'formatter': 'verbose'
312
+        },
313
+    },
314
+    'loggers': {
315
+        'console': {
316
+            'handlers': ['console'],
317
+            'level': 'DEBUG',
318
+            'propagate': True,
319
+        },
320
+    },
321
+}

+ 574 - 0
kodo/static/templet/js/jswe.js

@@ -0,0 +1,574 @@
1
+!(function(e, t) {
2
+    var config = {
3
+        wxconfig: 'http://api.tt4it.com/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
+        baseFlag: false,
15
+        baseHide: false,
16
+        close: false,
17
+        hideMenuItems: [],
18
+        showMenuItems: []
19
+    }, jsApiList = [
20
+        'checkJsApi',
21
+        'onMenuShareTimeline',
22
+        'onMenuShareAppMessage',
23
+        'onMenuShareQQ',
24
+        'onMenuShareWeibo',
25
+        'onMenuShareQZone',
26
+        'hideMenuItems',
27
+        'showMenuItems',
28
+        'hideAllNonBaseMenuItem',
29
+        'showAllNonBaseMenuItem',
30
+        'translateVoice',
31
+        'startRecord',
32
+        'stopRecord',
33
+        'onRecordEnd',
34
+        'playVoice',
35
+        'pauseVoice',
36
+        'stopVoice',
37
+        'uploadVoice',
38
+        'downloadVoice',
39
+        'chooseImage',
40
+        'previewImage',
41
+        'uploadImage',
42
+        'downloadImage',
43
+        'getLocalImgData',
44
+        'getNetworkType',
45
+        'openLocation',
46
+        'getLocation',
47
+        'hideOptionMenu',
48
+        'showOptionMenu',
49
+        'closeWindow',
50
+        'scanQRCode',
51
+        'chooseWXPay',
52
+        'openEnterpriseRedPacket',
53
+        'openProductSpecificView',
54
+        'addCard',
55
+        'chooseCard',
56
+        'openCard'
57
+    ], wxApiFun
58
+
59
+    function isEmpty(obj) {
60
+        if (obj == null) return true
61
+        if (obj.length > 0) return false
62
+        if (obj.length === 0) return true
63
+        for (var key in obj) {
64
+            if (Object.prototype.hasOwnProperty.call(obj, key)) return false
65
+        }
66
+        return true
67
+    }
68
+
69
+    function isNotEmpty(obj) {
70
+        return !isEmpty(obj)
71
+    }
72
+
73
+    function isOpenOnPC() {  // 判断当前网页是否在 PC 浏览器中打开
74
+        var ua = navigator.userAgent
75
+        return /windows nt/i.test(ua) || /macintosh/i.test(ua) || /linux x86_64/i.test(ua)
76
+    }
77
+
78
+    function isOpenInWeixin() {  // 判断当前网页是否在微信内置浏览器中打开
79
+        return /micromessenger/i.test(navigator.userAgent)
80
+    }
81
+
82
+    function getWeixinVersion() {
83
+        var ua = navigator.userAgent,
84
+            mt = ua.match(/micromessenger\/([\d.]+)/i)
85
+        return (mt ? mt[1] : '')
86
+    }
87
+
88
+    // This function checks whether Wechat is the appointed version or not
89
+    // Cmp: http://jsperf.com/regexp-test-vs-indexof-ignore-upper-and-lower
90
+    function isWeixinVersion(version) {
91
+        // return new RegExp('micromessenger/' + version , 'i').test(navigator.userAgent)
92
+        return navigator.userAgent.toLowerCase().indexOf('micromessenger/' + version) != -1
93
+    }
94
+
95
+    function hideOptionMenu() {
96
+        wxConfig.hide = true
97
+        fixedWxData()
98
+    }
99
+
100
+    function showOptionMenu() {
101
+        wxConfig.hide = false
102
+        fixedWxData()
103
+    }
104
+
105
+    function hideMenuItems(items) {
106
+        wxConfig.hideMenuItems = items
107
+        fixedWxData()
108
+    }
109
+
110
+    function showMenuItems(items) {
111
+        wxConfig.showMenuItems = items
112
+        fixedWxData()
113
+    }
114
+
115
+    function hideAllNonBaseMenuItem() {
116
+        wxConfig.baseFlag = true
117
+        wxConfig.baseHide = true
118
+        fixedWxData()
119
+    }
120
+
121
+    function showAllNonBaseMenuItem() {
122
+        wxConfig.baseFlag = true
123
+        wxConfig.baseHide = false
124
+        fixedWxData()
125
+    }
126
+
127
+    function closeWindow() {
128
+        wxConfig.close = true
129
+        fixedWxData()
130
+    }
131
+
132
+    function wxReady(data) {
133
+        data = typeof data === 'object' ? data : JSON.parse(data)
134
+        wx.config({
135
+            debug: wxData.debug,
136
+            appId: data.appId,
137
+            timestamp: data.timestamp,
138
+            nonceStr: data.nonceStr,
139
+            signature: data.signature,
140
+            jsApiList: jsApiList
141
+        })
142
+
143
+        var callbacks = {
144
+            trigger: function (res) {
145
+                // alert('用户点击发送给朋友')
146
+                if (JSWE.wxTrigger) {JSWE.wxTrigger(res)}
147
+            },
148
+            success: function (res) {
149
+                // alert('已分享')
150
+                if (JSWE.wxSuccess) {JSWE.wxSuccess(res)}
151
+            },
152
+            cancel: function (res) {
153
+                // alert('已取消')
154
+                if (JSWE.wxCancel) {JSWE.wxCancel(res)}
155
+            },
156
+            fail: function (res) {
157
+                // alert(JSON.stringify(res))
158
+                if (JSWE.wxFail) {JSWE.wxFail(res)}
159
+            }
160
+        }, shareInfo = function(flag) {
161
+            var _share = {
162
+                title: flag ? wxData.title : (wxData.timeLine || wxData.desc),
163
+                link: wxData.link,
164
+                imgUrl: wxData.imgUrl,
165
+                trigger: callbacks.trigger,
166
+                success: callbacks.success,
167
+                cancel: callbacks.cancel,
168
+                fail: callbacks.fail
169
+            }
170
+            if (flag) _share.desc = wxData.desc
171
+            return _share
172
+        }, wxShareApi = function() {
173
+            // 2. 分享接口
174
+            // 2.1 监听“分享给朋友”,按钮点击、自定义分享内容及分享结果接口
175
+            wx.onMenuShareAppMessage(shareInfo(1))
176
+            // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口
177
+            wx.onMenuShareTimeline(shareInfo(0))
178
+            // 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口
179
+            wx.onMenuShareQQ(shareInfo(1))
180
+            // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口
181
+            wx.onMenuShareWeibo(shareInfo(1))
182
+            // 2.5 监听“分享到QQ空间”按钮点击、自定义分享内容及分享结果接口
183
+            wx.onMenuShareQZone(shareInfo(1))
184
+        }, wxMenuApi = function () {
185
+            // 8. 界面操作接口
186
+            // 8.1 隐藏右上角菜单
187
+            // 8.2 显示右上角菜单
188
+            if (wxConfig.hide) {wx.hideOptionMenu()} else {wx.showOptionMenu()}
189
+            // 8.3 批量隐藏菜单项
190
+            if (isNotEmpty(wxConfig.hideMenuItems)) {
191
+                wx.hideMenuItems({
192
+                    menuList: wxConfig.hideMenuItems,
193
+                    success: function (res) {
194
+                        if (JSWE.wxHideMenuItemsSuccess) {JSWE.wxHideMenuItemsSuccess(res)}
195
+                    },
196
+                    fail: function (res) {
197
+                        if (JSWE.wxHideMenuItemsFail) {JSWE.wxHideMenuItemsFail(res)}
198
+                    }
199
+                })
200
+            }
201
+            // 8.4 批量显示菜单项
202
+            if (isNotEmpty(wxConfig.showMenuItems)) {
203
+                wx.showMenuItems({
204
+                    menuList: wxConfig.showMenuItems,
205
+                    success: function (res) {
206
+                        if (JSWE.wxShowMenuItemsSuccess) {JSWE.wxShowMenuItemsSuccess(res)}
207
+                    },
208
+                    fail: function (res) {
209
+                        if (JSWE.wxShowMenuItemsFail) {JSWE.wxShowMenuItemsFail(res)}
210
+                    }
211
+                })
212
+            }
213
+            // 8.5 隐藏所有非基本菜单项
214
+            // 8.6 显示所有被隐藏的非基本菜单项
215
+            if (wxConfig.baseFlag) {
216
+                if (wxConfig.baseHide) {wx.hideAllNonBaseMenuItem()} else {wx.showAllNonBaseMenuItem()}
217
+            }
218
+            // 8.7 关闭当前窗口
219
+            if (wxConfig.close) {wx.closeWindow()}
220
+        }, wxVoiceApi = function() {
221
+            // 4.3 监听录音自动停止
222
+            wx.onVoiceRecordEnd({
223
+                complete: function (res) {
224
+                    voice.localId = res.localId
225
+                    if (JSWE.wxVoiceRecordEnd) {JSWE.wxVoiceRecordEnd(res)}
226
+                }
227
+            })
228
+            // 4.7 监听录音播放停止
229
+            wx.onVoicePlayEnd({
230
+                complete: function (res) {
231
+                    if (JSWE.wxVoicePlayEnd) {JSWE.wxVoicePlayEnd(res)}
232
+                }
233
+            })
234
+        }, wxApi = function () {
235
+            wxShareApi()
236
+            wxMenuApi()
237
+            wxVoiceApi()
238
+        }
239
+
240
+        wx.ready(wxApi)
241
+
242
+        return wxApiFun = wxApi
243
+    }
244
+
245
+    if (isOpenInWeixin() || isOpenOnPC()) {
246
+        if ('undefined' !== typeof JSWE_CONF_UPDATE) JSWE_CONF_UPDATE(config)
247
+        $.ajax({
248
+            url: config.wxconfig,
249
+            type: 'get',
250
+            dataType: 'jsonp',
251
+            jsonpCallback: config.callback,
252
+            data: {
253
+                url: window.location.href.split('#')[0]
254
+            },
255
+            success: wxReady
256
+        })
257
+    }
258
+
259
+    function initWxData(data, flag) {
260
+        for(var d in data) {if (d in wxData) wxData[d] = data[d]}
261
+        if (flag) fixedWxData()
262
+    }
263
+
264
+    function changeWxData(key, value, flag) {
265
+        if (key in falDwxDataata) {wxData[key] = value}
266
+        if (flag) fixedWxData()
267
+    }
268
+
269
+    function fixedWxData() {
270
+        if ('undefined' !== typeof wxApiFun) wxApiFun()
271
+    }
272
+
273
+    // 3 智能接口
274
+    var voice = {
275
+        localId: '',
276
+        serverId: ''
277
+    }
278
+    // 3.1 识别音频并返回识别结果
279
+    function translateVoice() {
280
+        if (voice.localId == '') {
281
+            if (JSWE.wxTranslateVoiceEmpty) {JSWE.wxTranslateVoiceEmpty()}
282
+            return
283
+        }
284
+        wx.translateVoice({
285
+            localId: voice.localId,
286
+            complete: function (res) {
287
+                if (JSWE.wxTranslateVoiceComplete) {JSWE.wxTranslateVoiceComplete(res)}
288
+            }
289
+        })
290
+    }
291
+
292
+    // 4 音频接口
293
+    // 4.1 开始录音
294
+    function startRecord() {
295
+        wx.startRecord({
296
+            cancel: function () {
297
+                if (JSWE.wxStartRecordCancel) {JSWE.wxStartRecordCancel(res)}
298
+            }
299
+        })
300
+    }
301
+
302
+    // 4.2 停止录音
303
+    function stopRecord() {
304
+        wx.stopRecord({
305
+          success: function (res) {
306
+              voice.localId = res.localId
307
+              if (JSWE.wxStopRecordSuccess) {JSWE.wxStopRecordSuccess(res)}
308
+          },
309
+          fail: function (res) {
310
+              if (JSWE.wxStopRecordFail) {JSWE.wxStopRecordFail(res)}
311
+          }
312
+        })
313
+    }
314
+
315
+    // 4.4 播放音频
316
+    function playVoice() {
317
+        if (voice.localId == '') {
318
+            if (JSWE.wxPlayVoiceEmpty) {JSWE.wxPlayVoiceEmpty()}
319
+            return
320
+        }
321
+        wx.playVoice({
322
+            localId: voice.localId
323
+        })
324
+    }
325
+
326
+    // 4.5 暂停播放音频
327
+    function pauseVoice() {
328
+        if (voice.localId == '') {
329
+            if (JSWE.wxPauseVoiceEmpty) {JSWE.wxPauseVoiceEmpty()}
330
+            return
331
+        }
332
+        wx.pauseVoice({
333
+            localId: voice.localId
334
+        })
335
+    }
336
+
337
+    // 4.6 停止播放音频
338
+    function stopVoice() {
339
+        if (voice.localId == '') {
340
+            if (JSWE.wxStopVoiceEmpty) {JSWE.wxStopVoiceEmpty()}
341
+            return
342
+        }
343
+        wx.stopVoice({
344
+            localId: voice.localId
345
+        })
346
+    }
347
+
348
+    // 4.8 上传语音
349
+    function uploadVoice() {
350
+        var localId = voice.localId
351
+        if (localId == '') {
352
+            if (JSWE.wxUploadVoiceEmpty) {JSWE.wxUploadVoiceEmpty()}
353
+            return
354
+        }
355
+        wx.uploadVoice({
356
+            localId: localId,
357
+            success: function (res) {
358
+                voice.serverId = res.serverId
359
+                if (JSWE.wxUploadVoiceSuccess) {JSWE.wxUploadVoiceSuccess(res, localId)}
360
+            }
361
+        })
362
+    }
363
+
364
+    // 4.9 下载语音
365
+    function downloadVoice() {
366
+        var serverId = voice.serverId
367
+        if (serverId == '') {
368
+            if (JSWE.wxDownloadVoiceEmpty) {JSWE.wxDownloadVoiceEmpty()}
369
+            return
370
+        }
371
+        wx.downloadVoice({
372
+            serverId: serverId,
373
+            success: function (res) {
374
+                voice.localId = res.localId
375
+                if (JSWE.wxDownloadVoiceSuccess) {JSWE.wxDownloadVoiceSuccess(res, serverId)}
376
+            }
377
+        })
378
+    }
379
+
380
+    // 5 图片接口
381
+    var images = {
382
+        localIds: [],
383
+        serverIds: []
384
+    }
385
+    // 5.1 拍照、本地选图
386
+    function chooseImage(choose_params) {
387
+        if ('undefined' === typeof choose_params) choose_params = {}
388
+        wx.chooseImage({
389
+            count: choose_params.count || 9, // 默认9
390
+            sizeType: choose_params.sizeType || ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
391
+            sourceType: choose_params.sourceType || ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
392
+            success: function (res) {
393
+                images.localIds = res.localIds // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
394
+                // 判断是否直接上传
395
+                if (choose_params.directUpload) {setTimeout(uploadImages({localIds: images.localIds, isShowProgressTips: choose_params.isShowProgressTips || 1}), 100)}
396
+                // 拍照、本地选图成功后的回调函数
397
+                if (JSWE.wxChooseImageSuccess) {JSWE.wxChooseImageSuccess(res, choose_params.extras || {})}
398
+            }
399
+        })
400
+    }
401
+
402
+    // 5.2 图片预览
403
+    function previewImage(preview_params) {
404
+        wx.previewImage({
405
+            current: preview_params.current, // 当前显示图片的链接,不填则默认为 urls 的第一张
406
+            urls: preview_params.urls // 需要预览的图片链接列表
407
+        })
408
+    }
409
+
410
+    // 5.3 上传图片
411
+    function uploadImage(upload_params) {
412
+        // 上传图片为异步处理,重复上传同一图片,返回的serverId也是不同的
413
+        var localId = upload_params.localId
414
+        wx.uploadImage({
415
+            localId: localId, // 需要上传的图片的本地ID,由chooseImage接口获得
416
+            isShowProgressTips: upload_params.isShowProgressTips || 1, // 默认为1,显示进度提示
417
+            success: function (res) {
418
+                images.serverIds.push(res.serverId) // 返回图片的服务器端ID
419
+                // 上传图片成功后的回调函数
420
+                if (JSWE.wxUploadImageSuccess) {JSWE.wxUploadImageSuccess(res, localId)}
421
+            }
422
+        })
423
+    }
424
+
425
+    function uploadImages(upload_params) {
426
+        var localIds = upload_params.localIds, isShowProgressTips = upload_params.isShowProgressTips || 1
427
+        images.serverIds = []
428
+        for (var idx in localIds) {uploadImage({localId: localIds[idx], isShowProgressTips: isShowProgressTips})}
429
+    }
430
+
431
+    // 5.4 下载图片
432
+    function downloadImage(download_params) {
433
+        var serverId = download_params.serverId
434
+        wx.downloadImage({
435
+            serverId: serverId, // 需要下载的图片的服务器端ID,由uploadImage接口获得
436
+            isShowProgressTips: download_params.isShowProgressTips || 1, // 默认为1,显示进度提示
437
+            success: function (res) {
438
+                images.localId.push(res.localId)
439
+                if (JSWE.wxDownloadImageSuccess) {JSWE.wxDownloadImageSuccess(res, serverId)}
440
+            }
441
+        })
442
+    }
443
+
444
+    function downloadImages(download_params) {
445
+        var serverIds = download_params.serverIds, isShowProgressTips = download_params.isShowProgressTips || 1
446
+        images.localIds = []
447
+        for (var idx in serverIds) {downloadImage({serverId: serverIds[idx], isShowProgressTips: isShowProgressTips})}
448
+    }
449
+
450
+    function getLocalImgData(localId) {
451
+        wx.getLocalImgData({
452
+            localId: localId, // 图片的localID
453
+            success: function (res) {
454
+                // var localData = res.localData; // localData是图片的base64数据,可以用img标签显示
455
+                if (JSWE.wxGetLocalImgDataSuccess) {JSWE.wxGetLocalImgDataSuccess(res)}
456
+            }
457
+        })
458
+    }
459
+
460
+    // 9 微信原生接口
461
+    // 9.1.1 扫描二维码并返回结果
462
+    // 9.1.2 扫描二维码并返回结果
463
+    function scanQRCode(scan_params) {
464
+        if ('undefined' === typeof scan_params) scan_params = {}
465
+        wx.scanQRCode({
466
+            needResult: scan_params.needResult || 0,  // 默认为0,0扫描结果由微信处理,1直接返回扫描结果
467
+            scanType: scan_params.scanType || ['qrCode', 'barCode'],  // 可以指定扫二维码还是一维码,默认二者都有
468
+            success: function (res) {  // 当 needResult 为 1 时,扫码返回的结果
469
+                if (JSWE.wxScanQRCodeSuccess) {JSWE.wxScanQRCodeSuccess(res)}
470
+            }
471
+        })
472
+    }
473
+
474
+    // QRCode & BarCode is different
475
+    function parseScanQRCodeResultStr(resultStr) {
476
+        var strs = resultStr.split(',')
477
+        return strs[strs.length - 1]
478
+    }
479
+
480
+    // 10 微信支付接口
481
+    // 10.1 发起一个支付请求
482
+    function chooseWXPay(wxpay_params) {
483
+        wx.chooseWXPay({
484
+            timestamp: wxpay_params.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
485
+            nonceStr: wxpay_params.nonceStr, // 支付签名随机串,不长于 32 位
486
+            package: wxpay_params.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
487
+            signType: wxpay_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
488
+            paySign: wxpay_params.paySign, // 支付签名
489
+            success: function (res) {
490
+                // 支付成功后的回调函数
491
+                if (JSWE.wxPaySuccess) {JSWE.wxPaySuccess(res)}
492
+            }
493
+        })
494
+    }
495
+
496
+    // xx 微信原生企业红包接口
497
+    // xx.1 发起一个发送原生企业红包请求
498
+    function openEnterpriseRedPacket(wxredpack_params) {
499
+        wx.openEnterpriseRedPacket({
500
+            timeStamp: wxredpack_params.timeStamp, // 红包签名时间戳,注意原生企业红包接口timeStamp字段名需大写其中的S字符,而支付接口timeStamp字段名无需大写其中的S字符。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
501
+            nonceStr: wxredpack_params.nonceStr, // 红包签名随机串,不长于 32 位
502
+            package: encodeURIComponent(wxredpack_params.package), // 发放红包接口返回的prepay_id参数值,提交格式如:prepay_id=***)
503
+            signType: wxredpack_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
504
+            paySign: wxredpack_params.paySign, // 红包签名
505
+            success: function (res) {
506
+                // 发送原生企业红包成功后的回调函数
507
+                if (JSWE.wxEnterpriseRedPacketSuccess) {JSWE.wxEnterpriseRedPacketSuccess(res)}
508
+            }
509
+        })
510
+    }
511
+
512
+    var v = {
513
+        version: '1.0.5',
514
+
515
+        // Basic Vars
516
+        config: config,
517
+        wxData: wxData,
518
+        jsApiList: jsApiList,
519
+
520
+        isEmpty: isEmpty,
521
+        isNotEmpty: isNotEmpty,
522
+
523
+        // Weixin Function
524
+        isOpenInWeixin: isOpenInWeixin,
525
+        getWeixinVersion: getWeixinVersion,
526
+        isWeixinVersion: isWeixinVersion,
527
+
528
+        // Menu Function
529
+        hideOptionMenu: hideOptionMenu,
530
+        showOptionMenu: showOptionMenu,
531
+        hideMenuItems: hideMenuItems,
532
+        showMenuItems: showMenuItems,
533
+        hideAllNonBaseMenuItem: hideAllNonBaseMenuItem,
534
+        showAllNonBaseMenuItem: showAllNonBaseMenuItem,
535
+        closeWindow: closeWindow,
536
+
537
+        // Share Function
538
+        initWxData: initWxData,
539
+        changeWxData: changeWxData,
540
+        fixedWxData: fixedWxData,
541
+
542
+        // Voice Function
543
+        voice: voice,
544
+        translateVoice: translateVoice,
545
+        startRecord: startRecord,
546
+        stopRecord: stopRecord,
547
+        playVoice: playVoice,
548
+        pauseVoice: pauseVoice,
549
+        stopVoice: stopVoice,
550
+        uploadVoice: uploadVoice,
551
+        downloadVoice: downloadVoice,
552
+
553
+        // Image Function
554
+        images: images,
555
+        chooseImage: chooseImage,
556
+        previewImage: previewImage,
557
+        uploadImage: uploadImage,
558
+        uploadImages: uploadImages,
559
+        downloadImage: downloadImage,
560
+        downloadImages: downloadImages,
561
+        getLocalImgData: getLocalImgData,
562
+
563
+        // Scan Function
564
+        scanQRCode: scanQRCode,
565
+        parseScanQRCodeResultStr: parseScanQRCodeResultStr,
566
+
567
+        // Pay Function
568
+        chooseWXPay: chooseWXPay,
569
+
570
+        // EnterpriseRedPacket Function
571
+        openEnterpriseRedPacket: openEnterpriseRedPacket
572
+    }
573
+    e.JSWE = e.V = v
574
+})(window)

+ 5 - 0
kodo/templates/admin/view.html

@@ -0,0 +1,5 @@
1
+{% extends "admin/change_form.html" %}
2
+{% load i18n %}
3
+
4
+{% block submit_buttons_bottom %}
5
+{% endblock %}

+ 36 - 0
kodo/urls.py

@@ -0,0 +1,36 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+"""kodo 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'^s/', include('django_short_url.urls', namespace='django_short_url')),
28
+    url(r'^uniapi/', include('django_uniapi.urls', namespace='uniapi')),
29
+    url(r'^we/', include('django_we.urls', namespace='wechat')),
30
+]
31
+
32
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
33
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
34
+
35
+# AdminSite
36
+admin.site.site_header = 'My administration'

+ 17 - 0
kodo/wsgi.py

@@ -0,0 +1,17 @@
1
+"""
2
+WSGI config for kodo 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", "kodo.settings")
16
+
17
+application = get_wsgi_application()

+ 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", "kodo.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
mch/__init__.py


+ 22 - 0
mch/admin.py

@@ -0,0 +1,22 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.contrib import admin
4
+
5
+from mch.models import BrandInfo, DistributorInfo, ModelInfo
6
+
7
+
8
+class BrandInfoAdmin(admin.ModelAdmin):
9
+    list_display = ('brand_id', 'brand_name', 'brand_descr', 'position', 'status', 'created_at', 'updated_at')
10
+
11
+
12
+class ModelInfoAdmin(admin.ModelAdmin):
13
+    list_display = ('model_id', 'model_name', 'model_descr', 'position', 'status', 'created_at', 'updated_at')
14
+
15
+
16
+class DistributorInfoAdmin(admin.ModelAdmin):
17
+    list_display = ('distributor_id', 'distributor_name', 'distributor_descr', 'position', 'status', 'created_at', 'updated_at')
18
+
19
+
20
+admin.site.register(BrandInfo, BrandInfoAdmin)
21
+admin.site.register(ModelInfo, ModelInfoAdmin)
22
+admin.site.register(DistributorInfo, DistributorInfoAdmin)

+ 8 - 0
mch/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 MchConfig(AppConfig):
8
+    name = 'mch'

+ 68 - 0
mch/migrations/0001_initial.py

@@ -0,0 +1,68 @@
1
+# -*- coding: utf-8 -*-
2
+# Generated by Django 1.11.3 on 2017-12-30 13:37
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='BrandInfo',
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='Status', verbose_name='status')),
22
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
23
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
24
+                ('brand_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='\u54c1\u724c\u552f\u4e00\u6807\u8bc6', max_length=22, unique=True)),
25
+                ('brand_name', models.CharField(blank=True, help_text='\u54c1\u724c\u540d\u79f0', max_length=255, null=True, verbose_name='brand_name')),
26
+                ('brand_descr', models.TextField(blank=True, help_text='\u54c1\u724c\u63cf\u8ff0', max_length=255, null=True, verbose_name='brand_descr')),
27
+                ('position', models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position')),
28
+            ],
29
+            options={
30
+                'verbose_name': '\u54c1\u724c\u4fe1\u606f',
31
+                'verbose_name_plural': '\u54c1\u724c\u4fe1\u606f',
32
+            },
33
+        ),
34
+        migrations.CreateModel(
35
+            name='DistributorInfo',
36
+            fields=[
37
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
38
+                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
39
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
40
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
41
+                ('distributor_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='\u7ecf\u9500\u5546\u552f\u4e00\u6807\u8bc6', max_length=22, unique=True)),
42
+                ('distributor_name', models.CharField(blank=True, help_text='\u7ecf\u9500\u5546\u540d\u79f0', max_length=255, null=True, verbose_name='distributor_name')),
43
+                ('distributor_descr', models.TextField(blank=True, help_text='\u7ecf\u9500\u5546\u63cf\u8ff0', max_length=255, null=True, verbose_name='distributor_descr')),
44
+                ('position', models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position')),
45
+            ],
46
+            options={
47
+                'verbose_name': '\u7ecf\u9500\u5546\u4fe1\u606f',
48
+                'verbose_name_plural': '\u7ecf\u9500\u5546\u4fe1\u606f',
49
+            },
50
+        ),
51
+        migrations.CreateModel(
52
+            name='ModelInfo',
53
+            fields=[
54
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
55
+                ('status', models.BooleanField(db_index=True, default=True, help_text='Status', verbose_name='status')),
56
+                ('created_at', models.DateTimeField(auto_now_add=True, help_text='Create Time', verbose_name='created_at')),
57
+                ('updated_at', models.DateTimeField(auto_now=True, help_text='Update Time', verbose_name='updated_at')),
58
+                ('model_id', shortuuidfield.fields.ShortUUIDField(blank=True, db_index=True, editable=False, help_text='\u578b\u53f7\u552f\u4e00\u6807\u8bc6', max_length=22, unique=True)),
59
+                ('model_name', models.CharField(blank=True, help_text='\u578b\u53f7\u540d\u79f0', max_length=255, null=True, verbose_name='model_name')),
60
+                ('model_descr', models.TextField(blank=True, help_text='\u578b\u53f7\u63cf\u8ff0', max_length=255, null=True, verbose_name='model_descr')),
61
+                ('position', models.IntegerField(default=1, help_text='\u6392\u5e8f', verbose_name='position')),
62
+            ],
63
+            options={
64
+                'verbose_name': '\u578b\u53f7\u4fe1\u606f',
65
+                'verbose_name_plural': '\u578b\u53f7\u4fe1\u606f',
66
+            },
67
+        ),
68
+    ]

+ 0 - 0
mch/migrations/__init__.py


+ 75 - 0
mch/models.py

@@ -0,0 +1,75 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.db import models
4
+from django.utils.translation import ugettext_lazy as _
5
+from models_ext import BaseModelMixin
6
+from shortuuidfield import ShortUUIDField
7
+
8
+
9
+class BrandInfo(BaseModelMixin):
10
+    brand_id = ShortUUIDField(_(u'brand_id'), max_length=32, help_text=u'品牌唯一标识', db_index=True, unique=True)
11
+    brand_name = models.CharField(_(u'brand_name'), max_length=255, blank=True, null=True, help_text=u'品牌名称')
12
+    brand_descr = models.TextField(_(u'brand_descr'), max_length=255, blank=True, null=True, help_text=u'品牌描述')
13
+
14
+    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
15
+
16
+    class Meta:
17
+        verbose_name = _(u'品牌信息')
18
+        verbose_name_plural = _(u'品牌信息')
19
+
20
+    def __unicode__(self):
21
+        return unicode(self.pk)
22
+
23
+    @property
24
+    def data(self):
25
+        return {
26
+            'brand_id': self.brand_id,
27
+            'brand_name': self.brand_name,
28
+            'brand_descr': self.brand_descr,
29
+        }
30
+
31
+
32
+class ModelInfo(BaseModelMixin):
33
+    model_id = ShortUUIDField(_(u'model_id'), max_length=32, help_text=u'型号唯一标识', db_index=True, unique=True)
34
+    model_name = models.CharField(_(u'model_name'), max_length=255, blank=True, null=True, help_text=u'型号名称')
35
+    model_descr = models.TextField(_(u'model_descr'), max_length=255, blank=True, null=True, help_text=u'型号描述')
36
+
37
+    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
38
+
39
+    class Meta:
40
+        verbose_name = _(u'型号信息')
41
+        verbose_name_plural = _(u'型号信息')
42
+
43
+    def __unicode__(self):
44
+        return unicode(self.pk)
45
+
46
+    @property
47
+    def data(self):
48
+        return {
49
+            'model_id': self.model_id,
50
+            'model_name': self.model_name,
51
+            'model_descr': self.model_descr,
52
+        }
53
+
54
+
55
+class DistributorInfo(BaseModelMixin):
56
+    distributor_id = ShortUUIDField(_(u'distributor_id'), max_length=32, help_text=u'经销商唯一标识', db_index=True, unique=True)
57
+    distributor_name = models.CharField(_(u'distributor_name'), max_length=255, blank=True, null=True, help_text=u'经销商名称')
58
+    distributor_descr = models.TextField(_(u'distributor_descr'), max_length=255, blank=True, null=True, help_text=u'经销商描述')
59
+
60
+    position = models.IntegerField(_(u'position'), default=1, help_text=u'排序')
61
+
62
+    class Meta:
63
+        verbose_name = _(u'经销商信息')
64
+        verbose_name_plural = _(u'经销商信息')
65
+
66
+    def __unicode__(self):
67
+        return unicode(self.pk)
68
+
69
+    @property
70
+    def data(self):
71
+        return {
72
+            'distributor_id': self.distributor_id,
73
+            'distributor_name': self.distributor_name,
74
+            'distributor_descr': self.distributor_descr,
75
+        }

+ 7 - 0
mch/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
mch/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
+pycodestyle --exclude=build,migrations,.tox --ignore=E128,E501 .

+ 18 - 0
requirements.txt

@@ -0,0 +1,18 @@
1
+Django==1.11.3
2
+StatusCode==1.0.0
3
+django-admin==1.1.0
4
+django-detect==1.0.5
5
+django-json-render==1.0.0
6
+django-json-response==1.1.5
7
+django-models-ext==1.0.5
8
+django-short-url==1.0.2
9
+django-uniapi==1.0.0
10
+django-we==1.1.2
11
+furl==1.0.1
12
+hiredis==0.2.0
13
+mysqlclient==1.3.12
14
+pywe-oauth==1.0.5
15
+pywe-pay==1.0.11
16
+redis==2.10.6
17
+redis-extensions==1.1.6
18
+rsa==3.4.2

+ 0 - 0
utils/__init__.py


+ 0 - 0
utils/algorithm/__init__.py


+ 13 - 0
utils/algorithm/b64.py

@@ -0,0 +1,13 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import base64
4
+
5
+from CodeConvert import CodeConvert as cc
6
+
7
+
8
+def b64_encrypt(plaintext):
9
+    return base64.urlsafe_b64encode(cc.Convert2Utf8(plaintext))
10
+
11
+
12
+def b64_decrypt(ciphertext):
13
+    return cc.Convert2Unicode(base64.urlsafe_b64decode(ciphertext))

+ 18 - 0
utils/algorithm/rsalg.py

@@ -0,0 +1,18 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import base64
4
+
5
+import rsa
6
+from CodeConvert import CodeConvert as cc
7
+
8
+
9
+pubkey = rsa.PublicKey(7733936986002684982484845608354489436048239676995253266549456282870195715569430535348099548536388503919509506510435040149560886821029985877148893951171111, 65537)
10
+privkey = rsa.PrivateKey(7733936986002684982484845608354489436048239676995253266549456282870195715569430535348099548536388503919509506510435040149560886821029985877148893951171111, 65537, 316971401565576878144472516350155768882090601834219605321449556369521730928872332388800749109622843453327077688969025635980606763507018292148749534091473, 4517492317789178911663214752269837474466539823144998211438927363654134055916886851, 1711997816918835594017245862832442114582648667392542139046338517030653261)
11
+
12
+
13
+def rsa_encrypt(plaintext):
14
+    return base64.urlsafe_b64encode(rsa.encrypt(cc.Convert2Utf8(plaintext), pubkey))
15
+
16
+
17
+def rsa_decrypt(ciphertext):
18
+    return rsa.decrypt(base64.urlsafe_b64decode(cc.Convert2Utf8(ciphertext)), privkey)

+ 0 - 0
utils/error/__init__.py


+ 72 - 0
utils/error/errno_utils.py

@@ -0,0 +1,72 @@
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
+
11
+class PhoneStatusCode(BaseStatusCode):
12
+    """ 4002xx 手机相关错误码 """
13
+    INVALID_PHONE = StatusCodeField(400200, 'Invalid Phone', description=u'非法手机号')
14
+    PHONE_NOT_FOUND = StatusCodeField(400201, 'Phone Not Found', description=u'手机号不存在')
15
+    PHONE_ALREADY_EXISTS = StatusCodeField(400202, 'Phone Already Exists', description=u'手机号已存在')
16
+
17
+
18
+class OrderStatusCode(BaseStatusCode):
19
+    """ 4040xx 订单/支付相关错误码 """
20
+    UNIFIED_ORDER_FAIL = StatusCodeField(404000, 'Unified Order Fail', description=u'统一下单失败')
21
+    ORDER_NOT_FOUND = StatusCodeField(404001, 'Order Not Found', description=u'订单不存在')
22
+    # 订单支付状态
23
+    ORDER_NOT_PAY = StatusCodeField(404011, 'Order Not Pay', description=u'订单未支付')
24
+    ORDER_PAYING = StatusCodeField(404012, 'Order Paying', description=u'订单支付中')
25
+    ORDER_PAY_FAIL = StatusCodeField(404013, 'Order Pay Fail', description=u'微信支付失败')
26
+    # 通知校验状态
27
+    SIGN_CHECK_FAIL = StatusCodeField(404090, 'Sign Check Fail', description=u'签名校验失败')
28
+    FEE_CHECK_FAIL = StatusCodeField(404091, 'FEE Check Fail', description=u'金额校验失败')
29
+
30
+
31
+class PayStatusCode(BaseStatusCode):
32
+    """ 4041xx 支付相关错误码 """
33
+
34
+
35
+class WithdrawStatusCode(BaseStatusCode):
36
+    """ 4042xx 提现相关错误码 """
37
+    BALANCE_INSUFFICIENT = StatusCodeField(404200, 'Balance Insufficient', description=u'提现金额不足')
38
+
39
+
40
+class TokenStatusCode(BaseStatusCode):
41
+    """ 4090xx 票据相关错误码 """
42
+    TOKEN_NOT_FOUND = StatusCodeField(409001, 'Token Not Found', description=u'票据不存在')
43
+
44
+
45
+class SignatureStatusCode(BaseStatusCode):
46
+    """ 4091xx 签名校验错误 """
47
+    SIGNATURE_ERROR = StatusCodeField(409101, 'Signature Error', description=u'签名错误')
48
+
49
+
50
+class GVCodeStatusCode(BaseStatusCode):
51
+    """ 4095xx 图形验证码相关错误码 """
52
+    GRAPHIC_VCODE_ERROR = StatusCodeField(409101, 'Graphic VCode Error', description=u'图形验证码错误')
53
+
54
+
55
+class SVCodeStatusCode(BaseStatusCode):
56
+    """ 4092xx 短信验证码相关错误码 """
57
+    SMS_QUOTA_LIMIT = StatusCodeField(409200, 'SMS Quota Limit', description=u'短信次数超限')
58
+    SMS_VCODE_ERROR = StatusCodeField(409201, 'SMS VCode Error', description=u'验证码错误,请稍后重试')
59
+    SMS_VCODE_HAS_SEND = StatusCodeField(409202, 'SMS VCode Has Send', description=u'验证码已发送,请勿重复获取')
60
+
61
+
62
+class InsufficientStatusCode(BaseStatusCode):
63
+    """ 4095xx 不足相关错误码 """
64
+    BALANCE_INSUFFICIENT = StatusCodeField(409501, 'Balance Insufficient', description=u'余额不足')
65
+    INTEGRAL_INSUFFICIENT = StatusCodeField(409502, 'Integral Insufficient', description=u'积分不足')
66
+
67
+
68
+class PermissionStatusCode(BaseStatusCode):
69
+    """ 4099xx 权限相关错误码 """
70
+    PERMISSION_DENIED = StatusCodeField(409900, 'Permission Denied', description=u'权限不足')
71
+    UPLOAD_PERMISSION_DENIED = StatusCodeField(409910, 'Upload Permission Denied', description=u'上传权限不足')
72
+    UPDATE_PERMISSION_DENIED = StatusCodeField(409930, 'Update Permission Denied', 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 -*-

+ 0 - 0
utils/user/__init__.py


+ 18 - 0
utils/user/userinfo_save.py

@@ -0,0 +1,18 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def userinfo_save(userinfo):
5
+    """ Save profile or something else """
6
+    # from account.models import UserInfo
7
+    # from django.conf import settings
8
+    #
9
+    # unique_identifier = userinfo.get(settings.WECHAT_UNIQUE_IDENTIFICATION, '')
10
+    #
11
+    # user, created = UserInfo.objects.select_for_update().get_or_create(**{settings.WECHAT_UNIQUE_IDENTIFICATION: unique_identifier})
12
+    # user.unionid = userinfo.get('unionid', '')
13
+    # user.openid = userinfo.get('openid', '')
14
+    # user.nickname = userinfo.get('nickname', '')
15
+    # user.avatar = userinfo.get('headimgurl', '')
16
+    # user.save()
17
+    #
18
+    # return user

kodo - Gogs: Go Git Service

Brak opisu

views.py 14KB

    # -*- coding: utf-8 -*- from django.conf import settings from django.db import transaction from django.db.models import Sum from django_logit import logit from django_models_ext.provincemodels import ProvinceShortModelMixin from django_query import get_query_value from django_response import response from TimeConvert import TimeConvert as tc from mch.models import BrandInfo, DistributorInfo, ModelInfo, SaleclerkInfo from statistic.models import (ConsumeDistributorSaleStatisticInfo, ConsumeModelSaleStatisticInfo, ConsumeProvinceSaleStatisticInfo, ConsumeSaleStatisticInfo, ConsumeUserStatisticInfo, DistributorSaleStatisticInfo, ModelSaleStatisticInfo, ProvinceSaleStatisticInfo, RegisterStatisticInfo, SaleclerkSaleStatisticInfo, SaleStatisticInfo) from utils.rdm_utils import randnum @logit def tj_distributor(request): brand_id = request.POST.get('brand_id') or settings.KODO_DEFAULT_BRAND_ID ymd = int(tc.local_string(format='%Y%m%d')) # 注册用户统计 & 今日注册用户 try: register_num = RegisterStatisticInfo.objects.get(brand_id=brand_id, ymd=ymd).num except RegisterStatisticInfo.DoesNotExist: register_num = 0 # # 注册用户数趋势 # register_trends = RegisterStatisticInfo.objects.filter(status=True).order_by('-ymd') # register_trends = [r.data for r in register_trends] # 销量统计 & 今日销量 try: sale_num = SaleStatisticInfo.objects.get(brand_id=brand_id, ymd=ymd).num except SaleStatisticInfo.DoesNotExist: sale_num = 0 # # 商品销量趋势 # sale_trends = SaleStatisticInfo.objects.filter(status=True).order_by('-ymd') # sale_trends = [s.data for s in sale_trends] # 型号销量统计 & 热销商品榜 model_sales = ModelSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('-num') model_sales = [m.data for m in model_sales] # 经销商销量统计 & 经销商榜 distributor_sales = DistributorSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('-num') distributor_sales = [d.data for d in distributor_sales] # 各地区实时销量 province_sales = ProvinceSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('position') province_sales = [p.data for p in province_sales] # TOADD: ROI rois = ModelSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True) rois = [m.roi for m in rois] return response(200, 'Get TJ Data Success', u'获取统计数据成功', { 'register_num': randnum() if settings.DEBUG_STATISTIC_DATA_FLAG else register_num, # 注册用户统计 & 今日注册用户 # 'register_trends': register_trends, # 注册用户数趋势 'sale_num': randnum() if settings.DEBUG_STATISTIC_DATA_FLAG else sale_num, # 销量统计 & 今日销量 # 'sale_trends': sale_trends, # 商品销量趋势 'model_sales': model_sales, # 型号销量统计 & 热销商品榜 'distributor_sales': distributor_sales, # 经销商销量统计 & 经销商榜 'province_sales': province_sales, # 各地区实时销量 'rois': rois, # ROI }) @logit def tj_consumer(request): brand_id = request.POST.get('brand_id') or settings.KODO_DEFAULT_BRAND_ID ymd = int(tc.local_string(format='%Y%m%d')) # 注册用户统计 & 今日注册用户 try: register_num = RegisterStatisticInfo.objects.get(brand_id=brand_id, ymd=ymd).num except RegisterStatisticInfo.DoesNotExist: register_num = 0 # 注册用户数趋势 register_trends = RegisterStatisticInfo.objects.filter(brand_id=brand_id, status=True).order_by('-ymd')[:30] register_trends = [r.data for r in register_trends][::-1] # 销量统计 & 今日销量 try: sale_num = ConsumeSaleStatisticInfo.objects.get(brand_id=brand_id, ymd=ymd).num except ConsumeSaleStatisticInfo.DoesNotExist: sale_num = 0 # 商品销量趋势 sale_trends = ConsumeSaleStatisticInfo.objects.filter(brand_id=brand_id, status=True).order_by('-ymd')[:30] sale_trends = [s.data for s in sale_trends][::-1] # 型号销量统计 & 热销商品榜 model_sales = ConsumeModelSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('-num') model_sales = [m.data for m in model_sales] # 经销商销量统计 & 经销商榜 distributor_sales = ConsumeDistributorSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('-num') distributor_sales = [d.data for d in distributor_sales] # 各地区实时销量 province_sales = ConsumeProvinceSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('position') province_sales = [p.data for p in province_sales] return response(200, 'Get TJ Data Success', u'获取统计数据成功', { 'register_num': randnum() if settings.DEBUG_STATISTIC_DATA_FLAG else register_num, # 注册用户统计 & 今日注册用户 'register_trends': register_trends, # 注册用户数趋势 'sale_num': randnum() if settings.DEBUG_STATISTIC_DATA_FLAG else sale_num, # 销量统计 & 今日销量 'sale_trends': sale_trends, # 商品销量趋势 'model_sales': model_sales, # 型号销量统计 & 热销商品榜 'distributor_sales': distributor_sales, # 经销商销量统计 & 经销商榜 'province_sales': province_sales, # 各地区实时销量 }) @logit @transaction.atomic def tj_generate(request): # 1 0 * * * curl http://kodo.xfoto.com.cn/api/tj/generate __tj_generate(ymd=None) return response() @transaction.atomic def __tj_generate(ymd=None): ymd = ymd or int(tc.local_string(format='%Y%m%d')) brands = BrandInfo.objects.filter(status=True) for brand in brands: for pcode, pname in ProvinceShortModelMixin.PROVINCE_CODE_NAME_DICT.items(): pssi, created = ProvinceSaleStatisticInfo.objects.get_or_create( brand_id=brand.brand_id, province_code=pcode, ymd=ymd, ) pssi.province_name = pname pssi.save() cpssi, created = ConsumeProvinceSaleStatisticInfo.objects.get_or_create( brand_id=brand.brand_id, province_code=pcode, ymd=ymd, ) cpssi.province_name = pname cpssi.save() models = ModelInfo.objects.filter(brand_id=brand.brand_id, status=True) for mdl in models: mssi, created = ModelSaleStatisticInfo.objects.get_or_create( brand_id=brand.brand_id, model_id=mdl.model_id, ymd=ymd, ) mssi.model_name = mdl.model_name mssi.save() cmssi, created = ConsumeModelSaleStatisticInfo.objects.get_or_create( brand_id=brand.brand_id, model_name=mdl.model_uni_name, ymd=ymd, ) cmssi.save() distributors = DistributorInfo.objects.filter(brand_id=brand.brand_id, status=True) for dtbt in distributors: dssi, created = DistributorSaleStatisticInfo.objects.get_or_create( brand_id=brand.brand_id, distributor_id=dtbt.distributor_id, ymd=ymd, ) dssi.distributor_name = dtbt.distributor_name dssi.save() cdssi, created = ConsumeDistributorSaleStatisticInfo.objects.get_or_create( brand_id=brand.brand_id, distributor_id=dtbt.distributor_id, ymd=ymd, ) cdssi.distributor_name = dtbt.distributor_name cdssi.save() RegisterStatisticInfo.objects.select_for_update().get_or_create( brand_id=brand.brand_id, ymd=ymd, ) SaleStatisticInfo.objects.select_for_update().get_or_create( brand_id=brand.brand_id, ymd=ymd, ) ConsumeSaleStatisticInfo.objects.select_for_update().get_or_create( brand_id=brand.brand_id, ymd=ymd, ) def ytj(brand_id): # [消费者维度] 周期内扫描用户人数 cusis = ConsumeUserStatisticInfo.objects.filter(brand_id=brand_id, ymd__lt=9999) users = [] for cusi in cusis: users += cusi.users scan_user_count = len(set(users)) # [消费者维度] 周期内镜头销售支数 sell_volume_count = ConsumeSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd__lt=9999).aggregate(Sum('num')).get('num__sum', 0) or 0 startd = tc.local_string(months=-12, format='%Y%m%d') endd = tc.local_string(format='%Y%m%d') # [消费者维度] 统计周期内型号扫描排行数据,请按顺序返回 models = ConsumeModelSaleStatisticInfo.objects.filter( brand_id=brand_id, ymd__gt=startd, ymd__lte=endd, status=True ).order_by( 'model_name' ).values( 'model_id', 'model_name' ).annotate( num=Sum('num') ).order_by( '-num' )[:20] # [经销商维度] 统计周期内销售员排行数据,请按顺序返回 salesmen = SaleclerkSaleStatisticInfo.objects.filter( brand_id=brand_id, ymd__gt=startd, ymd__lte=endd, status=True ).order_by( 'clerk_id' ).values( 'clerk_id' ).annotate( num=Sum('num') ).order_by( '-num' )[:20] clerks = SaleclerkInfo.objects.filter(brand_id=brand_id, status=True) clerks = {clerk.clerk_id: {'distributor_id': clerk.distributor_id, 'distributor_name': clerk.distributor_name, 'clerk_name': clerk.clerk_name, 'salesman_id': clerk.clerk_id, 'salesman_name': clerk.clerk_name} for clerk in clerks} salesmen = [dict(sm, **clerks.get(sm.get('clerk_id', ''), {})) for sm in salesmen] # [收费者维度] 统计周期内省份销量排行数据,请按顺序返回 provinces = ConsumeProvinceSaleStatisticInfo.objects.filter( brand_id=brand_id, ymd__gt=startd, ymd__lte=endd, status=True ) # ).order_by( # 'province_code' # ).values( # 'province_code' # ).annotate( # num=Sum('num') # ).order_by( # '-num' # ) provinces_users = {} for province in provinces: if provinces_users.get(province.province_code): provinces_users[province.province_code] += province.users else: provinces_users[province.province_code] = province.users provinces = [{'province_code': province_code, 'num': len(set(users))} for province_code, users in provinces_users.items()] provinces = sorted(provinces, key=lambda p: p['num'], reverse=True) return { 'scan_user_count': scan_user_count, 'sell_volume_count': sell_volume_count, 'user_count_increase_pct': -1, # 与上个统计周期数据的用户人数比例 'volume_count_increase_pct': -1, # 与上个统计周期数据的销售支数比例 'models': list(models), 'salesmen': salesmen, 'provinces': provinces, } def ymdtj(brand_id, ymd, lastymd): # [消费者维度] 周期内扫描用户人数 try: scan_user_count = ConsumeUserStatisticInfo.objects.get(brand_id=brand_id, ymd=ymd).num except ConsumeUserStatisticInfo.DoesNotExist: scan_user_count = 0 try: last_scan_user_count = ConsumeUserStatisticInfo.objects.get(brand_id=brand_id, ymd=lastymd).num except ConsumeUserStatisticInfo.DoesNotExist: last_scan_user_count = 0 # [消费者维度] 周期内镜头销售支数 try: sell_volume_count = ConsumeSaleStatisticInfo.objects.get(brand_id=brand_id, ymd=ymd).num except ConsumeSaleStatisticInfo.DoesNotExist: sell_volume_count = 0 try: last_sell_volume_count = ConsumeSaleStatisticInfo.objects.get(brand_id=brand_id, ymd=lastymd).num except ConsumeSaleStatisticInfo.DoesNotExist: last_sell_volume_count = 0 # 与上个统计周期数据的销售支数比例 volume_count_increase_pct = '%.2f' % (sell_volume_count * 100.0 / last_sell_volume_count) if last_sell_volume_count != 0 else -1 # 与上个统计周期数据的用户人数比例 user_count_increase_pct = '%.2f' % (scan_user_count * 100.0 / last_scan_user_count) if last_scan_user_count != 0 else -1 # [消费者维度] 统计周期内型号扫描排行数据,请按顺序返回 current_models = ConsumeModelSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('-num') models = [m.data for m in current_models[:20]] # [经销商维度] 统计周期内销售员排行数据,请按顺序返回 salesmen = SaleclerkSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('-num') salesmen = [s.data for s in salesmen[:20]] # [收费者维度] 统计周期内省份销量排行数据,请按顺序返回 provinces = ConsumeProvinceSaleStatisticInfo.objects.filter(brand_id=brand_id, ymd=ymd, status=True).order_by('-num') provinces = [p.data for p in provinces] return { 'scan_user_count': scan_user_count, 'sell_volume_count': sell_volume_count, 'user_count_increase_pct': user_count_increase_pct, 'volume_count_increase_pct': volume_count_increase_pct, 'models': models, 'salesmen': salesmen, 'provinces': provinces, } @logit def v2_tj_distributor(request): brand_id = get_query_value(request, 'brand_id', settings.KODO_DEFAULT_BRAND_ID) # year = tc.local_string(format='%Y') month = tc.local_string(format='%Y%m') day = tc.local_string(format='%Y%m%d') # lastyear = tc.local_string(years=-1, format='%Y') lastmonth = tc.local_string(months=-1, format='%Y%m') lastday = tc.local_string(days=-1, format='%Y%m%d') year_data = ytj(brand_id) month_data = ymdtj(brand_id, month, lastmonth) day_data = ymdtj(brand_id, day, lastday) return response(200, 'Get TJ Data Success', u'获取统计数据成功', data={ 'year_data': year_data, 'month_data': month_data, 'day_data': day_data, })