我试图根据用户权限限制序列化程序中的fields
列表。我有一个通用例程,可以对所有序列化程序执行此操作。它适用于父级序列化程序,但不适用于嵌套序列化程序。
我有一个客户模型和一个客户资料(称为“联系人”),如下所示。客户端配置文件模型是用户模型(一对一关系)的扩展。
class Client(AddressPhoneModelMixin, DateFieldsModelMixin, models.Model):
name = models.CharField(max_length=100)
status = models.CharField(max_length=25)
class Meta:
permissions = (
# Object-level
('view_all_clients', 'Can view all clients'),
('change_all_clients', 'Can change all clients'),
# Field-level
('view_client_id', 'Can view client ID'),
('view_client_name', 'Can view client name'),
...others omitted...
)
class ClientProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
blank=True,
null=True,
)
client = models.ForeignKey(
Client,
on_delete=models.PROTECT,
related_name='contacts',
)
receive_invoices = models.BooleanField(default=False)
我的对象级权限逻辑在列表视图中:
class ClientList(ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ClientSerializer
def get_queryset(self):
user = self.request.user
queryset = None
if user.has_perm('view_client') or user.has_perm('clients.view_all_clients'):
queryset = Client.objects.all().exclude(status__in=['deleted', 'archived'])
if user.has_perm('view_client'): # View only "assigned" clients
if user.type == 'client':
# See if user is a "contact".
queryset = queryset.distinct().filter(contacts__user=self.request.user)
else:
# See if user is assigned to projects for the client(s).
queryset = queryset.distinct().filter(projects__project_users__user=self.request.user)
if queryset is None:
raise PermissionDenied('You do not have permission to view clients.')
return self.get_serializer_class().setup_eager_loading(queryset)
从序列化程序“ fields”属性中删除字段是通过序列化程序__init__
方法完成的(来自我在SO的示例中):
class ClientContactsSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='clients:clientprofile-detail')
user = UserSerializer()
class Meta:
model = ClientProfile
fields = (
'url',
'receive_invoices',
'user',
)
def __init__(self, *args, **kwargs):
super(ClientContactsSerializer, self).__init__(*args, **kwargs)
check_field_permissions(self, 'view')
class ClientSerializer(AddressPhoneSerializerMixin, serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='clients:client-detail')
contacts = ClientContactsSerializer(many=True, read_only=True)
projects = ClientProjectsSerializer(many=True, read_only=True)
class Meta:
model = Client
fields = (
'url',
'id',
'name',
...omitted for brevity...
'contacts',
'projects',
)
def __init__(self, *args, **kwargs):
super(ClientSerializer, self).__init__(*args, **kwargs)
check_field_permissions(self, 'view')
@staticmethod
def setup_eager_loading(queryset):
queryset = queryset.select_related('country')
return queryset.prefetch_related('contacts', 'contacts__user', 'contacts__user__country', 'projects')
最后,这是check_field_permissions函数:
def check_field_permissions(serializer, action='view'):
request = serializer.context.get('request', None)
fields = serializer.get_fields()
model = serializer.Meta.model
app_name = model._meta.app_label
model_name = model._meta.model_name
if request is not None and app_name is not None and model_name is not None:
user = request.user
for field_name in fields:
if hasattr(serializer.fields[field_name], 'child'):
continue
if not user.has_perm(app_name + '.' + action + '_' + model_name + '_' + field_name):
serializer.fields.pop(field_name)
在Client列表的页面加载过程中逐步调试,我可以看到上面的函数首先为clientprofile调用,并且请求为None。第二次,它为客户端调用,并且请求是有效的请求对象。
第一个问题,__init__
是否是限制要序列化的字段列表的正确位置?
第二,如何在嵌套的序列化程序(客户端配置文件)中获取请求对象?
答案 0 :(得分:0)
在阅读了无可争议的DRF权威汤姆·克里斯蒂(Tom Christie)的帖子后,我得以解决我的问题。他指出,每个嵌套的序列化程序实际上都具有上下文对象(以及请求和用户)。您只需要处理父__init__
中的嵌套序列化程序,而不是它们自己的__init__
中的嵌套序列化程序。
这是我修改后的check_field_permissions()函数:
def check_field_permissions(serializer, action='view'):
request = serializer.context.get('request', None)
fields = serializer.get_fields()
model = serializer.Meta.model
app_name = model._meta.app_label
model_name = model._meta.model_name
if request is not None and app_name is not None and model_name is not None:
user = request.user
extra_fields = []
for field_name in fields:
if field_name == 'url':
extra_fields.append(field_name)
continue
if hasattr(serializer.fields[field_name], 'child'):
check_field_permissions(serializer.fields[field_name].child, action)
extra_fields.append(field_name)
continue
if not user.has_perm(app_name + '.' + action + '_' + model_name + '_' + field_name):
serializer.fields.pop(field_name)
# If only "url" and child fields remain, remove all fields.
if len(serializer.fields) == len(extra_fields):
for field_name in extra_fields:
serializer.fields.pop(field_name)
现在是递归的。如果它命中具有“ child”属性的字段,则说明它是嵌套的序列化程序字段。它使用作为arg传递的子序列化器来调用自身。
另一个变化是__init__
已从ClientContactsSerializer中删除,因为它不需要调用check_field_permissions()。