简体   繁体   中英

Django/Django Rest Framework - How do I allow model serializer to set model's foreign key field to null?

I want to be able to set the borrower field (a foreign key) in my Asset model to be NULL but I can't seem to get it to work. I'm trying to send a PATCH request with JSON data that has the key borrower equal to value of NULL but the borrower field won't get updated to NULL for the model instance. Perhaps there is an issue with the serializer that is preventing the foreign key field from being able to be set to NULL ?

I have already tried passing in allow_null=True to BorrowSerializer class but that hasn't worked. I've searched high and low on StackOverflow for posts with similar problems and solutions but nothing I've tried has worked.

Here is my 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)])

Here is my serializers.py file:

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',
        )

Here are my DRF API Views:

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()

I expected the borrower field in my Asset model instance to be updated to NULL when I pass in this JSON:

data = {
   'borrower': null
}

However, my model instance won't update the borrower field when it's a foreign key. It works fine if the field is CharField or something else, but on foreign keys. I printed out the data returned from my AJAX request but the borrower field stays unchanged.

Thank you in advance for any help you can provide.

From the docs :

If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.

Thus you need to implement the .update() method in your AssetSerializer :

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

This should do the work. So no matter that you've passed allow_null=True , it will accept null values , but won't update your nested relationship unless you define your custom update mechanism.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM