Django REST Framework多用户权限与自定义用户模型技术问询
1. 是否需要创建自定义用户模型来实现上述需求?
是的,非常建议创建自定义用户模型。虽然你可以通过给默认auth.User添加分组(Group)或者额外关联模型(比如当前的Company/Employee与User关联)来区分角色,但自定义用户模型是Django官方推荐的最佳实践,原因如下:
- 后期如果需要扩展用户字段(比如添加手机号、角色标识),自定义模型可以避免重构数据库的麻烦;
- 把角色直接存在用户模型里,权限判断会更直接,减少关联查询;
- 统一用户逻辑,避免多个模型关联带来的复杂度。
当然,如果项目已经处于中期不想重构默认用户模型,也可以通过OneToOneField给auth.User关联角色信息,但长远来看自定义模型更灵活。
2. 若需要,如何创建该自定义用户模型?
咱们一步步来实现:
第一步:定义自定义用户模型
在models.py中创建继承自AbstractUser的模型,添加角色字段:
from django.db import models from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): ROLE_CHOICES = ( ('admin', 'Admin'), ('company', '企业用户'), ('employee', '员工用户'), ) role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='employee') def __str__(self): return self.username
第二步:配置settings.py
告诉Django使用你的自定义用户模型,在settings.py末尾添加:
AUTH_USER_MODEL = 'restcompanies.CustomUser'
第三步:更新关联模型
把原来Company、Employee里的ForeignKey('auth.User')替换为关联CustomUser:
# Company模型示例 class Company(models.Model): user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) empresa_nome = models.CharField(max_length=100, default='') razao_social = models.CharField(max_length=100, default='') cnpj = models.CharField(max_length=18, default='') def __str__(self): return self.empresa_nome # Employee模型同理 class Employee(models.Model): empresa = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees') user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) phone = models.CharField(max_length=20) def __str__(self): return self.phone
第四步:迁移数据库
- 新项目直接执行:
python manage.py makemigrations python manage.py migrate
- 已有数据的项目需先备份数据,清空现有迁移文件(除
__init__.py),再重新生成迁移。
第五步:在admin中注册自定义用户
在admin.py中注册,方便后台管理:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import CustomUser admin.site.register(CustomUser, UserAdmin)
3. 如何设置符合上述规则的权限?
我们需要基于Django REST Framework的自定义权限类实现,每个角色对应不同的权限逻辑:
第一步:创建权限类
在app中新建permissions.py:
from rest_framework import permissions class IsAdmin(permissions.BasePermission): """管理员权限:允许所有操作""" def has_permission(self, request, view): return request.user.is_authenticated and request.user.role == 'admin' class IsCompanyOwner(permissions.BasePermission): """企业用户权限:仅能操作自身旗下资源""" def has_permission(self, request, view): return request.user.is_authenticated and request.user.role == 'company' def has_object_permission(self, request, view, obj): # 判断对象是否属于当前企业用户 if hasattr(obj, 'empresa'): return obj.empresa.user == request.user elif hasattr(obj, 'company'): return obj.company.user == request.user elif isinstance(obj, Company): return obj.user == request.user return False class IsEmployeeOfCompany(permissions.BasePermission): """员工权限:仅能查看本企业的视频""" def has_permission(self, request, view): return request.user.is_authenticated and request.user.role == 'employee' def has_object_permission(self, request, view, obj): employee = request.user.employee_set.first() if not employee: return False # 验证视频/测试是否属于员工所在企业 if isinstance(obj, Video): return obj.video.company == employee.empresa elif isinstance(obj, Test): return obj.company == employee.empresa return False
第二步:在视图集里应用权限
修改views.py中的各个ViewSet,添加permission_classes并过滤查询集:
from rest_framework import viewsets from .models import CustomUser, Company, Employee, Test, Video from .serializers import ( UserSerializer, CompanySerializer, EmployeeSerializer, TestSerializer, VideoSerializer ) from .permissions import IsAdmin, IsCompanyOwner, IsEmployeeOfCompany class UserViewSet(viewsets.ModelViewSet): queryset = CustomUser.objects.all() serializer_class = UserSerializer permission_classes = [IsAdmin] # 仅管理员可管理用户 class CompanyViewSet(viewsets.ModelViewSet): serializer_class = CompanySerializer permission_classes = [IsAdmin | IsCompanyOwner] def get_queryset(self): user = self.request.user if user.role == 'company': return Company.objects.filter(user=user) return Company.objects.all() class EmployeeViewSet(viewsets.ModelViewSet): serializer_class = EmployeeSerializer permission_classes = [IsAdmin | IsCompanyOwner] def get_queryset(self): user = self.request.user if user.role == 'company': return Employee.objects.filter(empresa__user=user) return Employee.objects.all() class TestViewSet(viewsets.ModelViewSet): serializer_class = TestSerializer permission_classes = [IsAdmin | IsCompanyOwner | IsEmployeeOfCompany] def get_queryset(self): user = self.request.user if user.role == 'company': return Test.objects.filter(company__user=user) elif user.role == 'employee': employee = user.employee_set.first() return Test.objects.filter(company=employee.empresa) if employee else Test.objects.none() return Test.objects.all() class VideoViewSet(viewsets.ModelViewSet): serializer_class = VideoSerializer permission_classes = [IsAdmin | IsCompanyOwner | IsEmployeeOfCompany] def get_queryset(self): user = self.request.user if user.role == 'company': return Video.objects.filter(video__company__user=user) elif user.role == 'employee': employee = user.employee_set.first() return Video.objects.filter(video__company=employee.empresa) if employee else Video.objects.none() return Video.objects.all()
4. 如何管理注册表单、登录功能,以及管控不同用户可访问的路由?
注册表单与逻辑
自定义DRF的APIView处理注册,根据角色创建对应关联模型:
# views.py中添加注册视图 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .serializers import CustomUserSerializer, CompanySerializer, EmployeeSerializer class RegisterView(APIView): def post(self, request): user_data = request.data.copy() role = user_data.get('role', 'employee') user_serializer = CustomUserSerializer(data=user_data) if user_serializer.is_valid(): user = user_serializer.save() # 根据角色创建关联模型 if role == 'company': company_data = request.data.get('company', {}) company_data['user'] = user.id company_serializer = CompanySerializer(data=company_data) if company_serializer.is_valid(): company_serializer.save() elif role == 'employee': employee_data = request.data.get('employee', {}) employee_data['user'] = user.id employee_serializer = EmployeeSerializer(data=employee_data) if employee_serializer.is_valid(): employee_serializer.save() return Response(user_serializer.data, status=status.HTTP_201_CREATED) return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
在urls.py中添加注册路由:
from django.conf.urls import url from .views import RegisterView urlpatterns = [ # 其他路由... url(r'^register/$', RegisterView.as_view(), name='register'), ]
登录功能
使用DRF的TokenAuthentication实现:
- 配置settings.py:
INSTALLED_APPS = [ # ... 'rest_framework.authtoken', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ], }
- 迁移生成token表:
python manage.py migrate
- 添加登录路由:
from rest_framework.authtoken.views import obtain_auth_token urlpatterns = [ # 其他路由... url(r'^login/$', obtain_auth_token, name='login'), ]
用户POST用户名和密码到/login/即可获取token,后续请求在Headers中携带Authorization: Token <your-token>即可认证。
路由管控
主要通过视图集的permission_classes和get_queryset实现精准控制:
- 管理员可访问所有路由;
- 企业用户仅能访问自身旗下的公司、员工、测试和视频;
- 员工仅能访问所在企业的测试和视频。
无需额外路由层面的复杂配置,权限类已经能满足需求。
内容的提问来源于stack exchange,提问作者AND4011002849

