Add tour guide apis

Brightcells 8 gadi atpakaļ
vecāks
revīzija
f724e0f9c0

+ 5 - 0
TODO.md

@@ -0,0 +1,5 @@
1
+* ``Refresh Cache`` => ``Delete Cache``
2
+* Remove GROUP_USERS_XXX_SET ?
3
+* ``get_group_info`` vs. ``group.data``
4
+* Docstring simplify
5
+* ``admin_id`` vs. ``user_id`

+ 34 - 2
account/tourguide_views.py

@@ -5,7 +5,7 @@ from __future__ import division
5 5
 from django.conf import settings
6 6
 from logit import logit
7 7
 
8
-from account.models import TourGuideInfo
8
+from account.models import TourGuideInfo, UserInfo
9 9
 from utils.error.errno_utils import TourGuideStatusCode
10 10
 from utils.error.response_utils import response
11 11
 
@@ -37,7 +37,7 @@ def tourguide_submit_api(request):
37 37
     }
38 38
 
39 39
     tourguide, created = TourGuideInfo.objects.get_or_create(unionid=unionid, defaults=fields)
40
-    # 状态为 UNVERIFIED 的允许修改, 其他需要登录摄影师 APP 进行信息的修改
40
+    # 状态为 UNVERIFIED 的允许修改, 其他需要登录导游 APP 进行信息的修改
41 41
     if tourguide.user_status not in [TourGuideInfo.UNVERIFIED, TourGuideInfo.REFUSED]:
42 42
         return response(TourGuideStatusCode.TOURGUIDE_ALREADY_NOT_UNVERIFIED)
43 43
     if not created:
@@ -46,3 +46,35 @@ def tourguide_submit_api(request):
46 46
         tourguide.save()
47 47
 
48 48
     return response(200, 'Submit Success', u'提交成功', {})
49
+
50
+
51
+@logit
52
+def tourguide_wx_authorize_api(request):
53
+    unionid = request.POST.get('unionid', '')
54
+    openid = request.POST.get('openid', '')
55
+
56
+    sex = request.POST.get('sex', 0)
57
+    nickname = request.POST.get('nickname', '') or request.POST.get('screen_name', '')
58
+    avatar = request.POST.get('headimgurl', '') or request.POST.get('profile_image_url', '')
59
+    country = request.POST.get('country', '')
60
+    province = request.POST.get('province', '')
61
+    city = request.POST.get('city', '')
62
+
63
+    try:
64
+        user = UserInfo.objects.get(unionid=unionid)
65
+    except UserInfo.DoesNotExist:
66
+        return response(TourGuideStatusCode.TOURGUIDE_NOT_FOUND)
67
+
68
+    if user.user_status != UserInfo.ACTIVATED:
69
+        return response(TourGuideStatusCode.TOURGUIDE_NOT_ACTIVATED)
70
+
71
+    user.openid = openid
72
+    user.sex = sex
73
+    user.nickname = nickname
74
+    user.avatar = avatar
75
+    user.country = country
76
+    user.province = province
77
+    user.city = city
78
+    user.save()
79
+
80
+    return response(200, 'Tour Guide Login Success', u'导游登录成功', user.data)

+ 0 - 1
account/views.py

@@ -133,7 +133,6 @@ def user_wx_authorize_api(request):
133 133
     province = request.POST.get('province', '')
134 134
     city = request.POST.get('city', '')
135 135
 
136
-    # 判断 unionid 是否已经存在,如果已经存在,则直接返回改帐户信息
137 136
     try:
138 137
         user = UserInfo.objects.select_for_update().get(unionid=unionid)
139 138
     except UserInfo.DoesNotExist:

+ 33 - 3
api/urls.py

@@ -4,8 +4,9 @@ from django.conf.urls import url
4 4
 
5 5
 from account import views as account_views
6 6
 from account import tourguide_views
7
+from geo import views as geo_views
7 8
 from group import views as group_views
8
-from group import lensman_views
9
+from group import lensman_views, tourguidegroup_views, tourguidegroupuser_views
9 10
 from message import views as message_views
10 11
 from operation import views as op_views
11 12
 from pay import views as pay_views
@@ -45,6 +46,8 @@ urlpatterns += [
45 46
 # 导游相关
46 47
 urlpatterns += [
47 48
     url(r'^t/submit$', tourguide_views.tourguide_submit_api, name='tourguide_submit_api'),  # 导游信息提交
49
+
50
+    url(r'^t/wx/authorize$', tourguide_views.tourguide_wx_authorize_api, name='tourguide_wx_authorize_api'),  # 微信用户授权
48 51
 ]
49 52
 
50 53
 # 群组相关
@@ -53,14 +56,36 @@ urlpatterns += [
53 56
     url(r'^g/detail$', group_views.group_detail_api, name='group_detail_api'),  # 群组详情
54 57
     url(r'^g/update$', group_views.group_update_api, name='group_update_api'),  # 群组更新
55 58
     url(r'^g/list$', group_views.group_list_api, name='group_list_api'),  # 群组列表
56
-    url(r'^g/join$', group_views.group_join_api, name='group_join_api'),  # 申请加群
57 59
     url(r'^g/lock$', group_views.group_lock_api, name='group_lock_api'),  # 群组锁定
58 60
     url(r'^g/unlock$', group_views.group_unlock_api, name='group_unlock_api'),  # 群组解锁
61
+    url(r'^g/data$', group_views.group_data_api, name='group_data_api'),  # 群组数据, 评论数, 点赞数
62
+]
63
+
64
+# 群用户相关
65
+urlpatterns += [
66
+    url(r'^g/join$', group_views.group_join_api, name='group_join_api'),  # 成员申请加群
59 67
     url(r'^g/remove$', group_views.group_remove_api, name='group_remove_api'),  # 成员移除, 管理员主动, 群成员被动
60 68
     url(r'^g/quit$', group_views.group_quit_api, name='group_quit_api'),  # 成员退出,群成员主动
61 69
     # url(r'^g/pass$', group_views.group_pass_api, name='group_pass_api'),  # 申请通过
62 70
     # url(r'^g/refuse$', group_views.group_refuse_api, name='group_refuse_api'),  # 申请拒绝
63
-    url(r'^g/data$', group_views.group_data_api, name='group_data_api'),  # 群组数据, 评论数, 点赞数
71
+]
72
+
73
+# 导游团相关
74
+urlpatterns += [
75
+    url(r'^tg/create$', tourguidegroup_views.tg_group_create_api, name='tg_group_create_api'),  # 导游团创建
76
+    url(r'^tg/detail$', tourguidegroup_views.tg_group_detail_api, name='tg_group_detail_api'),  # 导游团详情
77
+    url(r'^tg/update$', tourguidegroup_views.tg_group_update_api, name='tg_group_update_api'),  # 导游团更新
78
+    url(r'^tg/close$', tourguidegroup_views.tg_group_close_api, name='tg_group_close_api'),  # 导游团关闭
79
+    url(r'^tg/gather/start$', tourguidegroup_views.tg_group_gather_start_api, name='tg_group_gather_start_api'),  # 导游团设置集合时间和地点
80
+    # url(r'^tg/gather/end$', tourguidegroup_views.tg_group_gather_end_api, name='tg_group_gather_end_api'),  # 导游团集合结束,清理数据
81
+]
82
+
83
+# 导游团用户相关
84
+urlpatterns += [
85
+    url(r'^tgu/join$', tourguidegroupuser_views.tgu_group_user_join_api, name='tgu_group_user_join_api'),  # 导游团用户加群
86
+    url(r'^tgu/update$', tourguidegroupuser_views.tgu_group_user_update_api, name='tg_group_update_api'),  # 导游团用户更新
87
+    url(r'^tgu/locations$', tourguidegroupuser_views.tgu_group_user_locations_api, name='tgu_group_user_locations_api'),  # 导游团所有用户位置信息
88
+    url(r'^tgu/location$', tourguidegroupuser_views.tgu_group_user_location_api, name='tgu_group_user_location_api'),  # 导游团单个用户位置信息
64 89
 ]
65 90
 
66 91
 # 飞图相关
@@ -107,6 +132,11 @@ urlpatterns += [
107 132
     url(r'^op/download$', op_views.download_api, name='download_api'),  # 下载接口
108 133
 ]
109 134
 
135
+# 地理位置相关
136
+urlpatterns += [
137
+    url(r'^geo/submit$', geo_views.geo_submit_api, name='geo_submit_api'),  # 地理位置信息提交
138
+]
139
+
110 140
 # 支付相关
111 141
 urlpatterns += [
112 142
     url(r'^wx/order_create$', pay_views.wx_order_create_api, name='wx_order_create_api'),  # 订单创建

+ 0 - 0
geo/__init__.py


+ 4 - 0
geo/admin.py

@@ -0,0 +1,4 @@
1
+from django.contrib import admin
2
+
3
+
4
+# Register your models here.

+ 0 - 0
geo/migrations/__init__.py


+ 4 - 0
geo/models.py

@@ -0,0 +1,4 @@
1
+from django.db import models
2
+
3
+
4
+# Create your models here.

+ 4 - 0
geo/tests.py

@@ -0,0 +1,4 @@
1
+from django.test import TestCase
2
+
3
+
4
+# Create your tests here.

+ 38 - 0
geo/views.py

@@ -0,0 +1,38 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from __future__ import division
4
+
5
+import json
6
+
7
+from django.conf import settings
8
+from logit import logit
9
+
10
+from utils.error.errno_utils import GroupUserStatusCode
11
+from utils.error.response_utils import response
12
+from utils.redis.rkeys import (TOUR_GUIDE_GROUP_CUR_SESSION, TOUR_GUIDE_GROUP_GEO_INFO, TOUR_GUIDE_GROUP_USER_BELONG,
13
+                               TOUR_GUIDE_GROUP_USER_GEO_LIST)
14
+
15
+
16
+r = settings.REDIS_CACHE
17
+
18
+
19
+@logit
20
+def geo_submit_api(request):
21
+    user_id = request.POST.get('user_id', '')
22
+    longitude = request.POST.get('lon', '')  # 经度
23
+    latitude = request.POST.get('lat', '')  # 纬度
24
+
25
+    # 获取用户当前所处导游群组
26
+    group_id = r.get(TOUR_GUIDE_GROUP_USER_BELONG % user_id)
27
+    if not group_id:
28
+        return response(GroupUserStatusCode.USER_HAS_NOT_JOIN_GROUP)
29
+
30
+    r.geoadd(TOUR_GUIDE_GROUP_GEO_INFO % group_id, longitude, latitude, user_id)
31
+
32
+    session_id = r.get(TOUR_GUIDE_GROUP_CUR_SESSION % group_id)
33
+    r.rpush(TOUR_GUIDE_GROUP_USER_GEO_LIST % (group_id, session_id, user_id), json.dumps({
34
+        'lon': longitude,
35
+        'lat': latitude,
36
+    }))
37
+
38
+    return response(200, 'Geo Info Submit Success', u'地理位置信息上传成功')

+ 0 - 1
group/lensman_views.py

@@ -107,7 +107,6 @@ def lensman_wx_authorize_api(request):
107 107
     province = request.POST.get('province', '')
108 108
     city = request.POST.get('city', '')
109 109
 
110
-    # 判断 unionid 是否已经存在,如果已经存在,则直接返回改帐户信息
111 110
     try:
112 111
         user = UserInfo.objects.get(unionid=unionid)
113 112
     except UserInfo.DoesNotExist:

+ 74 - 0
group/migrations/0024_auto_20161214_1329.py

@@ -0,0 +1,74 @@
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
+        ('group', '0023_groupinfo_group_initio'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='groupinfo',
16
+            name='ended_at',
17
+            field=models.DateTimeField(help_text='\u7ed3\u675f\u65f6\u95f4', null=True, verbose_name='ended_at', blank=True),
18
+        ),
19
+        migrations.AddField(
20
+            model_name='groupinfo',
21
+            name='gather_at',
22
+            field=models.DateTimeField(help_text='\u96c6\u5408\u65f6\u95f4', null=True, verbose_name='gather_at', blank=True),
23
+        ),
24
+        migrations.AddField(
25
+            model_name='groupinfo',
26
+            name='gather_lat',
27
+            field=models.FloatField(help_text='\u96c6\u5408\u7eac\u5ea6', null=True, verbose_name='gather_lat', blank=True),
28
+        ),
29
+        migrations.AddField(
30
+            model_name='groupinfo',
31
+            name='gather_lon',
32
+            field=models.FloatField(help_text='\u96c6\u5408\u7ecf\u5ea6', null=True, verbose_name='gather_lon', blank=True),
33
+        ),
34
+        migrations.AddField(
35
+            model_name='groupinfo',
36
+            name='group_closed',
37
+            field=models.BooleanField(default=False, help_text='\u7fa4\u7ec4\u5173\u95ed', verbose_name='group_closed'),
38
+        ),
39
+        migrations.AddField(
40
+            model_name='groupinfo',
41
+            name='started_at',
42
+            field=models.DateTimeField(help_text='\u5f00\u59cb\u65f6\u95f4', null=True, verbose_name='started_at', blank=True),
43
+        ),
44
+        migrations.AddField(
45
+            model_name='groupuserinfo',
46
+            name='name',
47
+            field=models.CharField(help_text='\u7528\u6237\u59d3\u540d', max_length=255, null=True, verbose_name='name', blank=True),
48
+        ),
49
+        migrations.AddField(
50
+            model_name='groupuserinfo',
51
+            name='phone',
52
+            field=models.CharField(help_text='\u7528\u6237\u7535\u8bdd', max_length=255, null=True, verbose_name='phone', blank=True),
53
+        ),
54
+        migrations.AddField(
55
+            model_name='groupuserinfo',
56
+            name='relative_person',
57
+            field=models.IntegerField(default=1, help_text='\u5173\u8054\u4eba\u6570', verbose_name='relative_person'),
58
+        ),
59
+        migrations.AddField(
60
+            model_name='groupuserinfo',
61
+            name='remark',
62
+            field=models.CharField(help_text='\u5907\u6ce8', max_length=255, null=True, verbose_name='remark', blank=True),
63
+        ),
64
+        migrations.AddField(
65
+            model_name='groupuserinfo',
66
+            name='subadmin',
67
+            field=models.BooleanField(default=False, help_text='\u526f\u7fa4\u7ec4\u7ba1\u7406\u5458', verbose_name='subadmin'),
68
+        ),
69
+        migrations.AlterField(
70
+            model_name='groupinfo',
71
+            name='group_from',
72
+            field=models.IntegerField(default=0, help_text='\u7fa4\u7ec4\u6765\u6e90', verbose_name='group_from', choices=[(0, 'APP \u5efa\u7fa4'), (1, 'SESSION \u5efa\u7fa4'), (10, '\u5bfc\u6e38\u5efa\u7fa4')]),
73
+        ),
74
+    ]

+ 25 - 0
group/models.py

@@ -17,10 +17,12 @@ r = settings.REDIS_CACHE
17 17
 class GroupInfo(CreateUpdateMixin):
18 18
     APP_GROUP = 0
19 19
     SESSION_GROUP = 1
20
+    TOURGUIDE_GROUP = 10
20 21
 
21 22
     GROUP_FROM = (
22 23
         (APP_GROUP, u'APP 建群'),
23 24
         (SESSION_GROUP, u'SESSION 建群'),
25
+        (TOURGUIDE_GROUP, u'导游建群'),
24 26
     )
25 27
 
26 28
     group_id = models.CharField(_(u'group_id'), max_length=255, blank=True, null=True, help_text=u'群组唯一标识', db_index=True, unique=True)
@@ -33,6 +35,13 @@ class GroupInfo(CreateUpdateMixin):
33 35
     session_id = models.CharField(_(u'session_id'), max_length=255, blank=True, null=True, help_text=u'照片组唯一标识', db_index=True)
34 36
     group_lock = models.BooleanField(_(u'group_lock'), default=False, help_text=u'群组锁定')
35 37
     group_initio = models.BooleanField(_(u'group_initio'), default=False, help_text=u'群组查看照片从头开始')
38
+    # 导游团
39
+    started_at = models.DateTimeField(_(u'started_at'), blank=True, null=True, help_text=_(u'开始时间'))
40
+    ended_at = models.DateTimeField(_(u'ended_at'), blank=True, null=True, help_text=_(u'结束时间'))
41
+    group_closed = models.BooleanField(_(u'group_closed'), default=False, help_text=u'群组关闭')
42
+    gather_at = models.DateTimeField(_(u'gather_at'), blank=True, null=True, help_text=_(u'集合时间'))
43
+    gather_lon = models.FloatField(_(u'gather_lon'), blank=True, null=True, help_text=_(u'集合经度'))
44
+    gather_lat = models.FloatField(_(u'gather_lat'), blank=True, null=True, help_text=_(u'集合纬度'))
36 45
 
37 46
     class Meta:
38 47
         verbose_name = _(u'groupinfo')
@@ -57,6 +66,11 @@ class GroupInfo(CreateUpdateMixin):
57 66
             'group_from': self.group_from,
58 67
             'group_lock': self.group_lock,
59 68
             'group_initio': self.group_initio,
69
+            'started_at': self.started_at.replace(microsecond=0),
70
+            'ended_at': self.ended_at.replace(microsecond=0),
71
+            'gather_at': self.gather_at.replace(microsecond=0),
72
+            'gather_lon': self.gather_lon,
73
+            'gather_lat': self.gather_lat,
60 74
             'created_at': self.created_at.replace(microsecond=0),
61 75
         }
62 76
 
@@ -111,6 +125,12 @@ class GroupUserInfo(CreateUpdateMixin):
111 125
     refused_at = models.DateTimeField(_(u'refused_at'), blank=True, null=True, help_text=_(u'拒绝时间'))
112 126
     deleted_at = models.DateTimeField(_(u'deleted_at'), blank=True, null=True, help_text=_(u'删除时间'))
113 127
     quit_at = models.DateTimeField(_(u'quit_at'), blank=True, null=True, help_text=_(u'退出时间'))
128
+    # 导游团相关
129
+    subadmin = models.BooleanField(_(u'subadmin'), default=False, help_text=u'副群组管理员')
130
+    name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'用户姓名')
131
+    phone = models.CharField(_(u'phone'), max_length=255, blank=True, null=True, help_text=u'用户电话')
132
+    relative_person = models.IntegerField(_(u'relative_person'), default=1, help_text=u'关联人数')
133
+    remark = models.CharField(_(u'remark'), max_length=255, blank=True, null=True, help_text=u'备注')
114 134
 
115 135
     class Meta:
116 136
         verbose_name = _(u'groupuserinfo')
@@ -126,6 +146,11 @@ class GroupUserInfo(CreateUpdateMixin):
126 146
             'nickname': self.nickname,
127 147
             'avatar': self.avatar,
128 148
             'admin': self.admin,
149
+            'subadmin': self.subadmin,
150
+            'name': self.name,
151
+            'phone': self.phone,
152
+            'relative_person': self.relative_person,
153
+            'remark': self.remark,
129 154
         }
130 155
 
131 156
 

+ 239 - 0
group/tourguidegroup_views.py

@@ -0,0 +1,239 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from __future__ import division
4
+
5
+import os
6
+
7
+import shortuuid
8
+from curtail_uuid import CurtailUUID
9
+from django.conf import settings
10
+from django.core.files.storage import default_storage
11
+from logit import logit
12
+from TimeConvert import TimeConvert as tc
13
+
14
+from account.models import UserInfo
15
+from group.models import GroupInfo, GroupUserInfo
16
+from utils.error.errno_utils import GroupStatusCode, UserStatusCode
17
+from utils.error.response_utils import response
18
+from utils.redis.rgroup import get_group_info, get_group_users_info, set_group_info, set_group_users_info
19
+from utils.redis.rkeys import TOUR_GUIDE_GROUP_CUR_SESSION
20
+from utils.redis.rtourguide import set_tour_guide_own_group
21
+
22
+
23
+r = settings.REDIS_CACHE
24
+
25
+
26
+@logit
27
+def tg_group_create_api(request):
28
+    """
29
+    导游团创建
30
+    :param request:
31
+    :return:
32
+    """
33
+    user_id = request.POST.get('user_id', '')
34
+    group_name = request.POST.get('group_name', '')
35
+    group_default_avatar = int(request.POST.get('group_default_avatar', 0))
36
+    started_at = request.POST.get('started_at', '')
37
+    ended_at = request.POST.get('ended_at', '')
38
+
39
+    # 用户校验
40
+    try:
41
+        user = UserInfo.objects.get(user_id=user_id)
42
+    except UserInfo.DoesNotExist:
43
+        return response(UserStatusCode.USER_NOT_FOUND)
44
+
45
+    # 权限校验
46
+    if not user.istourguide:
47
+        return response(GroupStatusCode.NOT_GROUP_ADMIN)
48
+
49
+    # 导游团校验
50
+    if GroupInfo.objects.filter(
51
+        admin_id=user_id,
52
+        group_closed=False,
53
+        status=True,
54
+        ended_at__gt=tc.utc_datetime(),
55
+    ).exists():
56
+        return response(GroupStatusCode.COULD_HAVE_ONLY_ONE_ACTIVE_GROUP)
57
+
58
+    # 群组唯一标识
59
+    group_id = CurtailUUID.uuid(GroupInfo, 'group_id')
60
+
61
+    # 群组记录创建
62
+    group = GroupInfo.objects.create(
63
+        group_id=group_id,
64
+        admin_id=user_id,
65
+        group_name=group_name,
66
+        group_default_avatar=group_default_avatar,
67
+        group_from=GroupInfo.TOURGUIDE_GROUP,
68
+        started_at=started_at,
69
+        ended_at=ended_at,
70
+    )
71
+
72
+    # Redis 群组数据缓存
73
+    group_info = set_group_info(group)
74
+
75
+    # 群组用户记录创建
76
+    GroupUserInfo.objects.create(
77
+        group_id=group_id,
78
+        user_id=user_id,
79
+        nickname=user.final_nickname,
80
+        avatar=user.avatar,
81
+        admin=True,
82
+        user_status=GroupUserInfo.PASSED,
83
+        passed_at=tc.utc_datetime(),
84
+        subadmin=True,
85
+    )
86
+
87
+    # Redis 群组用户数据缓存
88
+    group_users = set_group_users_info(group)
89
+
90
+    # Redis 设置导游拥有的导游团
91
+    set_tour_guide_own_group(user_id, group_id)
92
+
93
+    return response(200, 'Create Tour Guide Group Success', u'导游团创建成功', {
94
+        'group_id': group_id,
95
+        'group': group_info,
96
+        'users': group_users,
97
+    })
98
+
99
+
100
+@logit
101
+def tg_group_detail_api(request):
102
+    """
103
+    导游团详情
104
+    :param request:
105
+    :return:
106
+    """
107
+    group_id = request.POST.get('group_id', '')
108
+    user_id = request.POST.get('user_id', '')
109
+
110
+    return response(200, 'Get Tour Guide Group Detail Info Success', u'获取导游团详情成功', {
111
+        'group_id': group_id,
112
+        'group': get_group_info(group_id),
113
+        'users': get_group_users_info(group_id, user_id),
114
+    })
115
+
116
+
117
+@logit
118
+def tg_group_update_api(request):
119
+    """
120
+    导游团更新
121
+    :param request:
122
+    :return:
123
+    """
124
+    group_id = request.POST.get('group_id', '')
125
+    admin_id = request.POST.get('admin_id', '') or request.POST.get('user_id', '')
126
+    group_name = request.POST.get('group_name', '')
127
+    group_desc = request.POST.get('group_desc', '')
128
+
129
+    group_avatar = request.FILES.get('group_avatar', '')
130
+
131
+    started_at = request.POST.get('started_at', '')
132
+    ended_at = request.POST.get('ended_at', '')
133
+
134
+    # 群组校验
135
+    try:
136
+        group = GroupInfo.objects.get(group_id=group_id)
137
+    except GroupInfo.DoesNotExist:
138
+        return response(GroupStatusCode.GROUP_NOT_FOUND)
139
+
140
+    # 权限校验
141
+    if group.admin_id != admin_id:
142
+        return response(GroupStatusCode.NO_UPDATE_PERMISSION)
143
+
144
+    # 群组名称更新
145
+    if group_name:
146
+        group.group_name = group_name
147
+    # 群组描述更新
148
+    if group_desc:
149
+        group.group_desc = group_desc
150
+    # 群组头像更新
151
+    if group_avatar:
152
+        _, extension = os.path.splitext(group_avatar.name)
153
+        group_avatar_path = 'group/{uuid}_{extension}'.format(uuid=shortuuid.uuid(), extension=extension)
154
+        if default_storage.exists(group_avatar_path):
155
+            default_storage.delete(group_avatar_path)
156
+        default_storage.save(group_avatar_path, group_avatar)
157
+        group.group_avatar = group_avatar_path
158
+    # 起止时间更新
159
+    group.started_at = started_at
160
+    group.ended_at = ended_at
161
+    group.save()
162
+
163
+    # Redis 群组数据缓存更新
164
+    group_info = set_group_info(group)
165
+
166
+    return response(200, 'Update Group Success', u'群组更新成功', {
167
+        'group_id': group_id,
168
+        'group': group_info,
169
+        'users': get_group_users_info(group_id, admin_id),
170
+    })
171
+
172
+
173
+@logit
174
+def tg_group_close_api(request):
175
+    """
176
+    导游团关闭
177
+    :param request:
178
+    :return:
179
+    """
180
+    group_id = request.POST.get('group_id', '')
181
+    admin_id = request.POST.get('admin_id', '') or request.POST.get('user_id', '')
182
+
183
+    # 群组校验
184
+    try:
185
+        group = GroupInfo.objects.get(group_id=group_id)
186
+    except GroupInfo.DoesNotExist:
187
+        return response(GroupStatusCode.GROUP_NOT_FOUND)
188
+
189
+    # 权限校验
190
+    if group.admin_id != admin_id:
191
+        return response(GroupStatusCode.NO_CLOSE_PERMISSION)
192
+
193
+    # 群组解锁
194
+    group.group_closed = True
195
+    group.closed_at = tc.utc_datetime()
196
+    group.save()
197
+
198
+    # Redis 群组数据缓存更新
199
+    set_group_info(group)
200
+
201
+    return response(200, u'Close Tour Guide Group Success', u'导游团关闭成功')
202
+
203
+
204
+@logit
205
+def tg_group_gather_start_api(request):
206
+    """
207
+    导游团设置集合时间和地点
208
+    :param request:
209
+    :return:
210
+    """
211
+    group_id = request.POST.get('group_id', '')
212
+    admin_id = request.POST.get('admin_id', '') or request.POST.get('user_id', '')
213
+    gather_at = request.POST.get('gather_at', '')
214
+    gather_lon = request.POST.get('lon', '')  # 经度
215
+    gather_lat = request.POST.get('lat', '')  # 纬度
216
+
217
+    # 群组校验
218
+    try:
219
+        group = GroupInfo.objects.get(group_id=group_id)
220
+    except GroupInfo.DoesNotExist:
221
+        return response(GroupStatusCode.GROUP_NOT_FOUND)
222
+
223
+    # 权限校验
224
+    if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists():
225
+        return response(GroupStatusCode.NO_UPDATE_PERMISSION)
226
+
227
+    # 集合信息设置
228
+    group.gather_at = gather_at
229
+    group.gather_lon = gather_lon
230
+    group.gather_lat = gather_lat
231
+    group.save()
232
+
233
+    # Redis 群组数据缓存更新
234
+    set_group_info(group)
235
+
236
+    # 更新Session
237
+    r.set(TOUR_GUIDE_GROUP_CUR_SESSION, shortuuid.uuid())
238
+
239
+    return response(200, u'Set Tour Guide Group Gather Info Success', u'设置导游团集合信息成功')

+ 184 - 0
group/tourguidegroupuser_views.py

@@ -0,0 +1,184 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from __future__ import division
4
+
5
+import json
6
+
7
+from django.conf import settings
8
+from logit import logit
9
+from TimeConvert import TimeConvert as tc
10
+
11
+from account.models import UserInfo
12
+from group.models import GroupInfo, GroupUserInfo
13
+from utils.error.errno_utils import GroupStatusCode, GroupUserStatusCode, UserStatusCode
14
+from utils.error.response_utils import response
15
+from utils.group_photo_utils import get_current_photos
16
+from utils.redis.rgroup import get_group_info, get_group_users_info, set_group_users_info
17
+from utils.redis.rkeys import (GROUP_LAST_PHOTO_PK, GROUP_USERS_DELETED_SET, GROUP_USERS_PASSED_SET,
18
+                               GROUP_USERS_QUIT_SET, GROUP_USERS_REFUSED_SET, TOUR_GUIDE_GROUP_CUR_SESSION,
19
+                               TOUR_GUIDE_GROUP_GEO_INFO, TOUR_GUIDE_GROUP_USER_GEO_LIST)
20
+from utils.redis.rtourguide import get_tour_guide_own_group
21
+
22
+
23
+r = settings.REDIS_CACHE
24
+
25
+
26
+@logit
27
+def tgu_group_user_join_api(request):
28
+    """
29
+    导游团用户加群
30
+    :param request:
31
+    :return:
32
+    """
33
+    admin_id = request.POST.get('admin_id', '')  # 导游唯一标识,识别二维码获取
34
+    user_id = request.POST.get('user_id', '')
35
+    nickname = request.POST.get('nickname', '')
36
+
37
+    # 获取导游团唯一标识
38
+    group_id = get_tour_guide_own_group(admin_id)
39
+
40
+    # 用户校验
41
+    try:
42
+        user = UserInfo.objects.get(user_id=user_id)
43
+    except UserInfo.DoesNotExist:
44
+        return response(UserStatusCode.USER_NOT_FOUND)
45
+
46
+    # 群组校验
47
+    try:
48
+        group = GroupInfo.objects.get(group_id=group_id)
49
+    except GroupInfo.DoesNotExist:
50
+        return response(GroupStatusCode.GROUP_NOT_FOUND)
51
+
52
+    # 群组锁定校验
53
+    if group.group_lock:
54
+        return response(GroupStatusCode.GROUP_HAS_LOCKED)
55
+
56
+    # 群组用户记录创建,若记录不存在,则创建,若记录已存在,则更新
57
+    group_user, created = GroupUserInfo.objects.get_or_create(
58
+        group_id=group_id,
59
+        user_id=user_id,
60
+    )
61
+    if group_user.user_status != GroupUserInfo.PASSED:
62
+        group_user.current_id = -1 if group.group_from == GroupInfo.SESSION_GROUP else int(
63
+            r.get(GROUP_LAST_PHOTO_PK % group_id) or -1)
64
+        group_user.nickname = nickname or user.final_nickname
65
+        group_user.avatar = user.avatar
66
+        # group_user.admin = False  # Admin Field Default False, Should Not Assign
67
+        group_user.user_status = GroupUserInfo.PASSED
68
+        group_user.passed_at = tc.utc_datetime()
69
+        group_user.save()
70
+
71
+    # Redis 群组用户数据缓存
72
+    set_group_users_info(group)
73
+
74
+    # Redis 群组通过集合缓存
75
+    r.srem(GROUP_USERS_REFUSED_SET % group_id, user_id)
76
+    r.srem(GROUP_USERS_DELETED_SET % group_id, user_id)
77
+    r.srem(GROUP_USERS_QUIT_SET % group_id, user_id)
78
+    r.sadd(GROUP_USERS_PASSED_SET % group_id, user_id)
79
+
80
+    curinfo = get_current_photos(group_id, user_id, group_user.current_id)
81
+
82
+    return response(200, 'Apply Success', u'申请成功', {
83
+        'current_id': curinfo.get('current_id', ''),
84
+        'photos': curinfo.get('photos', ''),
85
+        'group_id': group_id,
86
+        'group': get_group_info(group_id),
87
+        'user_id': user_id,
88
+        'users': get_group_users_info(group_id, user_id),
89
+    })
90
+
91
+
92
+@logit
93
+def tgu_group_user_update_api(request):
94
+    """
95
+    导游团用户更新
96
+    :param request:
97
+    :return:
98
+    """
99
+    group_id = request.POST.get('group_id', '')
100
+    admin_id = request.POST.get('admin_id', '')  # 导游唯一标识
101
+    user_id = request.POST.get('user_id', '')
102
+
103
+    name = request.POST.get('name', '')
104
+    phone = request.POST.get('phone', '')
105
+    relative_person = request.POST.get('relative_person', '')
106
+    remark = request.POST.get('remark', '')
107
+
108
+    # 群组校验
109
+    try:
110
+        group = GroupInfo.objects.get(group_id=group_id)
111
+    except GroupInfo.DoesNotExist:
112
+        return response(GroupStatusCode.GROUP_NOT_FOUND)
113
+
114
+    # 权限校验
115
+    if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists():
116
+        return response(GroupStatusCode.NO_UPDATE_PERMISSION)
117
+
118
+    try:
119
+        group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, status=True)
120
+    except GroupUserInfo.DoesNotExist:
121
+        return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND)
122
+
123
+    # 用户信息更新
124
+    # TODO: Whether sync name and phone to UserInfo or not?
125
+    group_user.name = name
126
+    group_user.phone = phone
127
+    group_user.relative_person = relative_person
128
+    group_user.remark = remark
129
+    group_user.save()
130
+
131
+    # Redis 群组用户数据缓存
132
+    group_users = set_group_users_info(group)
133
+
134
+    return response(200, 'Update Group Success', u'群组更新成功', {
135
+        'group_id': group_id,
136
+        'group': group.data,
137
+        'users': group_users,
138
+    })
139
+
140
+
141
+@logit
142
+def tgu_group_user_locations_api(request):
143
+    """
144
+    导游团所有用户位置信息
145
+    :param request:
146
+    :return:
147
+    """
148
+    group_id = request.POST.get('group_id', '')
149
+    admin_id = request.POST.get('admin_id', '')  # 导游唯一标识
150
+
151
+    # 权限校验
152
+    if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists():
153
+        return response(GroupStatusCode.NO_LOCATION_PERMISSION)
154
+
155
+    return response(200, 'Get Tour Guide Group All User Location Success', u'获取导游团用户地理位置信息成功', {
156
+        'group_id': group_id,
157
+        'locations': r.georadius(TOUR_GUIDE_GROUP_GEO_INFO % group_id, 0, 0, '+inf', unit='m', withdist=True, withcoord=True, sort='ASC')
158
+        # 'locations': [['x', 0.33, (2.68220901489e-06, 1.26736058093e-06)]]
159
+    })
160
+
161
+
162
+@logit
163
+def tgu_group_user_location_api(request):
164
+    """
165
+    导游团单个用户位置信息
166
+    :param request:
167
+    :return:
168
+    """
169
+    group_id = request.POST.get('group_id', '')
170
+    admin_id = request.POST.get('admin_id', '')  # 导游唯一标识
171
+    user_id = request.POST.get('user_id', '')
172
+
173
+    # 权限校验
174
+    if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists():
175
+        return response(GroupStatusCode.NO_LOCATION_PERMISSION)
176
+
177
+    session_id = r.get(TOUR_GUIDE_GROUP_CUR_SESSION % group_id)
178
+    locations = r.lrange(TOUR_GUIDE_GROUP_USER_GEO_LIST % (group_id, session_id, user_id), 0, -1)
179
+
180
+    return response(200, 'Get Tour Guide Group User Location Success', u'获取导游团用户地理位置信息成功', {
181
+        'group_id': group_id,
182
+        'user_id': user_id,
183
+        'locations': [json.loads(loc) for loc in locations]
184
+    })

+ 1 - 0
pai2/settings.py

@@ -46,6 +46,7 @@ INSTALLED_APPS = (
46 46
     'django_q',
47 47
     'api',
48 48
     'account',
49
+    'geo',
49 50
     'group',
50 51
     'message',
51 52
     'operation',

+ 19 - 11
utils/error/errno_utils.py

@@ -54,26 +54,34 @@ class PhotoStatusCode(BaseStatusCode):
54 54
 
55 55
 
56 56
 class GroupStatusCode(BaseStatusCode):
57
-    """ 群组相关错误码 4020xx """
57
+    """ 群组/团相关错误码 4020xx """
58 58
     GROUP_NOT_FOUND = StatusCodeField(402001, u'Group Not Found', description=u'群组不存在')
59 59
     GROUP_HAS_LOCKED = StatusCodeField(402002, u'Group Has Locked', description=u'群组已锁定')
60 60
     NOT_GROUP_ADMIN = StatusCodeField(402003, u'Not Group Admin', description=u'非群组管理员')
61
-    NO_UPDATE_PERMISSION = StatusCodeField(402004, u'No Update Permission', description=u'没有更新权限')
62
-    NO_LOCK_PERMISSION = StatusCodeField(402005, u'No Lock Permission', description=u'没有锁定权限')
63
-    NO_UNLOCK_PERMISSION = StatusCodeField(402006, u'No Unlock Permission', description=u'没有解锁权限')
64
-    NO_REMOVE_PERMISSION = StatusCodeField(402007, u'No Remove Permission', description=u'没有移除权限')
65
-    NO_QUIT_PERMISSION = StatusCodeField(402008, u'No Quit Permission', description=u'没有退出权限')
66
-    NO_PASS_PERMISSION = StatusCodeField(402009, u'No Pass Permission', description=u'没有通过权限')
67
-    NO_REFUSE_PERMISSION = StatusCodeField(402010, u'No Refuse Permission', description=u'没有拒绝权限')
68
-    DUPLICATE_JOIN_REQUEST = StatusCodeField(402011, u'Duplicate Join Request', description=u'重复加群申请')
69
-    JOIN_REQUEST_NOT_FOUND = StatusCodeField(402012, u'Join Request Not Found', description=u'加群申请不存在')
61
+
62
+    NO_UPDATE_PERMISSION = StatusCodeField(402010, u'No Update Permission', description=u'没有更新权限')
63
+    NO_LOCK_PERMISSION = StatusCodeField(402011, u'No Lock Permission', description=u'没有锁定权限')
64
+    NO_UNLOCK_PERMISSION = StatusCodeField(402012, u'No Unlock Permission', description=u'没有解锁权限')
65
+    NO_REMOVE_PERMISSION = StatusCodeField(402013, u'No Remove Permission', description=u'没有移除权限')
66
+    NO_QUIT_PERMISSION = StatusCodeField(402014, u'No Quit Permission', description=u'没有退出权限')
67
+    NO_PASS_PERMISSION = StatusCodeField(402015, u'No Pass Permission', description=u'没有通过权限')
68
+    NO_REFUSE_PERMISSION = StatusCodeField(402016, u'No Refuse Permission', description=u'没有拒绝权限')
69
+    NO_CLOSE_PERMISSION = StatusCodeField(402017, u'No Close Permission', description=u'没有关闭权限')
70
+    NO_LOCATION_PERMISSION = StatusCodeField(402018, u'No Location Permission', description=u'没有地理位置权限')
71
+
72
+    DUPLICATE_JOIN_REQUEST = StatusCodeField(402020, u'Duplicate Join Request', description=u'重复加群申请')
73
+    JOIN_REQUEST_NOT_FOUND = StatusCodeField(402021, u'Join Request Not Found', description=u'加群申请不存在')
74
+
75
+    COULD_HAVE_ONLY_ONE_ACTIVE_GROUP = StatusCodeField(402030, u'Could Have Only One Active Group', description=u'只能创建一个活跃团')
70 76
 
71 77
 
72 78
 class GroupUserStatusCode(BaseStatusCode):
73
-    """ 群组用户相关错误码 4021xx """
79
+    """ 群组/团用户相关错误码 4021xx """
74 80
     GROUP_USER_NOT_FOUND = StatusCodeField(402101, u'Group User Not Found', description=u'群组用户不存在')
75 81
     GROUP_USER_HAS_DELETED = StatusCodeField(402102, u'Group User Has Deleted', description=u'群组用户被移除')
76 82
 
83
+    USER_HAS_NOT_JOIN_GROUP = StatusCodeField(402131, u'User Has Not Join Group', description=u'用户未加入导游团')
84
+
77 85
 
78 86
 class GroupPhotoStatusCode(BaseStatusCode):
79 87
     """ 群组照片(飞图)相关错误码 4022xx """

+ 8 - 0
utils/redis/rkeys.py

@@ -6,6 +6,14 @@ UUID_LIST = 'uuid:list'  # List, 唯一标识列表
6 6
 # 用户相关
7 7
 PROFILE_INFO = 'profile:info:%s'  # STRING,用户信息,user_id
8 8
 
9
+# 导游相关
10
+TOUR_GUIDE_GROUP_GEO_INFO = 'tour:guide:group:geo:info:%s'  # ZSET,旅游团地理位置信息,group_id
11
+TOUR_GUIDE_GROUP_CUR_SESSION = 'tour:guide:group:cur:session:%s'  # STRING,旅游团当前Session,group_id,导游设置集合时间的时候更新
12
+TOUR_GUIDE_GROUP_USER_GEO_LIST = 'tour:guide:group:user:geo:list:%s:%s:%s'  # LIST,旅游团当前用户地理位置列表,group_id、session_id、user_id
13
+
14
+TOUR_GUIDE_GROUP_USER_OWN = 'tour:guide:group:user:own:%s'  # STRING,导游当前拥有的导游团,user_id,导游创建导游团的时候更新
15
+TOUR_GUIDE_GROUP_USER_BELONG = 'tour:guide:group:user:belong:%s'  # STRING,用户当前所属导游团,user_id,用户加入导游团的时候更新
16
+
9 17
 # 群组相关
10 18
 GROUP_INFO = 'group:info:%s'  # STRING,群组信息,group_id
11 19
 

+ 21 - 0
utils/redis/rtourguide.py

@@ -0,0 +1,21 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.conf import settings
4
+
5
+from utils.redis.rkeys import TOUR_GUIDE_GROUP_USER_OWN
6
+
7
+
8
+r = settings.REDIS_CACHE
9
+
10
+
11
+# 导游相关
12
+
13
+
14
+def set_tour_guide_own_group(user_id, group_id):
15
+    """ 设置导游拥有的导游团 """
16
+    r.set(TOUR_GUIDE_GROUP_USER_OWN % user_id, group_id)
17
+
18
+
19
+def get_tour_guide_own_group(user_id):
20
+    """ 获取导游拥有的导游团 """
21
+    return r.get(TOUR_GUIDE_GROUP_USER_OWN % user_id)