You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 23:31:05