From 00deeb711b3a0d5b7ab83b300a606e8df70e8801 Mon Sep 17 00:00:00 2001 From: wanjia Date: Mon, 17 Mar 2025 16:46:37 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=A8=E6=88=B7=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data_backup.json | Bin 0 -> 32184 bytes role_based_system/urls.py | 10 +- user_management/migrations/0001_initial.py | 156 +++----- user_management/views.py | 433 ++++++++++++++++++++- 4 files changed, 498 insertions(+), 101 deletions(-) create mode 100644 data_backup.json diff --git a/data_backup.json b/data_backup.json new file mode 100644 index 0000000000000000000000000000000000000000..368f99f29df2764e372a7080258ccf53215cb47b GIT binary patch literal 32184 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 bzJ2P=vUTo^Uf1}_T@Ge#h^>E3eO3J*YAGM> literal 0 HcmV?d00001 diff --git a/role_based_system/urls.py b/role_based_system/urls.py index 6bb0d406..2f510785 100644 --- a/role_based_system/urls.py +++ b/role_based_system/urls.py @@ -34,8 +34,8 @@ urlpatterns = [ ] # 添加调试工具栏(仅在DEBUG模式下) -if settings.DEBUG: - import debug_toolbar - urlpatterns = [ - path('__debug__/', include(debug_toolbar.urls)), - ] + urlpatterns +# if settings.DEBUG: +# import debug_toolbar +# urlpatterns = [ +# path('__debug__/', include(debug_toolbar.urls)), +# ] + urlpatterns diff --git a/user_management/migrations/0001_initial.py b/user_management/migrations/0001_initial.py index cd1103fd..c306a64f 100644 --- a/user_management/migrations/0001_initial.py +++ b/user_management/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.5 on 2025-02-26 09:23 +# Generated by Django 5.1.5 on 2025-03-17 05:47 import django.contrib.auth.models import django.contrib.auth.validators @@ -73,79 +73,22 @@ class Migration(migrations.Migration): name='KnowledgeBase', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100, verbose_name='知识库名称')), + ('user_id', models.UUIDField(verbose_name='创建者ID')), + ('name', models.CharField(max_length=100, unique=True, verbose_name='知识库名称')), ('desc', models.TextField(blank=True, null=True, verbose_name='知识库描述')), ('type', models.CharField(choices=[('admin', '管理级知识库'), ('leader', '部门级知识库'), ('member', '成员级知识库'), ('private', '私有知识库'), ('secret', '公司级别私密知识库')], default='private', max_length=20, verbose_name='知识库类型')), ('department', models.CharField(blank=True, max_length=50, null=True)), ('group', models.CharField(blank=True, max_length=50, null=True)), - ('user_id', models.CharField(max_length=50, verbose_name='创建者ID')), ('documents', models.JSONField(default=list)), ('char_length', models.IntegerField(default=0)), ('document_count', models.IntegerField(default=0)), ('external_id', models.UUIDField(blank=True, null=True)), ('create_time', models.DateTimeField(auto_now_add=True)), ('update_time', models.DateTimeField(auto_now=True)), - ('owners', models.ManyToManyField(related_name='owned_knowledge_bases', to=settings.AUTH_USER_MODEL, verbose_name='所有者')), ], options={ 'db_table': 'knowledge_bases', - }, - ), - migrations.CreateModel( - name='ChatHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('conversation_id', models.CharField(db_index=True, max_length=100)), - ('parent_id', models.CharField(blank=True, max_length=100, null=True)), - ('role', models.CharField(choices=[('user', '用户'), ('assistant', 'AI助手'), ('system', '系统')], max_length=20)), - ('content', models.TextField()), - ('tokens', models.IntegerField(default=0, help_text='消息token数')), - ('metadata', models.JSONField(blank=True, default=dict)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('is_deleted', models.BooleanField(default=False)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user_management.knowledgebase')), - ], - options={ - 'db_table': 'chat_history', - 'ordering': ['created_at'], - }, - ), - migrations.CreateModel( - name='KnowledgeBasePermission', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('can_read', models.BooleanField(default=False, verbose_name='查看权限')), - ('can_edit', models.BooleanField(default=False, verbose_name='修改权限')), - ('can_delete', models.BooleanField(default=False, verbose_name='删除权限')), - ('status', models.CharField(choices=[('active', '生效中'), ('expired', '已过期'), ('revoked', '已撤销')], default='active', max_length=10, verbose_name='状态')), - ('granted_at', models.DateTimeField(auto_now_add=True, verbose_name='授权时间')), - ('expires_at', models.DateTimeField(blank=True, null=True, verbose_name='过期时间')), - ('granted_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='granted_permissions', to=settings.AUTH_USER_MODEL, verbose_name='授权人')), - ('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_permissions', to='user_management.knowledgebase', verbose_name='知识库')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='knowledge_base_permissions', to=settings.AUTH_USER_MODEL, verbose_name='用户')), - ], - options={ - 'verbose_name': '知识库权限', - 'verbose_name_plural': '知识库权限', - 'db_table': 'knowledge_base_permissions', - }, - ), - migrations.CreateModel( - name='Notification', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('type', models.CharField(choices=[('permission_request', '权限申请'), ('permission_approved', '权限批准'), ('permission_rejected', '权限拒绝'), ('permission_expired', '权限过期'), ('system_notice', '系统通知')], max_length=20)), - ('title', models.CharField(max_length=100)), - ('content', models.TextField()), - ('is_read', models.BooleanField(default=False)), - ('related_resource', models.CharField(blank=True, max_length=100)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_notifications', to=settings.AUTH_USER_MODEL)), - ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_notifications', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ['-created_at'], + 'indexes': [models.Index(fields=['type'], name='knowledge_b_type_0439e7_idx'), models.Index(fields=['department'], name='knowledge_b_departm_e739fd_idx'), models.Index(fields=['group'], name='knowledge_b_group_3dcf34_idx')], }, ), migrations.CreateModel( @@ -181,40 +124,65 @@ class Migration(migrations.Migration): 'db_table': 'user_profiles', }, ), - migrations.AddIndex( - model_name='knowledgebase', - index=models.Index(fields=['type'], name='knowledge_b_type_0439e7_idx'), + migrations.CreateModel( + name='ChatHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('conversation_id', models.CharField(db_index=True, max_length=100)), + ('parent_id', models.CharField(blank=True, max_length=100, null=True)), + ('role', models.CharField(choices=[('user', '用户'), ('assistant', 'AI助手'), ('system', '系统')], max_length=20)), + ('content', models.TextField()), + ('tokens', models.IntegerField(default=0, help_text='消息token数')), + ('metadata', models.JSONField(blank=True, default=dict)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('is_deleted', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user_management.knowledgebase')), + ], + options={ + 'db_table': 'chat_history', + 'ordering': ['created_at'], + 'indexes': [models.Index(fields=['conversation_id', 'created_at'], name='chat_histor_convers_33721a_idx'), models.Index(fields=['user', 'created_at'], name='chat_histor_user_id_aa050a_idx')], + }, ), - migrations.AddIndex( - model_name='knowledgebase', - index=models.Index(fields=['department'], name='knowledge_b_departm_e739fd_idx'), + migrations.CreateModel( + name='KnowledgeBasePermission', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('can_read', models.BooleanField(default=False, verbose_name='查看权限')), + ('can_edit', models.BooleanField(default=False, verbose_name='修改权限')), + ('can_delete', models.BooleanField(default=False, verbose_name='删除权限')), + ('status', models.CharField(choices=[('active', '生效中'), ('expired', '已过期'), ('revoked', '已撤销')], default='active', max_length=10, verbose_name='状态')), + ('granted_at', models.DateTimeField(auto_now_add=True, verbose_name='授权时间')), + ('expires_at', models.DateTimeField(blank=True, null=True, verbose_name='过期时间')), + ('granted_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='granted_permissions', to=settings.AUTH_USER_MODEL, verbose_name='授权人')), + ('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_permissions', to='user_management.knowledgebase', verbose_name='知识库')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='knowledge_base_permissions', to=settings.AUTH_USER_MODEL, verbose_name='用户')), + ], + options={ + 'verbose_name': '知识库权限', + 'verbose_name_plural': '知识库权限', + 'db_table': 'knowledge_base_permissions', + 'indexes': [models.Index(fields=['knowledge_base', 'user', 'status'], name='knowledge_b_knowled_88e81e_idx')], + 'unique_together': {('knowledge_base', 'user')}, + }, ), - migrations.AddIndex( - model_name='knowledgebase', - index=models.Index(fields=['group'], name='knowledge_b_group_3dcf34_idx'), - ), - migrations.AddIndex( - model_name='chathistory', - index=models.Index(fields=['conversation_id', 'created_at'], name='chat_histor_convers_33721a_idx'), - ), - migrations.AddIndex( - model_name='chathistory', - index=models.Index(fields=['user', 'created_at'], name='chat_histor_user_id_aa050a_idx'), - ), - migrations.AddIndex( - model_name='knowledgebasepermission', - index=models.Index(fields=['knowledge_base', 'user', 'status'], name='knowledge_b_knowled_88e81e_idx'), - ), - migrations.AlterUniqueTogether( - name='knowledgebasepermission', - unique_together={('knowledge_base', 'user')}, - ), - migrations.AddIndex( - model_name='notification', - index=models.Index(fields=['receiver', '-created_at'], name='user_manage_receive_fcb0eb_idx'), - ), - migrations.AddIndex( - model_name='notification', - index=models.Index(fields=['type', 'is_read'], name='user_manage_type_362052_idx'), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('type', models.CharField(choices=[('permission_request', '权限申请'), ('permission_approved', '权限批准'), ('permission_rejected', '权限拒绝'), ('permission_expired', '权限过期'), ('system_notice', '系统通知')], max_length=20)), + ('title', models.CharField(max_length=100)), + ('content', models.TextField()), + ('is_read', models.BooleanField(default=False)), + ('related_resource', models.CharField(blank=True, max_length=100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_notifications', to=settings.AUTH_USER_MODEL)), + ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_notifications', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + 'indexes': [models.Index(fields=['receiver', '-created_at'], name='user_manage_receive_fcb0eb_idx'), models.Index(fields=['type', 'is_read'], name='user_manage_type_362052_idx')], + }, ), ] diff --git a/user_management/views.py b/user_management/views.py index 3de07ca9..f2b19adf 100644 --- a/user_management/views.py +++ b/user_management/views.py @@ -42,6 +42,7 @@ from rest_framework import serializers import traceback + # 添加模型导入 from .models import ( User, @@ -1665,7 +1666,6 @@ class KnowledgeBaseViewSet(viewsets.ModelViewSet): "data": None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - class PermissionViewSet(viewsets.ModelViewSet): serializer_class = PermissionSerializer permission_classes = [IsAuthenticated] @@ -1693,10 +1693,70 @@ class PermissionViewSet(viewsets.ModelViewSet): query = Q(applicant=user) # 自己发出的申请 query |= Q(knowledge_base_id__in=managed_kb_ids) # 有管理权限的知识库的申请 + # 使用 select_related 优化查询,预加载关联的对象 return Permission.objects.filter(query).distinct().select_related( - 'knowledge_base', 'applicant', 'approver' + 'knowledge_base', # 预加载知识库信息 + 'applicant', # 预加载申请人信息 + 'approver' # 预加载审批人信息 ) + def list(self, request, *args, **kwargs): + """获取权限申请列表,包含详细信息""" + queryset = self.get_queryset() + page = self.paginate_queryset(queryset) + + if page is not None: + data = [] + for permission in page: + permission_data = { + 'id': str(permission.id), + 'knowledge_base': { + 'id': str(permission.knowledge_base.id), + 'name': permission.knowledge_base.name, + 'type': permission.knowledge_base.type, + }, + 'applicant': { + 'id': str(permission.applicant.id), + 'username': permission.applicant.username, + 'name': permission.applicant.name, + 'department': permission.applicant.department, + }, + 'permissions': permission.permissions, + 'status': permission.status, + 'created_at': permission.created_at.strftime('%Y-%m-%d %H:%M:%S'), + 'expires_at': permission.expires_at.strftime('%Y-%m-%d %H:%M:%S') if permission.expires_at else None, + } + + # 添加审批人信息(如果已审批) + if permission.approver: + permission_data['approver'] = { + 'id': str(permission.approver.id), + 'username': permission.approver.username, + 'name': permission.approver.name, + 'department': permission.approver.department, + } + permission_data['response_message'] = permission.response_message + + data.append(permission_data) + + return Response({ + 'code': 200, + 'message': '获取权限申请列表成功', + 'data': { + 'total': self.paginator.page.paginator.count, + 'results': data + } + }) + + return Response({ + 'code': 200, + 'message': '获取权限申请列表成功', + 'data': { + 'total': queryset.count(), + 'results': [] + } + }) + def perform_create(self, serializer): """创建权限申请并发送通知给知识库创建者""" # 获取知识库 @@ -2001,6 +2061,375 @@ class PermissionViewSet(viewsets.ModelViewSet): return False + @action(detail=False, methods=['get']) + def user_permissions(self, request): + """获取指定用户的所有知识库权限""" + try: + # 获取用户名参数 + username = request.query_params.get('username') + if not username: + return Response({ + 'code': 400, + 'message': '请提供用户名', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + # 获取用户 + try: + target_user = User.objects.get(username=username) + except User.DoesNotExist: + return Response({ + 'code': 404, + 'message': f'用户 {username} 不存在', + 'data': None + }, status=status.HTTP_404_NOT_FOUND) + + # 获取该用户的所有权限记录 + permissions = KBPermissionModel.objects.filter( + user=target_user, + status='active' + ).select_related('knowledge_base', 'granted_by') + + # 构建响应数据 + permissions_data = [] + for perm in permissions: + perm_data = { + 'id': str(perm.id), + 'knowledge_base': { + 'id': str(perm.knowledge_base.id), + 'name': perm.knowledge_base.name, + 'type': perm.knowledge_base.type, + 'department': perm.knowledge_base.department, + 'group': perm.knowledge_base.group + }, + 'permissions': { + 'can_read': perm.can_read, + 'can_edit': perm.can_edit, + 'can_delete': perm.can_delete + }, + 'granted_by': { + 'id': str(perm.granted_by.id) if perm.granted_by else None, + 'username': perm.granted_by.username if perm.granted_by else None, + 'name': perm.granted_by.name if perm.granted_by else None + }, + 'created_at': perm.created_at.strftime('%Y-%m-%d %H:%M:%S'), + 'expires_at': perm.expires_at.strftime('%Y-%m-%d %H:%M:%S') if perm.expires_at else None, + 'status': perm.status + } + permissions_data.append(perm_data) + + return Response({ + 'code': 200, + 'message': '获取用户权限成功', + 'data': { + 'user': { + 'id': str(target_user.id), + 'username': target_user.username, + 'name': target_user.name, + 'department': target_user.department, + 'role': target_user.role + }, + 'permissions': permissions_data + } + }) + + except Exception as e: + logger.error(f"获取用户权限失败: {str(e)}") + logger.error(traceback.format_exc()) + return Response({ + 'code': 500, + 'message': f'获取用户权限失败: {str(e)}', + 'data': None + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @action(detail=False, methods=['get']) + def all_permissions(self, request): + """管理员获取所有用户的知识库权限(不包括私有知识库)""" + try: + # 检查是否是管理员 + if request.user.role != 'admin': + return Response({ + 'code': 403, + 'message': '只有管理员可以查看所有权限', + 'data': None + }, status=status.HTTP_403_FORBIDDEN) + + # 获取查询参数 + page = int(request.query_params.get('page', 1)) + page_size = int(request.query_params.get('page_size', 10)) + status_filter = request.query_params.get('status') # active/expired + department = request.query_params.get('department') + kb_type = request.query_params.get('kb_type') # 知识库类型筛选 + + # 构建基础查询 + queryset = KBPermissionModel.objects.filter( + ~Q(knowledge_base__type='private') # 排除私有知识库 + ).select_related( + 'user', + 'knowledge_base', + 'granted_by' + ) + + # 应用过滤条件 + if status_filter == 'active': + queryset = queryset.filter( + Q(expires_at__gt=timezone.now()) | Q(expires_at__isnull=True), + status='active' + ) + elif status_filter == 'expired': + queryset = queryset.filter( + Q(expires_at__lte=timezone.now()) | Q(status='inactive') + ) + + if department: + queryset = queryset.filter(user__department=department) + + if kb_type: + queryset = queryset.filter(knowledge_base__type=kb_type) + + # 计算总数 + total = queryset.count() + + # 分页 + start = (page - 1) * page_size + end = start + page_size + permissions = queryset.order_by('-granted_at')[start:end] + + # 获取所有相关的创建者ID + creator_ids = set(perm.knowledge_base.user_id for perm in permissions) + creators = { + str(user.id): user + for user in User.objects.filter(id__in=creator_ids) + } + + # 构建响应数据 + permissions_data = [] + for perm in permissions: + creator = creators.get(str(perm.knowledge_base.user_id)) + perm_data = { + 'id': str(perm.id), + 'user': { + 'id': str(perm.user.id), + 'username': perm.user.username, + 'name': getattr(perm.user, 'name', perm.user.username), + 'department': getattr(perm.user, 'department', None), + 'role': getattr(perm.user, 'role', None) + }, + 'knowledge_base': { + 'id': str(perm.knowledge_base.id), + 'name': perm.knowledge_base.name, + 'type': perm.knowledge_base.type, + 'department': perm.knowledge_base.department, + 'group': perm.knowledge_base.group, + 'creator': { + 'id': str(perm.knowledge_base.user_id), + 'name': creator.name if creator else None, + 'username': creator.username if creator else None + } + }, + 'permissions': { + 'can_read': perm.can_read, + 'can_edit': perm.can_edit, + 'can_delete': perm.can_delete + }, + 'granted_by': { + 'id': str(perm.granted_by.id) if perm.granted_by else None, + 'username': perm.granted_by.username if perm.granted_by else None, + 'name': getattr(perm.granted_by, 'name', perm.granted_by.username) if perm.granted_by else None + }, + 'granted_at': perm.granted_at.strftime('%Y-%m-%d %H:%M:%S'), + 'expires_at': perm.expires_at.strftime('%Y-%m-%d %H:%M:%S') if perm.expires_at else None, + 'status': perm.status + } + permissions_data.append(perm_data) + + return Response({ + 'code': 200, + 'message': '获取权限列表成功', + 'data': { + 'total': total, + 'page': page, + 'page_size': page_size, + 'results': permissions_data + } + }) + + except Exception as e: + logger.error(f"获取所有权限失败: {str(e)}") + logger.error(traceback.format_exc()) + return Response({ + 'code': 500, + 'message': f'获取所有权限失败: {str(e)}', + 'data': None + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @action(detail=False, methods=['post']) + def update_permission(self, request): + """管理员更新用户的知识库权限""" + try: + # 检查是否是管理员 + if request.user.role != 'admin': + return Response({ + 'code': 403, + 'message': '只有管理员可以直接修改权限', + 'data': None + }, status=status.HTTP_403_FORBIDDEN) + + # 验证必要参数 + user_id = request.data.get('user_id') + knowledge_base_id = request.data.get('knowledge_base_id') + permissions = request.data.get('permissions') + expires_at_str = request.data.get('expires_at') + + if not all([user_id, knowledge_base_id, permissions]): + return Response({ + 'code': 400, + 'message': '缺少必要参数', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + # 验证权限参数格式 + required_permission_fields = ['can_read', 'can_edit', 'can_delete'] + if not all(field in permissions for field in required_permission_fields): + return Response({ + 'code': 400, + 'message': '权限参数格式错误,必须包含 can_read、can_edit、can_delete', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + # 获取用户和知识库 + try: + user = User.objects.get(id=user_id) + knowledge_base = KnowledgeBase.objects.get(id=knowledge_base_id) + except User.DoesNotExist: + return Response({ + 'code': 404, + 'message': f'用户ID {user_id} 不存在', + 'data': None + }, status=status.HTTP_404_NOT_FOUND) + except KnowledgeBase.DoesNotExist: + return Response({ + 'code': 404, + 'message': f'知识库ID {knowledge_base_id} 不存在', + 'data': None + }, status=status.HTTP_404_NOT_FOUND) + + # 检查知识库类型和用户角色的匹配 + if knowledge_base.type == 'private' and str(knowledge_base.user_id) != str(user.id): + return Response({ + 'code': 403, + 'message': '不能修改其他用户的私有知识库权限', + 'data': None + }, status=status.HTTP_403_FORBIDDEN) + + # 处理过期时间 + expires_at = None + if expires_at_str: + try: + # 将字符串转换为datetime对象 + expires_at = timezone.datetime.strptime( + expires_at_str, + '%Y-%m-%dT%H:%M:%SZ' + ) + # 确保时区感知 + expires_at = timezone.make_aware(expires_at) + + # 检查是否早于当前时间 + if expires_at <= timezone.now(): + return Response({ + 'code': 400, + 'message': '过期时间不能早于或等于当前时间', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + except ValueError: + return Response({ + 'code': 400, + 'message': '过期时间格式错误,应为 ISO 格式 (YYYY-MM-DDThh:mm:ssZ)', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + # 根据用户角色限制权限 + if user.role == 'member' and permissions.get('can_delete'): + return Response({ + 'code': 400, + 'message': '普通成员不能获得删除权限', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + # 更新或创建权限记录 + try: + with transaction.atomic(): + permission, created = KBPermissionModel.objects.update_or_create( + user=user, + knowledge_base=knowledge_base, + defaults={ + 'can_read': permissions.get('can_read', False), + 'can_edit': permissions.get('can_edit', False), + 'can_delete': permissions.get('can_delete', False), + 'granted_by': request.user, + 'status': 'active', + 'expires_at': expires_at + } + ) + + # 发送通知给用户 + self.send_notification( + user=user, + title="知识库权限更新", + content=f"管理员已{created and '授予' or '更新'}您对知识库 '{knowledge_base.name}' 的权限", + notification_type="permission_updated", + related_object_id=permission.id + ) + except IntegrityError as e: + return Response({ + 'code': 500, + 'message': f'数据库操作失败: {str(e)}', + 'data': None + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + return Response({ + 'code': 200, + 'message': f"{'创建' if created else '更新'}权限成功", + 'data': { + 'id': str(permission.id), + 'user': { + 'id': str(user.id), + 'username': user.username, + 'name': user.name, + 'department': user.department, + 'role': user.role + }, + 'knowledge_base': { + 'id': str(knowledge_base.id), + 'name': knowledge_base.name, + 'type': knowledge_base.type, + 'department': knowledge_base.department, + 'group': knowledge_base.group + }, + 'permissions': { + 'can_read': permission.can_read, + 'can_edit': permission.can_edit, + 'can_delete': permission.can_delete + }, + 'granted_by': { + 'id': str(request.user.id), + 'username': request.user.username, + 'name': request.user.name + }, + 'expires_at': permission.expires_at.strftime('%Y-%m-%d %H:%M:%S') if permission.expires_at else None, + 'created': created + } + }) + + except Exception as e: + logger.error(f"更新权限失败: {str(e)}") + logger.error(traceback.format_exc()) + return Response({ + 'code': 500, + 'message': f'更新权限失败: {str(e)}', + 'data': None + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + class NotificationViewSet(viewsets.ModelViewSet): """通知视图集""" queryset = Notification.objects.all()