@@ -29,7 +29,8 @@ urlpatterns += [  | 
            ||
| 29 | 29 | 
                url(r'^g/join$', group_views.group_join_api, name='group_join_api'), # 申请加群  | 
            
| 30 | 30 | 
                url(r'^g/lock$', group_views.group_lock_api, name='group_lock_api'), # 群组锁定  | 
            
| 31 | 31 | 
                url(r'^g/unlock$', group_views.group_unlock_api, name='group_unlock_api'), # 群组解锁  | 
            
| 32 | 
                - url(r'^g/remove$', group_views.group_remove_api, name='group_remove_api'), # 成员移除  | 
            |
| 32 | 
                + url(r'^g/remove$', group_views.group_remove_api, name='group_remove_api'), # 成员移除,管理员主动,群成员被动  | 
            |
| 33 | 
                + url(r'^g/quit$', group_views.group_quit_api, name='group_quit_api'), # 成员退出,群成员主动  | 
            |
| 33 | 34 | 
                # url(r'^g/pass$', group_views.group_pass_api, name='group_pass_api'), # 申请通过  | 
            
| 34 | 35 | 
                # url(r'^g/refuse$', group_views.group_refuse_api, name='group_refuse_api'), # 申请拒绝  | 
            
| 35 | 36 | 
                ]  | 
            
                @@ -0,0 +1,24 @@  | 
            ||
| 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', '0015_groupphotoinfo_avatar'),
               | 
            |
| 11 | 
                + ]  | 
            |
| 12 | 
                +  | 
            |
| 13 | 
                + operations = [  | 
            |
| 14 | 
                + migrations.AddField(  | 
            |
| 15 | 
                + model_name='groupuserinfo',  | 
            |
| 16 | 
                + name='quit_at',  | 
            |
| 17 | 
                + field=models.DateTimeField(help_text='\u9000\u51fa\u65f6\u95f4', null=True, verbose_name='quit_at', blank=True),  | 
            |
| 18 | 
                + ),  | 
            |
| 19 | 
                + migrations.AlterField(  | 
            |
| 20 | 
                + model_name='groupuserinfo',  | 
            |
| 21 | 
                + name='user_status',  | 
            |
| 22 | 
                + field=models.IntegerField(default=0, verbose_name='user_status', choices=[(0, '\u7533\u8bf7\u4e2d'), (1, '\u5df2\u901a\u8fc7'), (2, '\u5df2\u62d2\u7edd'), (3, '\u5df2\u5220\u9664'), (4, '\u5df2\u9000\u51fa')]),  | 
            |
| 23 | 
                + ),  | 
            |
| 24 | 
                + ]  | 
            
                @@ -83,12 +83,14 @@ class GroupUserInfo(CreateUpdateMixin):  | 
            ||
| 83 | 83 | 
                PASSED = 1  | 
            
| 84 | 84 | 
                REFUSED = 2  | 
            
| 85 | 85 | 
                DELETED = 3  | 
            
| 86 | 
                + QUIT = 4  | 
            |
| 86 | 87 | 
                 | 
            
| 87 | 88 | 
                USER_STATUS = (  | 
            
| 88 | 89 | 
                (APPLYING, u'申请中'),  | 
            
| 89 | 90 | 
                (PASSED, u'已通过'),  | 
            
| 90 | 91 | 
                (REFUSED, u'已拒绝'),  | 
            
| 91 | 
                - (DELETED, u'已删除')  | 
            |
| 92 | 
                + (DELETED, u'已删除'),  | 
            |
| 93 | 
                + (QUIT, u'已退出'),  | 
            |
| 92 | 94 | 
                )  | 
            
| 93 | 95 | 
                 | 
            
| 94 | 96 | 
                group_id = models.CharField(_(u'group_id'), max_length=255, blank=True, null=True, help_text=u'群组唯一标识', db_index=True)  | 
            
                @@ -101,6 +103,7 @@ class GroupUserInfo(CreateUpdateMixin):  | 
            ||
| 101 | 103 | 
                passed_at = models.DateTimeField(_(u'passed_at'), blank=True, null=True, help_text=_(u'通过时间'))  | 
            
| 102 | 104 | 
                refused_at = models.DateTimeField(_(u'refused_at'), blank=True, null=True, help_text=_(u'拒绝时间'))  | 
            
| 103 | 105 | 
                deleted_at = models.DateTimeField(_(u'deleted_at'), blank=True, null=True, help_text=_(u'删除时间'))  | 
            
| 106 | 
                + quit_at = models.DateTimeField(_(u'quit_at'), blank=True, null=True, help_text=_(u'退出时间'))  | 
            |
| 104 | 107 | 
                 | 
            
| 105 | 108 | 
                class Meta:  | 
            
| 106 | 109 | 
                verbose_name = _(u'groupuserinfo')  | 
            
                @@ -20,7 +20,10 @@ from utils.url_utils import img_url  | 
            ||
| 20 | 20 | 
                from utils.error.errno_utils import UserStatusCode, GroupStatusCode, GroupUserStatusCode, GroupPhotoStatusCode  | 
            
| 21 | 21 | 
                from utils.error.response_utils import response  | 
            
| 22 | 22 | 
                 | 
            
| 23 | 
                -from utils.redis.rkeys import GROUP_USERS_APPLYING_SET, GROUP_USERS_PASSED_SET, GROUP_USERS_REFUSED_SET, GROUP_USERS_DELETED_SET  | 
            |
| 23 | 
                +from utils.redis.rkeys import (  | 
            |
| 24 | 
                + GROUP_USERS_APPLYING_SET, GROUP_USERS_PASSED_SET, GROUP_USERS_REFUSED_SET, GROUP_USERS_DELETED_SET,  | 
            |
| 25 | 
                + GROUP_USERS_QUIT_SET,  | 
            |
| 26 | 
                +)  | 
            |
| 24 | 27 | 
                from utils.redis.rkeys import GROUP_LAST_PHOTO_PK  | 
            
| 25 | 28 | 
                from utils.redis.rgroup import set_group_info, get_group_info, set_group_users_info, get_group_users_info  | 
            
| 26 | 29 | 
                 | 
            
                @@ -217,7 +220,8 @@ def group_join_api(request):  | 
            ||
| 217 | 220 | 
                return response(GroupStatusCode.GROUP_HAS_LOCKED)  | 
            
| 218 | 221 | 
                 | 
            
| 219 | 222 | 
                # 重复申请校验  | 
            
| 220 | 
                - if r.sismember(GROUP_USERS_APPLYING_SET % group_id, user_id) or r.sismember(GROUP_USERS_PASSED_SET % group_id, user_id):  | 
            |
| 223 | 
                + if (r.sismember(GROUP_USERS_APPLYING_SET % group_id, user_id) or  | 
            |
| 224 | 
                + r.sismember(GROUP_USERS_PASSED_SET % group_id, user_id)):  | 
            |
| 221 | 225 | 
                return response(GroupStatusCode.DUPLICATE_JOIN_REQUEST)  | 
            
| 222 | 226 | 
                 | 
            
| 223 | 227 | 
                # 群组用户记录创建  | 
            
                @@ -236,6 +240,9 @@ def group_join_api(request):  | 
            ||
| 236 | 240 | 
                set_group_users_info(group)  | 
            
| 237 | 241 | 
                 | 
            
| 238 | 242 | 
                # Redis 群组通过集合缓存  | 
            
| 243 | 
                + r.srem(GROUP_USERS_REFUSED_SET % group_id, user_id)  | 
            |
| 244 | 
                + r.srem(GROUP_USERS_DELETED_SET % group_id, user_id)  | 
            |
| 245 | 
                + r.srem(GROUP_USERS_QUIT_SET % group_id, user_id)  | 
            |
| 239 | 246 | 
                r.sadd(GROUP_USERS_PASSED_SET % group_id, user_id)  | 
            
| 240 | 247 | 
                 | 
            
| 241 | 248 | 
                     return JsonResponse({
               | 
            
                @@ -330,7 +337,7 @@ def group_remove_api(request):  | 
            ||
| 330 | 337 | 
                return response(GroupStatusCode.GROUP_NOT_FOUND)  | 
            
| 331 | 338 | 
                 | 
            
| 332 | 339 | 
                # 权限校验  | 
            
| 333 | 
                - if group.admin_id != admin_id and group_id.admin_id == user_id: # 管理员也不允许将自己移除  | 
            |
| 340 | 
                + if group.admin_id != admin_id or group.admin_id == user_id: # 管理员也不允许将自己移除  | 
            |
| 334 | 341 | 
                return response(GroupStatusCode.NO_REMOVE_PERMISSION)  | 
            
| 335 | 342 | 
                 | 
            
| 336 | 343 | 
                # 群组用户校验  | 
            
                @@ -361,6 +368,53 @@ def group_remove_api(request):  | 
            ||
| 361 | 368 | 
                })  | 
            
| 362 | 369 | 
                 | 
            
| 363 | 370 | 
                 | 
            
| 371 | 
                +def group_quit_api(request):  | 
            |
| 372 | 
                + """  | 
            |
| 373 | 
                + 成员退出  | 
            |
| 374 | 
                + :param request:  | 
            |
| 375 | 
                + :return:  | 
            |
| 376 | 
                + """  | 
            |
| 377 | 
                +    group_id = request.POST.get('group_id', '')
               | 
            |
| 378 | 
                +    user_id = request.POST.get('user_id', '')
               | 
            |
| 379 | 
                +  | 
            |
| 380 | 
                + # 群组校验  | 
            |
| 381 | 
                + try:  | 
            |
| 382 | 
                + group = GroupInfo.objects.get(group_id=group_id)  | 
            |
| 383 | 
                + except GroupInfo.DoesNotExist:  | 
            |
| 384 | 
                + return response(GroupStatusCode.GROUP_NOT_FOUND)  | 
            |
| 385 | 
                +  | 
            |
| 386 | 
                + # 权限校验  | 
            |
| 387 | 
                + if group.admin_id == user_id: # 管理员也不允许自己退出  | 
            |
| 388 | 
                + return response(GroupStatusCode.NO_QUIT_PERMISSION)  | 
            |
| 389 | 
                +  | 
            |
| 390 | 
                + # 群组用户校验  | 
            |
| 391 | 
                + try:  | 
            |
| 392 | 
                + group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, user_status=GroupUserInfo.PASSED)  | 
            |
| 393 | 
                + except GroupUserInfo.DoesNotExist:  | 
            |
| 394 | 
                + return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND)  | 
            |
| 395 | 
                +  | 
            |
| 396 | 
                + # 群组用户移除  | 
            |
| 397 | 
                + group_user.user_status = GroupUserInfo.QUIT  | 
            |
| 398 | 
                + group_user.quit_at = tc.utc_datetime()  | 
            |
| 399 | 
                + group_user.save()  | 
            |
| 400 | 
                +  | 
            |
| 401 | 
                + # Redis 群组数据缓存更新  | 
            |
| 402 | 
                + group_users = set_group_info(group)  | 
            |
| 403 | 
                +  | 
            |
| 404 | 
                + # Redis 群组删除集合缓存  | 
            |
| 405 | 
                + r.srem(GROUP_USERS_PASSED_SET % group_id, user_id)  | 
            |
| 406 | 
                + r.sadd(GROUP_USERS_QUIT_SET % group_id, user_id)  | 
            |
| 407 | 
                +  | 
            |
| 408 | 
                +    return JsonResponse({
               | 
            |
| 409 | 
                + 'status': 200,  | 
            |
| 410 | 
                + 'message': u'用户退出成功',  | 
            |
| 411 | 
                +        'data': {
               | 
            |
| 412 | 
                + 'group_id': group_id,  | 
            |
| 413 | 
                + 'users': group_users,  | 
            |
| 414 | 
                + },  | 
            |
| 415 | 
                + })  | 
            |
| 416 | 
                +  | 
            |
| 417 | 
                +  | 
            |
| 364 | 418 | 
                def group_pass_api(request):  | 
            
| 365 | 419 | 
                """  | 
            
| 366 | 420 | 
                申请通过  | 
            
                @@ -575,7 +629,7 @@ def comment_submit_api(request):  | 
            ||
| 575 | 629 | 
                group_photo.comment_num += 1  | 
            
| 576 | 630 | 
                group_photo.save()  | 
            
| 577 | 631 | 
                 | 
            
| 578 | 
                - # 判断群组照片发布者是否已经被管理员移除,如若移除,则不给发布者提醒  | 
            |
| 632 | 
                + # 判断群组照片发布者是否已经被管理员移除/主动退出,如若移除/退出,则不给发布者提醒  | 
            |
| 579 | 633 | 
                if r.sismember(GROUP_USERS_PASSED_SET % group_photo.group_id, group_photo.user_id):  | 
            
| 580 | 634 | 
                UserMessageInfo.objects.create(  | 
            
| 581 | 635 | 
                from_uid=user_id,  | 
            
                @@ -639,7 +693,7 @@ def thumbup_submit_api(request):  | 
            ||
| 639 | 693 | 
                group_photo.thumbup_num += 1  | 
            
| 640 | 694 | 
                group_photo.save()  | 
            
| 641 | 695 | 
                 | 
            
| 642 | 
                - # 判断群组照片发布者是否已经被管理员移除,如若移除,则不给发布者提醒  | 
            |
| 696 | 
                + # 判断群组照片发布者是否已经被管理员移除/主动退出,如若移除/退出,则不给发布者提醒  | 
            |
| 643 | 697 | 
                if r.sismember(GROUP_USERS_PASSED_SET % group_photo.group_id, group_photo.user_id):  | 
            
| 644 | 698 | 
                UserMessageInfo.objects.create(  | 
            
| 645 | 699 | 
                from_uid=user_id,  | 
            
                @@ -736,7 +790,7 @@ def thumbup_cancel_api(request):  | 
            ||
| 736 | 790 | 
                group_photo.thumbup_num -= 1  | 
            
| 737 | 791 | 
                group_photo.save()  | 
            
| 738 | 792 | 
                 | 
            
| 739 | 
                - # 判断群组照片发布者是否已经被管理员移除,如若移除,则不给发布者提醒  | 
            |
| 793 | 
                + # 判断群组照片发布者是否已经被管理员移除/主动退出,如若移除/退出,则不给发布者提醒  | 
            |
| 740 | 794 | 
                if r.sismember(GROUP_USERS_PASSED_SET % group_photo.group_id, group_photo.user_id):  | 
            
| 741 | 795 | 
                UserMessageInfo.objects.create(  | 
            
| 742 | 796 | 
                from_uid=user_id,  | 
            
                @@ -41,10 +41,11 @@ class GroupStatusCode(BaseStatusCode):  | 
            ||
| 41 | 41 | 
                NO_LOCK_PERMISSION = StatusCodeField(402005, u'No Lock Permission', description=u'没有锁定权限')  | 
            
| 42 | 42 | 
                NO_UNLOCK_PERMISSION = StatusCodeField(402006, u'No Unlock Permission', description=u'没有解锁权限')  | 
            
| 43 | 43 | 
                NO_REMOVE_PERMISSION = StatusCodeField(402007, u'No Remove Permission', description=u'没有移除权限')  | 
            
| 44 | 
                - NO_PASS_PERMISSION = StatusCodeField(402008, u'No Pass Permission', description=u'没有通过权限')  | 
            |
| 45 | 
                - NO_REFUSE_PERMISSION = StatusCodeField(402009, u'No Refuse Permission', description=u'没有拒绝权限')  | 
            |
| 46 | 
                - DUPLICATE_JOIN_REQUEST = StatusCodeField(402010, u'Duplicate Join Request', description=u'重复加群申请')  | 
            |
| 47 | 
                - JOIN_REQUEST_NOT_FOUND = StatusCodeField(402011, u'Join Request Not Found', description=u'加群申请不存在')  | 
            |
| 44 | 
                + NO_QUIT_PERMISSION = StatusCodeField(402008, u'No Quit Permission', description=u'没有退出权限')  | 
            |
| 45 | 
                + NO_PASS_PERMISSION = StatusCodeField(402009, u'No Pass Permission', description=u'没有通过权限')  | 
            |
| 46 | 
                + NO_REFUSE_PERMISSION = StatusCodeField(402010, u'No Refuse Permission', description=u'没有拒绝权限')  | 
            |
| 47 | 
                + DUPLICATE_JOIN_REQUEST = StatusCodeField(402011, u'Duplicate Join Request', description=u'重复加群申请')  | 
            |
| 48 | 
                + JOIN_REQUEST_NOT_FOUND = StatusCodeField(402012, u'Join Request Not Found', description=u'加群申请不存在')  | 
            |
| 48 | 49 | 
                 | 
            
| 49 | 50 | 
                 | 
            
| 50 | 51 | 
                class GroupUserStatusCode(BaseStatusCode):  | 
            
                @@ -12,6 +12,7 @@ GROUP_USERS_APPLYING_SET = 'group:users:applying:set:%s' # SET,群组用户  | 
            ||
| 12 | 12 | 
                GROUP_USERS_PASSED_SET = 'group:users:passed:set:%s' # SET,群组用户通过集合,group_id  | 
            
| 13 | 13 | 
                GROUP_USERS_REFUSED_SET = 'group:users:refused:set:%s' # SET,群组用户拒绝集合,group_id  | 
            
| 14 | 14 | 
                GROUP_USERS_DELETED_SET = 'group:users:deleted:set:%s' # SET,群组用户移除集合,group_id  | 
            
| 15 | 
                +GROUP_USERS_QUIT_SET = 'group:users:quit:set:%s' # SET,群组用户退出集合,group_id  | 
            |
| 15 | 16 | 
                 | 
            
| 16 | 17 | 
                # 群组照片相关  | 
            
| 17 | 18 | 
                GROUP_LAST_PHOTO_PK = 'group:last:photo:pk:%s' # STRING,群组最后一张照片PK,group_id  | 
            
                @@ -1,10 +1,14 @@  | 
            ||
| 1 | 1 | 
                # -*- coding: utf-8 -*-  | 
            
| 2 | 2 | 
                 | 
            
| 3 | 
                -PAI2_HOME_API = r"select " \  | 
            |
| 4 | 
                - r"T1.group_id, T2.group_name, T2.group_default_avatar, T2.group_avatar, T3.id, T3.photo_path, T3.photo_w, T3.photo_h, T3.photo_thumbnail_path, T3.photo_thumbnail_w, T3.photo_thumbnail_h, T3.user_id, T3.nickname, T3.avatar, T3.comment_num, T3.thumbup_num, T3.created_at " \  | 
            |
| 5 | 
                -                r"from (select * from group_groupuserinfo where user_id='{user_id}' and user_status=1) as T1 " \
               | 
            |
| 6 | 
                - r"left outer join group_groupinfo as T2 on T1.group_id = T2.group_id " \  | 
            |
| 7 | 
                - r"left outer join group_groupphotoinfo as T3 on T1.group_id = T3.group_id and T3.id > T1.current_id " \  | 
            |
| 8 | 
                - r"where T3.status = 1 " \  | 
            |
| 9 | 
                - r"order by DATE(T3.created_at) desc, T3.thumbup_num desc " \  | 
            |
| 10 | 
                -                r"limit {offset}, {rows};"
               | 
            |
| 3 | 
                +PAI2_HOME_API = (  | 
            |
| 4 | 
                + r"select "  | 
            |
| 5 | 
                + r"T1.group_id, T2.group_name, T2.group_default_avatar, T2.group_avatar, T3.id, T3.photo_path, T3.photo_w, "  | 
            |
| 6 | 
                + r"T3.photo_h, T3.photo_thumbnail_path, T3.photo_thumbnail_w, T3.photo_thumbnail_h, T3.user_id, T3.nickname, "  | 
            |
| 7 | 
                + r"T3.avatar, T3.comment_num, T3.thumbup_num, T3.created_at "  | 
            |
| 8 | 
                +    r"from (select * from group_groupuserinfo where user_id='{user_id}' and user_status=1) as T1 "
               | 
            |
| 9 | 
                + r"left outer join group_groupinfo as T2 on T1.group_id = T2.group_id "  | 
            |
| 10 | 
                + r"left outer join group_groupphotoinfo as T3 on T1.group_id = T3.group_id and T3.id > T1.current_id "  | 
            |
| 11 | 
                + r"where T3.status = 1 "  | 
            |
| 12 | 
                + r"order by DATE(T3.created_at) desc, T3.thumbup_num desc "  | 
            |
| 13 | 
                +    r"limit {offset}, {rows};"
               | 
            |
| 14 | 
                +)  |