如何通过django-tenant-users在Django中实现租户数据与功能隔离?
解决Django多租户(django-tenant + django-tenant-users)的租户隔离问题
你的思路方向是对的,但要实现全面的租户隔离,需要从全局校验、模型绑定、查询过滤、视图加固多个层面组合实施,以下是具体实操方案:
1. 优化租户认证中间件
你现有的中间件可以优化查询效率,同时处理匿名用户和无租户的场景:
from django.core.exceptions import PermissionDenied from django_tenants.utils import get_current_tenant class TenantAuthMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): tenant = get_current_tenant() # 无租户上下文直接拒绝(可根据公开资源需求调整) if not tenant: raise PermissionDenied("No tenant context found") if request.user.is_authenticated: # 用exists()替代in操作,避免全表查询 if not request.user.tenants.filter(pk=tenant.pk).exists(): raise PermissionDenied("You don't have access to this tenant") response = self.get_response(request) return response
注意:这个中间件要放在django-tenant自带的TenantMiddleware之后,确保request.tenant已经被正确设置。
2. 模型层强制租户关联
所有属于租户的业务模型必须绑定租户外键,从根源上确保数据归属:
from django.db import models from django_tenants.models import TenantMixin # 自定义租户模型 class Client(TenantMixin): name = models.CharField(max_length=100) paid_until = models.DateField(null=True, blank=True) on_trial = models.BooleanField(default=True) auto_create_schema = True # 自动创建租户schema # 业务模型示例:所有租户数据必须关联Client class Project(models.Model): tenant = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="projects") name = models.CharField(max_length=200) description = models.TextField(null=True, blank=True) def __str__(self): return self.name
3. 自动过滤租户查询集
自定义模型Manager,让所有默认查询自动过滤当前租户的数据,避免视图中重复写过滤逻辑:
from django.db import models from django_tenants.utils import get_current_tenant class TenantManager(models.Manager): def get_queryset(self): queryset = super().get_queryset() tenant = get_current_tenant() if tenant: return queryset.filter(tenant=tenant) # 无租户上下文时返回空集(或按需调整) return queryset.none() # 业务模型中使用该Manager class Project(models.Model): # ... 字段定义 objects = TenantManager()
这样无论你在视图中调用Project.objects.all()还是Project.objects.get(pk=1),都会自动过滤当前租户的数据。
4. 结合django-tenant-users的用户-租户关联
确保用户和租户的多对多关系正确维护,django-tenant-users提供了TenantUser模型来关联用户和租户:
from django_tenant_users.tenants.models import TenantUser from django.contrib.auth.models import User # 创建用户并关联租户 def create_tenant_user(tenant, username, password): user = User.objects.create_user(username=username, password=password) # 绑定用户到租户 TenantUser.objects.create(user=user, tenant=tenant) return user
同时,确保用户模型的tenants反向关联可用(django-tenant-users默认已配置),这样request.user.tenants能正确返回用户所属的租户列表。
5. 视图层加固权限
使用Mixin装饰器,在视图层面二次校验租户权限,防止绕过中间件的场景:
from django.core.exceptions import PermissionDenied from django.views.generic.base import ViewMixin from django_tenants.utils import get_current_tenant class TenantPermissionMixin(ViewMixin): def dispatch(self, request, *args, **kwargs): tenant = get_current_tenant() if not tenant: raise PermissionDenied if request.user.is_authenticated and not request.user.tenants.filter(pk=tenant.pk).exists(): raise PermissionDenied return super().dispatch(request, *args, **kwargs) # 在视图中使用 class ProjectListView(TenantPermissionMixin, ListView): model = Project template_name = "projects/list.html"
6. 管理后台租户隔离
如果使用Django Admin,需要自定义ModelAdmin来过滤租户数据:
from django.contrib import admin from django_tenants.utils import get_current_tenant from .models import Project class ProjectAdmin(admin.ModelAdmin): list_display = ("name", "tenant") def get_queryset(self, request): qs = super().get_queryset(request) tenant = get_current_tenant() if tenant: return qs.filter(tenant=tenant) return qs.none() admin.site.register(Project, ProjectAdmin)
总结
以上方案组合起来,从全局请求校验、模型数据绑定、自动查询过滤、视图权限加固、后台数据隔离五个层面实现了完整的租户隔离,确保租户用户无法访问其他租户的数据或功能。
内容的提问来源于stack exchange,提问作者alon_r
相关产品推荐
相关产品推荐

