Django REST含外键的LineItem增改报错,求标准解决方案
Django REST框架中外键关联子模型的标准实现方案
我在Django REST框架中遇到以下问题:Document作为父模型,LineItem通过外键与其关联。目前可以正常删除单个LineItem,但创建或更新LineItem时,会触发ValidationError,提示信息为:'document': [ErrorDetail(string='This field is required.', code='required')。我已找到临时解决方法,但想了解更标准的实现方式。
相关代码
models.py
class Document(models.Model): """ ORM representing Document object """ id = models.AutoField( primary_key=True, ) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name='documents', ) class LineItem(models.Model): """ ORM representing line items in a document. """ id = models.AutoField( primary_key=True, ) document = models.ForeignKey( Document, on_delete=models.CASCADE, related_name='line_items', ) description = models.CharField( max_length=50, blank=True, db_default='', default='', ) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name='line_items', )
serializers.py(初始版本)
class LineItemSerializer(serializers.ModelSerializer): user = serializers.HiddenField(default=serializers.CurrentUserDefault()) class Meta: model = LineItem fields = '__all__'
urls.py
# Add a document line item path('api/document/<int:doc_pk>/line/', DocumentLineItemCreateAPI.as_view(), name='document-line-item-add-api'), # Edit a document line item path('api/document/<int:doc_pk>/line/<int:pk>/', DocumentLineItemUpdateAPI.as_view(), name='document-line-item-update-api'),
views.py
class DocumentLineItemUpdateAPI(generics.RetrieveUpdateDestroyAPIView): """ Unified view for retreiving, updating or deleting document line items """ serializer_class = LineItemSerializer def get_queryset(self): return LineItem.objects.all() def get_object(self): obj = get_object_or_404(self.get_queryset(), \ document_id=self.kwargs.get('doc_pk'), id=self.kwargs.get('pk')) return obj def destroy(self, request, doc_pk, pk, *args, **kwargs): return super().destroy(request, *args, **kwargs) def update(self, request, doc_pk, pk, *args, **kwargs): return super().update(request, doc_pk, pk, *args, **kwargs)
临时解决方法(已实现)
修改Serializer,从URL参数中获取document_id并注入到验证数据中:
class LineItemSerializer(serializers.ModelSerializer): user = serializers.HiddenField(default=serializers.CurrentUserDefault()) document = serializers.SerializerMethodField() def get_document(self, obj): doc_id = self.context.get('request').parser_context.get('kwargs').get('doc_pk') return Document.objects.get(pk=doc_id) def create(self, validated_data): doc_id = self.context.get('request').parser_context.get('kwargs').get('doc_pk') validated_data['document_id'] = doc_id return super().create(validated_data) class Meta: model = LineItem fields = '__all__'
标准实现方案
核心思路
将从URL获取关联文档的逻辑从Serializer转移到视图层,让Serializer专注于数据序列化与验证,视图层负责业务逻辑处理,符合单一职责原则。
1. 优化Serializer
将document字段设为只读,避免前端提交时触发必填验证:
class LineItemSerializer(serializers.ModelSerializer): user = serializers.HiddenField(default=serializers.CurrentUserDefault()) class Meta: model = LineItem fields = '__all__' read_only_fields = ['document'] # 标记为只读,无需前端传入
2. 完善创建视图(DocumentLineItemCreateAPI)
继承CreateAPIView,在perform_create方法中注入关联的Document对象:
class DocumentLineItemCreateAPI(generics.CreateAPIView): serializer_class = LineItemSerializer queryset = LineItem.objects.all() def perform_create(self, serializer): # 从URL参数获取对应的Document对象 document = get_object_or_404(Document, pk=self.kwargs['doc_pk']) # 保存时关联该文档 serializer.save(document=document)
3. 优化更新视图(DocumentLineItemUpdateAPI)
限制查询集范围(仅当前文档下的LineItem),并在perform_update中确保关联文档不变:
class DocumentLineItemUpdateAPI(generics.RetrieveUpdateDestroyAPIView): serializer_class = LineItemSerializer def get_queryset(self): # 仅返回当前文档下的LineItem,提升安全性 return LineItem.objects.filter(document_id=self.kwargs['doc_pk']) def get_object(self): return get_object_or_404(self.get_queryset(), id=self.kwargs['pk']) def perform_update(self, serializer): # 确保更新时关联的文档不变(可选,URL已限定文档范围) document = get_object_or_404(Document, pk=self.kwargs['doc_pk']) serializer.save(document=document)
方案优势
- 职责清晰:Serializer只处理数据验证与序列化,视图层处理业务关联逻辑
- 安全性高:
get_queryset限定了操作范围,防止越权修改其他文档的LineItem - 代码简洁:避免在Serializer中解析请求上下文,降低耦合度
内容的提问来源于stack exchange,提问作者SeaSky
相关产品推荐
相关产品推荐

