至少在 2.x 版中,Django Rest Framework 并没有让这变得简单——而且我不确定是否有/是否有任何计划在版本 3 中让它变得更好。
在尝试标准化问题之前,我通过序列化程序 init 中的 try catch 过滤了数据字典中传递的父属性的任何适用字段的查询集,在不同的地方修复了这个问题 - 以下是我想出的。
SlugRelatedDependentField
class SlugRelatedDependentField(SlugRelatedField):
def __init__(self, depends_on=None, **kwargs):
assert depends_on is not None, 'The `depends_on` argument is required.'
self.depends_on = depends_on # archive_unit__organization or organization
self.depends_segments = self.depends_on.split('__')
self.depends_parent = self.depends_segments.pop(0)
self.depends_field = SimpleLazyObject(lambda: self.parent.parent.fields[self.depends_parent])
self.depends_queryset = SimpleLazyObject(lambda: self.depends_field.queryset)
self.depends_model = SimpleLazyObject(lambda: self.depends_queryset.model)
super(SlugRelatedDependentField, self).__init__(**kwargs)
def contextualize(self, instance, data):
self.data = data
self.instance = instance
def get_queryset(self):
try:
return self.queryset.filter(**{self.depends_on: reduce(getattr, self.depends_segments, self.get_relation())})
except self.depends_model.DoesNotExist:
# if parent was absent or invalid, empty the queryset
return self.queryset.none()
except TypeError:
# if parent was a Page instance, use the full queryset, it's only a list view
return self.queryset.all()
def get_relation(self):
try:
# if an allowed parent was passed, filter by it
return self.depends_queryset.get(**{self.depends_field.slug_field: self.data[self.depends_parent]})
except (KeyError, TypeError):
# if data was empty or no parent was passed, try and grab it off of the model instance
if isinstance(self.instance, self.parent.parent.Meta.model):
return getattr(self.instance, self.depends_parent)
elif self.instance is None:
raise self.depends_model.DoesNotExist
else:
raise TypeError
用法
class RepositorySerializer(ModelSerializer):
organization = SlugRelatedField(queryset=Organization.objects.all(), slug_field='slug')
teams = SlugRelatedDependentField(allow_null=True, depends_on='organization', many=True, queryset=Team.objects.all(), required=False, slug_field='slug')
def __init__(self, instance=None, data=empty, **kwargs):
f = self.fields['teams']
# assign instance and data for get_queryset
f.child_relation.contextualize(instance, data)
# inject relation values from instance if they were omitted so they are validated regardless
if data is not empty and instance and name not in data:
data[name] = [getattr(relation, f.child_relation.slug_field) for relation in getattr(instance, name).all()]
super(RepositorySerializer, self).__init__(instance=instance, data=data, **kwargs)
总结
SlugRelatedDependentField 扩展了常规的 SlugRelatedField 以接受 depends_on kwarg,该 kwarg 接受描述该字段与另一个字段的关系的字符串——在此示例中,该用法描述分配给此存储库的团队必须属于该组织.
几个问题
- 如果父项不存在,我用
.none() 清空查询集,这样可以避免选择泄漏,否则可能会通过OPTIIONS 请求和验证消息暴露出来,这通常是不可取的。
- 我在查询父记录时使用了
data,IIRC 我这样做的原因是因为data 始终可用,而父字段的对象可能不是例如在 PATCH 请求的情况下。
- 您会注意到我在序列化程序初始化的后半部分注入了任何省略的关系值,这用于强制验证在多个字段上运行 - 很有用,例如如果用户在
PATCH 请求中更改了记录的organization,则意味着分配的teams 不再适用。
支持远亲
此解决方案解决的另一个问题是引用远距离关系,这可以通过将__ 分隔字符串传递给depends_on 来完成,例如repository__organization,我没有很好的示例用例,但如果你需要它就在那里。