【问题标题】:Django/Django Rest Framework - How do I allow model serializer to set model's foreign key field to null?Django/Django Rest Framework - 如何允许模型序列化程序将模型的外键字段设置为空?
【发布时间】:2019-11-16 22:07:57
【问题描述】:

我希望能够将我的Asset 模型中的borrower 字段(外键)设置为NULL,但我似乎无法让它工作。我正在尝试发送一个带有 JSON 数据的 PATCH 请求,其键 borrower 等于 NULL 的值,但模型实例的 borrower 字段不会更新为 NULL。可能是序列化程序存在问题,导致无法将外键字段设置为NULL

我已经尝试将allow_null=True 传递给BorrowSerializer 类,但没有奏效。我在 StackOverflow 上搜索了有类似问题和解决方案的帖子,但我尝试过的都没有。

这是我的models.py:

from django.conf import settings
from django.db import models
from django.utils import timezone
from datetime import date
from django.contrib.auth.models import User
from django.urls import reverse

import uuid

class Category(models.Model):
    """Model representing an Asset category"""

    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Borrower(models.Model):
    first_name = models.CharField(max_length=64)
    last_name = models.CharField(max_length=128)
    associated_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

class Asset(models.Model):
    """Model representing an Asset"""
    # Unique identifier for an instance of an asset (a barcode of sorts)
    uid = models.UUIDField(primary_key=True, default=uuid.uuid4)
    name = models.CharField(max_length=200)
    manufacturer = models.CharField(max_length=64)
    model = models.CharField(max_length=128)
    description = models.TextField()
    category = models.ManyToManyField(Category)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    borrower = models.ForeignKey(Borrower, on_delete=models.CASCADE, null=True, blank=True)
    checked_out = models.BooleanField(default=False)
    return_date = models.DateField(null=True, blank=True)    

    CONDITION_TYPE = (
        ('e', 'Excellent'),
        ('g', 'Good'),
        ('f', 'Fair'),
        ('p', 'Poor'),
    )

    condition = models.CharField(
        max_length=1,
        choices=CONDITION_TYPE,
        blank=True,
        help_text='Asset condition')

    class Meta:
            ordering = ['return_date']

    @property
    def is_dueback(self):
        if self.return_date and date.today() > self.return_date:
            return True
        return False

    def display_category(self):
        """Create a string for the Category. This is required to display category in Admin."""
        return ', '.join(category.name for category in self.category.all())

    display_category.short_description = 'Category'

    def __str__(self):
        return f'{self.uid} - {self.name}'

    def get_absolute_url(self):
        return reverse('asset-detail', args=[str(self.uid)])

这是我的 serializers.py 文件:

from rest_framework import serializers
from inventory.models import Asset, Borrower, Category

class BorrowerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Borrower
        fields = ('first_name',
                  'last_name',
                  'associated_user'
        )

    def update(self, instance, validated_data):
        print('Update method triggered.')
        instance.first_name = validated_data.get('first_name', instance.first_name)
        instance.last_name = validated_data.get('last_name', instance.last_name)
        instance.associated_user = validated_data.get('associated_user'. instance.associated_user)
        instance.save()
        return instance

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = '__all__'

class AssetSerializer(serializers.ModelSerializer):
    borrower = BorrowerSerializer(allow_null=True)
    category = serializers.StringRelatedField(many=True)
    condition = serializers.CharField(source='get_condition_display')

    class Meta:
        model = Asset
        fields = ('name', 
                  'manufacturer', 
                  'model',
                  'description',
                  'condition',
                  'category',
                  'borrower',
                  'checked_out',
                  'return_date',
                  'is_dueback',
        )

这是我的 DRF API 视图:

class AssetRetrieveUpdateDestroy(RetrieveUpdateDestroyAPIView):
    lookup_field = 'uid'
    serializer_class = AssetSerializer

    def get_queryset(self):
        user = self.request.user
        return Asset.objects.filter(owner=user)

class BorrowerRetrieveUpdateDestroy(RetrieveUpdateDestroyAPIView):
    lookup_field = 'id'
    serializer_class = BorrowerSerializer

    def get_queryset(self):
        return Borrower.objects.all()

当我传入此 JSON 时,我希望 Asset 模型实例中的 borrower 字段更新为 NULL:

data = {
   'borrower': null
}

但是,当 borrower 字段是外键时,我的模型实例不会更新它。如果该字段是CharField 或其他东西,但在外键上,它工作正常。我打印出我的 AJAX 请求返回的数据,但 borrower 字段保持不变。

提前感谢您提供的任何帮助。

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案1】:

    来自the docs

    如果您支持可写嵌套表示,则需要编写处理保存多个对象的 .create().update() 方法。

    因此您需要在您的AssetSerializer 中实现.update() 方法:

    class AssetSerializer(serializers.ModelSerializer):
        borrower = BorrowerSerializer(allow_null=True)
        ...
    
        class Meta:
            model = Asset
            fields = (...)
    
        def update(self, instance, validated_data):
            instance.borrower = validated_data.get('borrower')
            instance.save()
    
            return instance
    

    这应该可以完成工作。因此,无论您是否传递了 allow_null=True,它都会接受 null 值,但不会更新您的嵌套关系,除非您定义自定义更新机制。

    【讨论】:

    • 谢谢!我必须使用validated_data.get() 而不是validated_data.pop() 让序列化程序根据需要更新我的模型实例。非常感谢您为我指明正确方向的帮助!
    • @elev8eng 确保接受答案,以引导更多读者走向正确的方向。 :)
    猜你喜欢
    • 1970-01-01
    • 2018-06-07
    • 1970-01-01
    • 2013-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-06
    • 1970-01-01
    相关资源
    最近更新 更多