From d13d402d982feb754ad9f7f767a7a0550f6260e2 Mon Sep 17 00:00:00 2001 From: colaftc Date: Mon, 5 Jan 2026 19:06:45 +0800 Subject: [PATCH] feat: added /api/v2/roles/ --- api_v2/tests.py | 57 +++++++++++++++++++++++++++++++++++++++- api_v2/urls.py | 2 ++ api_v2/views/__init__.py | 3 ++- api_v2/views/users.py | 29 ++++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/api_v2/tests.py b/api_v2/tests.py index 702fc5a..e8c4b9a 100644 --- a/api_v2/tests.py +++ b/api_v2/tests.py @@ -2,7 +2,7 @@ from decimal import Decimal import datetime from django.contrib.auth import get_user_model -from django.contrib.auth.models import Permission +from django.contrib.auth.models import Group, Permission from django.test import TestCase from django.utils import timezone from rest_framework.test import APIClient, APIRequestFactory @@ -62,6 +62,61 @@ class QuickCreateEmployeeUserAPITest(TestCase): self.assertEqual(response.status_code, 400) self.assertIn('商户不存在', str(response.data)) + def test_quick_create_employee_user_with_role(self): + """测试创建用户时指定角色""" + role = Group.objects.create(name='管理员') + payload = {**self.payload, 'role_id': role.id} + response = self.client.post(self.url, payload, format='json') + self.assertEqual(response.status_code, 201) + self.assertEqual(response.data['user']['role_id'], role.id) + + created_user = get_user_model().objects.get(username=self.payload['username']) + self.assertIn(role, created_user.groups.all()) + + def test_quick_create_employee_user_with_invalid_role(self): + """测试创建用户时指定不存在的角色""" + payload = {**self.payload, 'role_id': 99999} + response = self.client.post(self.url, payload, format='json') + self.assertEqual(response.status_code, 400) + self.assertIn('角色不存在', str(response.data)) + + +class RoleListV2APITest(TestCase): + def setUp(self): + self.client = APIClient() + self.user = get_user_model().objects.create_user(username='test_user', password='pass12345') + self.client.force_authenticate(user=self.user) + self.url = '/api/v2/roles/' + + def test_role_list_returns_all_roles(self): + """测试获取所有角色""" + role1 = Group.objects.create(name='管理员') + role2 = Group.objects.create(name='普通用户') + + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 2) + + names = [r['name'] for r in response.data] + self.assertIn('管理员', names) + self.assertIn('普通用户', names) + + # 检查返回字段只有 id 和 name + for role in response.data: + self.assertEqual(set(role.keys()), {'id', 'name'}) + + def test_role_list_empty(self): + """测试没有角色时返回空列表""" + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, []) + + def test_role_list_requires_authentication(self): + """测试未认证用户无法访问""" + client = APIClient() + response = client.get(self.url) + self.assertEqual(response.status_code, 401) + class HealthCheckV2APITest(TestCase): def setUp(self): diff --git a/api_v2/urls.py b/api_v2/urls.py index 09a837a..d55fa5d 100644 --- a/api_v2/urls.py +++ b/api_v2/urls.py @@ -3,6 +3,7 @@ from django.urls import path from api_v2.views import ( HealthCheckView, QuickCreateEmployeeUserView, + RoleListView, PrintingJobByCustomerView, PrintingJobBatchAdvancePreviewView, PrintingJobBatchAdvanceSubmitView, @@ -16,6 +17,7 @@ from api_v2.views.basic_info import CustomerEmployeeBindingView urlpatterns = [ path('health/', HealthCheckView.as_view(), name='api_v2_health_check'), + path('roles/', RoleListView.as_view(), name='api_v2_role_list'), path('users/quick-create/', QuickCreateEmployeeUserView.as_view(), name='api_v2_user_quick_create'), path('customers/bind-employee/', CustomerEmployeeBindingView.as_view(), name='api_v2_customer_bind_employee'), path('printing-jobs/by-customer/', PrintingJobByCustomerView.as_view(), name='api_v2_printing_job_by_customer'), diff --git a/api_v2/views/__init__.py b/api_v2/views/__init__.py index e43bacf..c93987d 100644 --- a/api_v2/views/__init__.py +++ b/api_v2/views/__init__.py @@ -3,7 +3,7 @@ api_v2 视图包。 """ from .healthy import HealthCheckView -from .users import QuickCreateEmployeeUserView +from .users import QuickCreateEmployeeUserView, RoleListView from .printing import ( PrintingJobByCustomerView, PrintingJobV2Serializer, @@ -19,6 +19,7 @@ from .stateflow import BusinessObjectCloneView __all__ = [ 'HealthCheckView', 'QuickCreateEmployeeUserView', + 'RoleListView', 'PrintingJobByCustomerView', 'PrintingJobV2Serializer', 'PrintingJobBatchAdvancePreviewView', diff --git a/api_v2/views/users.py b/api_v2/views/users.py index 77213db..c44bddb 100644 --- a/api_v2/views/users.py +++ b/api_v2/views/users.py @@ -1,4 +1,5 @@ from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from django.db import transaction from rest_framework import serializers, status from rest_framework.permissions import IsAuthenticated @@ -10,6 +11,21 @@ from basic_info import models as basic_models User = get_user_model() +class RoleListView(APIView): + """ + 获取所有角色列表(基于 Django Group)。 + + GET /api/v2/roles/ + 返回: [{"id": 1, "name": "管理员"}, ...] + """ + + permission_classes = [IsAuthenticated] + + def get(self, request): + roles = Group.objects.all().values('id', 'name').order_by('id') + return Response(list(roles)) + + class QuickEmployeeUserCreateSerializer(serializers.Serializer): username = serializers.CharField(max_length=150) password = serializers.CharField(write_only=True, min_length=6) @@ -24,6 +40,7 @@ class QuickEmployeeUserCreateSerializer(serializers.Serializer): required=False, default=basic_models.EmployeeStatusEnum.ACTIVE, ) + role_id = serializers.IntegerField(required=False, allow_null=True, help_text='角色ID(可选)') def validate_username(self, value): if User.objects.filter(username=value).exists(): @@ -35,6 +52,11 @@ class QuickEmployeeUserCreateSerializer(serializers.Serializer): raise serializers.ValidationError('商户不存在') return value + def validate_role_id(self, value): + if value is not None and not Group.objects.filter(id=value).exists(): + raise serializers.ValidationError('角色不存在') + return value + class QuickCreateEmployeeUserView(APIView): """ @@ -53,6 +75,7 @@ class QuickCreateEmployeeUserView(APIView): merchant = basic_models.Merchant.objects.get(id=data['merchant_id']) display_name = data['display_name'] status_value = data.get('status') or basic_models.EmployeeStatusEnum.ACTIVE + role_id = data.get('role_id') with transaction.atomic(): user = User.objects.create_user( @@ -63,6 +86,11 @@ class QuickCreateEmployeeUserView(APIView): user.first_name = display_name user.save(update_fields=['first_name']) + # 如果指定了角色,将用户添加到对应的 Group + if role_id: + group = Group.objects.get(id=role_id) + user.groups.add(group) + employee = basic_models.Employee.objects.create( merchant=merchant, sys_user=user, @@ -79,6 +107,7 @@ class QuickCreateEmployeeUserView(APIView): 'id': user.id, 'username': user.username, 'display_name': display_name, + 'role_id': role_id, }, 'employee': { 'id': employee.id,