简体   繁体   中英

In a Django serializer, how to set foreign key field based on view argument?

Using the Django REST Framework, I would like to allow users to create and save instances of a Django model through a ListCreateAPIView (via POST ). One of the fields (a foreign-key field called domain ) shall be determined from a view parameter as defined in urls.py .

Furthermore, the user can modify the model instance later using PUT or PATCH requests to a RetrieveUpdateDestroyAPIView endpoint (using the same serializer). I don't want the user to be able to modify the domain field at this point.

While I have the code for the model and the view / serializer structure ready, I'm not sure how to tell the serializer to determine the value of the domain field based on the view parameter. Here's what I got:

class RRset(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(null=True)
    domain = models.ForeignKey(Domain, on_delete=models.CASCADE, related_name='rrsets')
    subname = models.CharField(max_length=255, blank=True)
    type = models.CharField(max_length=10)

... and a straight-forward ListCreateAPIView :

class RRsetsDetail(generics.ListCreateAPIView):
    serializer_class = RRsetSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        name = self.kwargs['name']
        return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)

urls.py contains the following line:

url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets')

This allows the user to list and create RRset objects using the RRsetsSerializer serializer (the name field is listed for completeness only, but I do not believe it to be important in this context):

class RRsetSerializer(serializers.ModelSerializer):
    name = serializers.SerializerMethodField()

    def get_name(self, obj):
        return '.'.join(filter(None, [obj.subname, obj.domain.name])) + '.' # returns 'subname.name.'

    class Meta:
        model = RRset
        fields = ('created', 'updated', 'domain', 'name', 'type',)
        read_only_fields = ('created', 'updated', 'domain', 'type',)

Questions:

  1. What do I need to modify to have the serializer take the domain name from the view name parameter?
  2. The the serializer's read_only_fields setting prevents the user from modifying the domain field later. However, I'm not sure if this setting somehow interacts with the serializer trying to set a default value (can the serializer write the default value, even if read-only is set)?

To summarize: What I'm looking for is something like a "write-once field with a default value based on a view parameter".

I think you are looking for a HiddenField with a combination of CreateOnlyDefault

HiddenField

A field class that does not take a value based on user input, but instead takes its value from a default value or callable.

CreateOnlyDefault

A default class that can be used to only set a default argument during create operations. During updates the field is omitted.

It takes a single argument, which is the default value or callable that should be used during create operations.

And because you want to access the view, you can't just use callable, but you have to use Class-based callable which can have access to a context data.

class DomainDefault(object):
    def set_context(self, serializer_field):
        view = serializer_field.context['view']
        request = serializer_field.context['request']
        self.domain = ...#determine the domain based on request+view

    def __call__(self):
        return self.domain


class RRsetSerializer(serializers.ModelSerializer):
    domain = serializers.HiddenField(default=serializers.CreateOnlyDefault(DomainDefault()))

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