@@ -33,7 +33,7 @@ class LensmanIncomeExpensesInfoAdmin(admin.ModelAdmin): |
||
33 | 33 |
|
34 | 34 |
class UserInfoAdmin(admin.ModelAdmin): |
35 | 35 |
readonly_fields = ('user_id', ) |
36 |
- list_display = ('user_id', 'user_from', 'username', 'wx_uid', 'name', 'sex', 'phone', 'location', 'user_status', 'status', 'created_at', 'updated_at') |
|
36 |
+ list_display = ('user_id', 'user_from', 'username', 'wx_uid', 'name', 'sex', 'nickname', 'phone', 'location', 'user_status', 'status', 'created_at', 'updated_at') |
|
37 | 37 |
search_fields = ('name', 'phone', 'location') |
38 | 38 |
list_filter = ('user_from', 'sex', 'user_status', 'status') |
39 | 39 |
|
@@ -0,0 +1,19 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+from __future__ import unicode_literals |
|
3 |
+ |
|
4 |
+from django.db import models, migrations |
|
5 |
+ |
|
6 |
+ |
|
7 |
+class Migration(migrations.Migration): |
|
8 |
+ |
|
9 |
+ dependencies = [ |
|
10 |
+ ('account', '0009_auto_20160428_1410'), |
|
11 |
+ ] |
|
12 |
+ |
|
13 |
+ operations = [ |
|
14 |
+ migrations.AddField( |
|
15 |
+ model_name='userinfo', |
|
16 |
+ name='uuid', |
|
17 |
+ field=models.CharField(max_length=255, blank=True, help_text='\u901a\u7528\u552f\u4e00\u8bc6\u522b\u7801 (Universally Unique Identifier)', null=True, verbose_name='uuid', db_index=True), |
|
18 |
+ ), |
|
19 |
+ ] |
@@ -127,6 +127,7 @@ class UserInfo(CreateUpdateMixin): |
||
127 | 127 |
user_id = models.CharField(_(u'user_id'), max_length=255, blank=True, null=True, help_text=u'用户唯一标识', db_index=True, unique=True) |
128 | 128 |
|
129 | 129 |
user_from = models.IntegerField(_(u'user_from'), choices=USER_FROM, default=APP_USER, help_text=u'用户来源') |
130 |
+ uuid = models.CharField(_(u'uuid'), max_length=255, blank=True, null=True, help_text=u'通用唯一识别码 (Universally Unique Identifier)', db_index=True) |
|
130 | 131 |
# APP 创建用户 |
131 | 132 |
username = models.CharField(_(u'username'), max_length=255, blank=True, null=True, help_text=u'用户用户名', db_index=True, unique=True) |
132 | 133 |
password = models.CharField(_(u'password'), max_length=255, blank=True, null=True, help_text=u'用户密码') |
@@ -1,7 +1,6 @@ |
||
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
|
3 | 3 |
from curtail_uuid import CurtailUUID |
4 |
-from django.conf import settings |
|
5 | 4 |
from django.contrib.auth.hashers import check_password, make_password |
6 | 5 |
from django.contrib.auth.models import Group, User |
7 | 6 |
from django.http import JsonResponse |
@@ -10,9 +9,12 @@ from TimeConvert import TimeConvert as tc |
||
10 | 9 |
|
11 | 10 |
from account.models import LensmanInfo, UserInfo, UserLoginLogInfo |
12 | 11 |
from account.serializers import GroupSerializer, LensmanInfoSerializer, UserInfoSerializer, UserSerializer |
12 |
+from operation.models import GuestEntranceControlInfo |
|
13 | 13 |
from utils.error.errno_utils import LensmanStatusCode, UserStatusCode |
14 | 14 |
from utils.error.response_utils import response |
15 | 15 |
from utils.ip_utils import ip_addr |
16 |
+from utils.redis.rversion import get_guest_entrance_control |
|
17 |
+from utils.version_utils import is_version_match |
|
16 | 18 |
|
17 | 19 |
|
18 | 20 |
# curl -X POST -F username=xxxxxxx -F password=xxxxxxx http://api.pai.ai/login |
@@ -209,10 +211,41 @@ def wx_authorize_api(request): |
||
209 | 211 |
|
210 | 212 |
|
211 | 213 |
def guest_login_api(request): |
212 |
- try: |
|
213 |
- user = UserInfo.objects.get(user_id=settings.GUEST_USER_ID) |
|
214 |
- except UserInfo.DoesNotExist: |
|
215 |
- return response(UserStatusCode.GUEST_NOT_FOUND) |
|
214 |
+ """ 游客登录 """ |
|
215 |
+ gen = get_guest_entrance_control() |
|
216 |
+ |
|
217 |
+ # 是否配置游客入口控制信息 |
|
218 |
+ if not gen: |
|
219 |
+ return response(UserStatusCode.GUEST_NOT_ALLOWED) |
|
220 |
+ |
|
221 |
+ # 平台校验 |
|
222 |
+ platform = gen.get('platform', '') |
|
223 |
+ if request.Android: |
|
224 |
+ if platform not in [GuestEntranceControlInfo.ADR, GuestEntranceControlInfo.BOTH]: |
|
225 |
+ return response(UserStatusCode.GUEST_NOT_ALLOWED) |
|
226 |
+ else: |
|
227 |
+ if platform not in [GuestEntranceControlInfo.IOS, GuestEntranceControlInfo.BOTH]: |
|
228 |
+ return response(UserStatusCode.GUEST_NOT_ALLOWED) |
|
229 |
+ |
|
230 |
+ # 版本校验 |
|
231 |
+ if not is_version_match(request, gen): |
|
232 |
+ return response(UserStatusCode.GUEST_NOT_ALLOWED) |
|
233 |
+ |
|
234 |
+ # 通用唯一识别码 (Universally Unique Identifier) |
|
235 |
+ uuid = request.POST.get('uuid', '') |
|
236 |
+ # 游客字段 |
|
237 |
+ fields = { |
|
238 |
+ 'user_id': CurtailUUID.uuid(UserInfo, 'user_id'), |
|
239 |
+ 'user_from': UserInfo.GUEST_USER, |
|
240 |
+ 'uuid': uuid, |
|
241 |
+ 'nickname': u'游客', |
|
242 |
+ 'user_status': UserInfo.ACTIVATED, |
|
243 |
+ } |
|
244 |
+ # 若 uuid 存在,则 get_or_create,否则 create |
|
245 |
+ if uuid: |
|
246 |
+ user, created = UserInfo.objects.get_or_create(user_from=UserInfo.GUEST_USER, uuid=uuid, defaults=fields) |
|
247 |
+ else: |
|
248 |
+ user = UserInfo.objects.create(**fields) |
|
216 | 249 |
|
217 | 250 |
return JsonResponse({ |
218 | 251 |
'status': 200, |
@@ -2,7 +2,8 @@ |
||
2 | 2 |
|
3 | 3 |
from django.contrib import admin |
4 | 4 |
|
5 |
-from operation.models import FeedbackInfo, LatestAppInfo, SplashInfo |
|
5 |
+from operation.models import FeedbackInfo, GuestEntranceControlInfo, LatestAppInfo, SplashInfo |
|
6 |
+from utils.redis.rversion import set_guest_entrance_control |
|
6 | 7 |
|
7 | 8 |
|
8 | 9 |
class LatestAppInfoAdmin(admin.ModelAdmin): |
@@ -17,6 +18,17 @@ class FeedbackInfoAdmin(admin.ModelAdmin): |
||
17 | 18 |
list_display = ('user_id', 'feedback', 'status', 'created_at', 'updated_at') |
18 | 19 |
|
19 | 20 |
|
21 |
+class GuestEntranceControlInfoAdmin(admin.ModelAdmin): |
|
22 |
+ list_display = ('platform', 'min_adr', 'max_adr', 'min_ios', 'max_ios', 'status', 'created_at', 'updated_at') |
|
23 |
+ |
|
24 |
+ def save_model(self, request, obj, form, change): |
|
25 |
+ obj.save() |
|
26 |
+ |
|
27 |
+ # 设置游客入口控制 |
|
28 |
+ set_guest_entrance_control(obj) |
|
29 |
+ |
|
30 |
+ |
|
20 | 31 |
admin.site.register(LatestAppInfo, LatestAppInfoAdmin) |
21 | 32 |
admin.site.register(SplashInfo, SplashInfoAdmin) |
22 | 33 |
admin.site.register(FeedbackInfo, FeedbackInfoAdmin) |
34 |
+admin.site.register(GuestEntranceControlInfo, GuestEntranceControlInfoAdmin) |
@@ -0,0 +1,32 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+from __future__ import unicode_literals |
|
3 |
+ |
|
4 |
+from django.db import models, migrations |
|
5 |
+ |
|
6 |
+ |
|
7 |
+class Migration(migrations.Migration): |
|
8 |
+ |
|
9 |
+ dependencies = [ |
|
10 |
+ ('operation', '0003_feedbackinfo'), |
|
11 |
+ ] |
|
12 |
+ |
|
13 |
+ operations = [ |
|
14 |
+ migrations.CreateModel( |
|
15 |
+ name='GuestEntranceControlInfo', |
|
16 |
+ fields=[ |
|
17 |
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
|
18 |
+ ('status', models.BooleanField(default=True, help_text='\u72b6\u6001', db_index=True, verbose_name='status')), |
|
19 |
+ ('created_at', models.DateTimeField(help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at', auto_now_add=True)), |
|
20 |
+ ('updated_at', models.DateTimeField(help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at', auto_now=True)), |
|
21 |
+ ('platform', models.IntegerField(default=0, help_text='\u652f\u6301\u5e73\u53f0', db_index=True, verbose_name='plat', choices=[(0, '\u5168\u5e73\u53f0'), (1, 'Android'), (2, 'iOS')])), |
|
22 |
+ ('min_adr', models.CharField(help_text='Adr \u6700\u4f4e\u7248\u672c', max_length=255, null=True, verbose_name='min_adr', blank=True)), |
|
23 |
+ ('min_ios', models.CharField(help_text='iOS \u6700\u4f4e\u7248\u672c', max_length=255, null=True, verbose_name='min_ios', blank=True)), |
|
24 |
+ ('max_adr', models.CharField(help_text='Adr \u6700\u9ad8\u7248\u672c', max_length=255, null=True, verbose_name='max_adr', blank=True)), |
|
25 |
+ ('max_ios', models.CharField(help_text='iOS \u6700\u9ad8\u7248\u672c', max_length=255, null=True, verbose_name='max_ios', blank=True)), |
|
26 |
+ ], |
|
27 |
+ options={ |
|
28 |
+ 'verbose_name': 'guestentrancecontrolinfo', |
|
29 |
+ 'verbose_name_plural': 'guestentrancecontrolinfo', |
|
30 |
+ }, |
|
31 |
+ ), |
|
32 |
+ ] |
@@ -8,7 +8,7 @@ from django.conf import settings |
||
8 | 8 |
from django.db import models |
9 | 9 |
from django.utils.translation import ugettext_lazy as _ |
10 | 10 |
|
11 |
-from pai2.basemodels import CreateUpdateMixin |
|
11 |
+from pai2.basemodels import CreateUpdateMixin, PlatformMixin, VersionMixin |
|
12 | 12 |
|
13 | 13 |
|
14 | 14 |
def upload_path(instance, old_filename): |
@@ -38,14 +38,13 @@ class LatestAppInfo(CreateUpdateMixin): |
||
38 | 38 |
def final_latest_url(self): |
39 | 39 |
return self.latest_url or u'{}{}'.format(settings.DOMAIN, self.latest_app and self.latest_app.url) |
40 | 40 |
|
41 |
- def _data(self): |
|
41 |
+ @property |
|
42 |
+ def data(self): |
|
42 | 43 |
return { |
43 | 44 |
'latest_version': self.latest_version, |
44 | 45 |
'latest_url': self.final_latest_url, |
45 | 46 |
} |
46 | 47 |
|
47 |
- data = property(_data) |
|
48 |
- |
|
49 | 48 |
|
50 | 49 |
class SplashInfo(CreateUpdateMixin): |
51 | 50 |
splash_image = models.ImageField(_(u'splash_image'), upload_to=upload_path, blank=True, null=True, help_text=u'启动页面图片') |
@@ -63,15 +62,14 @@ class SplashInfo(CreateUpdateMixin): |
||
63 | 62 |
def splash_image_url(self): |
64 | 63 |
return self.splash_image and (settings.DOMAIN + self.splash_image.url) |
65 | 64 |
|
66 |
- def _data(self): |
|
65 |
+ @property |
|
66 |
+ def data(self): |
|
67 | 67 |
return { |
68 | 68 |
'splash_image_url': self.splash_image_url, |
69 | 69 |
'spalash_image_airtime': self.spalash_image_airtime, |
70 | 70 |
'spalash_image_deadline': self.spalash_image_deadline, |
71 | 71 |
} |
72 | 72 |
|
73 |
- data = property(_data) |
|
74 |
- |
|
75 | 73 |
|
76 | 74 |
class FeedbackInfo(CreateUpdateMixin): |
77 | 75 |
user_id = models.CharField(_(u'user_id'), max_length=255, blank=True, null=True, help_text=u'用户唯一标识') |
@@ -83,3 +81,23 @@ class FeedbackInfo(CreateUpdateMixin): |
||
83 | 81 |
|
84 | 82 |
def __unicode__(self): |
85 | 83 |
return u'{0.pk}'.format(self) |
84 |
+ |
|
85 |
+ |
|
86 |
+class GuestEntranceControlInfo(CreateUpdateMixin, PlatformMixin, VersionMixin): |
|
87 |
+ |
|
88 |
+ class Meta: |
|
89 |
+ verbose_name = _('guestentrancecontrolinfo') |
|
90 |
+ verbose_name_plural = _('guestentrancecontrolinfo') |
|
91 |
+ |
|
92 |
+ def __unicode__(self): |
|
93 |
+ return u'{0.pk}'.format(self) |
|
94 |
+ |
|
95 |
+ @property |
|
96 |
+ def data(self): |
|
97 |
+ return { |
|
98 |
+ 'platform': self.platform, |
|
99 |
+ 'min_adr': self.min_adr, |
|
100 |
+ 'min_ios': self.min_ios, |
|
101 |
+ 'max_adr': self.max_adr, |
|
102 |
+ 'max_ios': self.max_ios, |
|
103 |
+ } |
@@ -3,6 +3,8 @@ |
||
3 | 3 |
from django.db import models |
4 | 4 |
from django.utils.translation import ugettext_lazy as _ |
5 | 5 |
|
6 |
+from utils.version_utils import is_version_match |
|
7 |
+ |
|
6 | 8 |
|
7 | 9 |
class CreateUpdateMixin(models.Model): |
8 | 10 |
status = models.BooleanField(_(u'status'), default=True, help_text=_(u'状态'), db_index=True) |
@@ -11,3 +13,38 @@ class CreateUpdateMixin(models.Model): |
||
11 | 13 |
|
12 | 14 |
class Meta: |
13 | 15 |
abstract = True |
16 |
+ |
|
17 |
+ |
|
18 |
+class PlatformMixin(models.Model): |
|
19 |
+ BOTH = 0 |
|
20 |
+ ADR = 1 |
|
21 |
+ IOS = 2 |
|
22 |
+ |
|
23 |
+ SUPPORT_PLATFORM = ( |
|
24 |
+ (BOTH, u'全平台'), |
|
25 |
+ (ADR, u'Android'), |
|
26 |
+ (IOS, u'iOS'), |
|
27 |
+ ) |
|
28 |
+ |
|
29 |
+ platform = models.IntegerField(_(u'plat'), choices=SUPPORT_PLATFORM, default=BOTH, help_text=u'支持平台', db_index=True) |
|
30 |
+ |
|
31 |
+ class Meta: |
|
32 |
+ abstract = True |
|
33 |
+ |
|
34 |
+ |
|
35 |
+class VersionMixin(models.Model): |
|
36 |
+ min_adr = models.CharField(_(u'min_adr'), max_length=255, blank=True, null=True, help_text=u'Adr 最低版本') |
|
37 |
+ min_ios = models.CharField(_(u'min_ios'), max_length=255, blank=True, null=True, help_text=u'iOS 最低版本') |
|
38 |
+ max_adr = models.CharField(_(u'max_adr'), max_length=255, blank=True, null=True, help_text=u'Adr 最高版本') |
|
39 |
+ max_ios = models.CharField(_(u'max_ios'), max_length=255, blank=True, null=True, help_text=u'iOS 最高版本') |
|
40 |
+ |
|
41 |
+ def version_match(self, request): |
|
42 |
+ return is_version_match(request, { |
|
43 |
+ 'min_adr': self.min_adr, |
|
44 |
+ 'min_ios': self.min_ios, |
|
45 |
+ 'max_adr': self.max_adr, |
|
46 |
+ 'max_ios': self.max_ios, |
|
47 |
+ }) |
|
48 |
+ |
|
49 |
+ class Meta: |
|
50 |
+ abstract = True |
@@ -64,6 +64,7 @@ MIDDLEWARE_CLASSES = ( |
||
64 | 64 |
'django.contrib.messages.middleware.MessageMiddleware', |
65 | 65 |
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
66 | 66 |
'django.middleware.security.SecurityMiddleware', |
67 |
+ 'detect.middleware.UserAgentDetectionMiddleware', |
|
67 | 68 |
) |
68 | 69 |
|
69 | 70 |
MIDDLEWARE_CLASSES += ('multidomain.middleware.DomainMiddleware', ) |
@@ -254,6 +255,11 @@ GROUP_PER_PAGE = 20 # 群组每页数量 |
||
254 | 255 |
# 游客设置 |
255 | 256 |
GUEST_USER_ID = 'guest' |
256 | 257 |
|
258 |
+# 版本设置 |
|
259 |
+MIN_VERSION = '0.0.0' |
|
260 |
+MAX_VERSION = '999.999.999' |
|
261 |
+CURRENT_VERSION = '1.0.0' |
|
262 |
+ |
|
257 | 263 |
# 价格设置 |
258 | 264 |
LENSMAN_PHOTO_HAGGLE_MAX_TIMES = 3 # 摄影师照片最大砍价次数 |
259 | 265 |
|
@@ -1,10 +1,11 @@ |
||
1 | 1 |
CodeConvert==2.0.4 |
2 | 2 |
Django==1.8.4 |
3 | 3 |
MySQL-python==1.2.5 |
4 |
-TimeConvert==1.1.6 |
|
4 |
+TimeConvert==1.2.0 |
|
5 | 5 |
cryptography==1.2.1 |
6 | 6 |
django-curtail-uuid==1.0.0 |
7 |
-django-logit==1.0.0 |
|
7 |
+django-detect==1.0.3 |
|
8 |
+django-logit==1.0.2 |
|
8 | 9 |
django-multidomain==1.1.4 |
9 | 10 |
django-shortuuidfield==0.1.3 |
10 | 11 |
djangorestframework==3.3.1 |
@@ -17,4 +18,5 @@ pytz==2015.7 |
||
17 | 18 |
redis==2.10.5 |
18 | 19 |
shortuuid==0.4.2 |
19 | 20 |
uWSGI==2.0.11.1 |
20 |
-wechatpy==1.2.6 |
|
21 |
+versions==0.10.0 |
|
22 |
+wechatpy==1.2.8 |
@@ -26,7 +26,7 @@ class UserStatusCode(BaseStatusCode): |
||
26 | 26 |
USER_PASSWORD_ERROR = StatusCodeField(400102, u'User Password Error', description=u'用户密码错误') |
27 | 27 |
USERNAME_HAS_REGISTERED = StatusCodeField(400103, u'Username Has Registered', description=u'用户名已注册') |
28 | 28 |
|
29 |
- GUEST_NOT_FOUND = StatusCodeField(400111, u'Guest Not Found', description=u'游客不存在') |
|
29 |
+ GUEST_NOT_ALLOWED = StatusCodeField(400111, u'Guest Not ALLOWED', description=u'游客登录不允许') |
|
30 | 30 |
|
31 | 31 |
|
32 | 32 |
class PhotoStatusCode(BaseStatusCode): |
@@ -26,3 +26,6 @@ LENSMAN_PHOTO_ORDER_RECORD = 'lensman:photo:order:record:%s:%s' # STRING,摄 |
||
26 | 26 |
# 系统消息相关 |
27 | 27 |
SYSTEM_MESSAGE_READ_INFO = 'system:message:read:info:%s' # STRING,系统消息读取信息,user_id |
28 | 28 |
SYSTEM_MESSAGE_DELETED_INFO = 'system:message:deleted:info:%s' # STRING,系统消息删除信息,user_id |
29 |
+ |
|
30 |
+# 游客入口相关 |
|
31 |
+GUEST_ENTRANCE_CONTROL_INFO = 'guest:entrance:control:info' # STRING,游客入口控制信息 |
@@ -0,0 +1,24 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+import json |
|
4 |
+ |
|
5 |
+from django.conf import settings |
|
6 |
+ |
|
7 |
+from utils.redis.rkeys import GUEST_ENTRANCE_CONTROL_INFO |
|
8 |
+ |
|
9 |
+ |
|
10 |
+r = settings.REDIS_CACHE |
|
11 |
+ |
|
12 |
+ |
|
13 |
+# 游客入口控制相关 |
|
14 |
+ |
|
15 |
+ |
|
16 |
+def set_guest_entrance_control(gen): |
|
17 |
+ """ 设置游客入口控制 """ |
|
18 |
+ r.set(GUEST_ENTRANCE_CONTROL_INFO, json.dumps(gen.data)) |
|
19 |
+ return gen.data |
|
20 |
+ |
|
21 |
+ |
|
22 |
+def get_guest_entrance_control(): |
|
23 |
+ """ 获取游客入口控制 """ |
|
24 |
+ return json.loads(r.get(GUEST_ENTRANCE_CONTROL_INFO) or '{}') |
@@ -0,0 +1,10 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+from django.conf import settings |
|
4 |
+from versions import Version |
|
5 |
+ |
|
6 |
+ |
|
7 |
+def is_version_match(request, vers={}): |
|
8 |
+ minv, maxv = (vers.get('min_adr', ''), vers.get('max_adr', '')) if request.Android else (vers.get('min_ios', ''), vers.get('max_ios', '')) |
|
9 |
+ return Version.parse(minv or settings.MIN_VERSION) <= Version.parse( |
|
10 |
+ request.REQUEST.get('version', settings.CURRENT_VERSION)) <= Version.parse(maxv or settings.MAX_VERSION) |