How does Django Rest Framework deserialize foreign key relationships? - python

In the tutorial, there's this loose one-to-one mapping between serializer fields and model fields. I can expect that if a serializer field and a model field are both CharFields it will save a string of characters when deserializing into a model instance:
models.py:
class Deck(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, unique=True, blank=False, null=False)
serializers.py:
class DeckSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Deck
fields = ('url', 'id', 'created', 'name')
extra_kwargs = {
'url': {
'view_name': 'goals:deck-detail',
}
}
But when I try a relationship, the serializer field is a ReadOnlyField, which from what I understand is essentially a Charfield, but the model field is a ForeignKeyField, and to add to the confusion, it seems like I'm saving an object in the views when I override perform_create:
models.py:
class Deck(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, unique=True, blank=False, null=False)
user = models.ForeignKey('users.User', related_name='decks', on_delete=models.CASCADE, null=False)
serializers.py:
class DeckSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
class Meta:
model = Deck
fields = ('url', 'id', 'created', 'name', 'user')
extra_kwargs = {
'url': {
'view_name': 'goals:deck-detail',
}
}
views.py:
class DeckList(generics.ListCreateAPIView):
serializer_class = DeckSerializer
def get_queryset(self):
return Deck.objects.all().filter(user__username=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
What's going on here? When dealing with relationships, why is it that I am saving an object by overriding perform_create in the views, declaring in the serializers a CharField, and in the models declaring a relationship field?
What's missing in my understanding, or what's really happening under the hood such that the user field (ForeignKey) can be represented as a string but saved as an object?
Edit:
If I'm overriding serializer.save(user=user) in the views and the serializers.py has the user field as
user = serializers.CharField(read_only=True)
And I want to override the save method in serializers.py, how do I pass the proper data so that it will know how to serialize? Do I just grab the whole User object, save it, and it'll do the rest? Is the serializers.save() override in the views the same as serializers.save() in serializers.py?

Not 100% sure that I've understood what you're asking, but if the question is:
What's happening under the hood when a ForeignKey field is saved in the Django ORM?
Then the answer is that:
The relation is saved in the DB as an (e.g.) int field which stores the primary key of the related object.
The ForeignKey field reference section in the Django docs explains how this part of the ORM works, and the "Database Representation" subsection likely touches on the specific bit you're interested in.
For example, for your case of User being a related field in the Deck model the underlying table would likely look like this (assuming postgresql):
myapp_deck
id int
created timestamp
name varChar
user_id int
The Deck -> User relation is mapped by the DB storing the pk for the related User object in the user_id field in the myapp_deck table.
So, all Django (and, consequently, DRF) needs to do to change the User in the Deck model is change the user_id in the myapp_deck table to the PK of a different User object.
Hope this helps, and please let me know if I've missed the point of your question.
Edited to Add Example of Custom .create() method
If you want to override the custom "save" method in a serializer then the methods to override are create() and update() accordingly (see Serializer "Saving instances" section in the DRF docs).
An example of this might be:
class DeckSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
... Rest of your serializer code ...
def create(self, validated_data, **kwargs):
user_obj = User.objects.get(pk=validated_data["user"])
deck = Deck.objects.create(
name=validated_data["name"],
user=user_obj,
)
return deck
Note: This assumes that the pk of the related User object is passed throught the serializer, validates OK, and is available in the validated_data dict.

Related

django rest framework - foreign key fields are read only

The model:
class Item(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE)
item_num = models.IntegerField()
# other fields...
class Meta:
unique_together = [('company', 'item_num') ]
Serializer:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('company_id', 'item_num', )
The problem is that django rest framework generates a ReadOnlyField() for the company_id field, so this field is not editable when I create a new instance in view code like this:
s = ItemSerializer(data=request.POST)
s.save()
I also lose the default UniqueTogetherValidator that is defined in the model.
Though, if I change the serializer field name from 'company_id' to 'company', I do get the validator, as drf will generate PrimaryKeyRelatedField so it will be editable.
How can I still name my foreign key objects like 'company_id', because I do prefer naming them like this, and still get the default validation and saving behavior? Preferably without adding to much code to the serializer.
company_id is a read-only field because it lacks a proper definition in the Model for the serializer to understand it.
Steps to get it working:
Add an explicit field definition
Add the constraint
Serializer would be:
class ItemSerializer(serializers.ModelSerializer):
company_id = serializers.PrimaryKeyRelatedField(source=company, queryset=Company.objects.all())
class Meta:
model = Item
fields = ('company_id', 'item_num', )
validators = [
UniqueTogetherValidator(
queryset=Item.objects.all(),
fields=('company_id', 'item_num')
)
]

In Django, how to update other fields (in another table) in serializers.py?

I am beginner to django framework and am trying to update a foriegn key reference of my content, basically, there is a Users model as below
models.py
class Users(models.Model):
usr_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
class Member(models.Model):
mem_id = models.AutoField(primary_key=True)
usr_id = models.ForeignKey(Users)
mem_details = models.CharField(max_length=100)
With the above in the models in place, I am trying to create a serializer
with the aim that, whenever I create a instance of User table(instance), I should also update Member table details. Also,
in the serializer I am trying to do as below for the Member, but not working.
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = Users
fields = ('usr_id','name'....) #have removed other fields
class MemberSerializer(serializers.ModelSerializer):
usr_id = UserSerializer()
class Meta:
model = Member
fields = ('mem_id','usr_id','name'...)#removed other fields
this is not taking UserSerializer's usr_id and it not pushing,
Should I need to define override create method here??? Please help...
Error am getting currently:
"error": {
"usr_id": [
"This field is required."
]
},
Found answer myself, though the answer is very easy...
i.e.. Output of one response say from Users Serializers, should be made as input of the other in the views.py file
serializer.save()
serializer_mem = MemberSerializer(data=serializer.data)
if serializer_mem.is_valid():
.....

Django Rest update many to many by id

I'm novice in python and django rest. But I'm confused. What is the best way to update many to many relation in django rest framework.
I read the docs
http://www.django-rest-framework.org/api-guide/relations/#manytomanyfields-with-a-through-model
By default, relational fields that target a ManyToManyField with a through model specified are set to read-only.
If you explicitly specify a relational field pointing to a ManyToManyField with a through model, be sure to set read_only to True.
So if I have a code
class Master(models.Model):
# other fields
skills = models.ManyToManyField(Skill)
class MasterSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=False)
This will return skills as list of objects. And I don't have a way to update them. As far as I understood Django prefers work with objects vs object id when it comes to M2M. If I work with yii or rails I will work with "through" models. I would like to get skill_ids field. That I could read and write. And I can do this for write operation
class MasterSerializer(serializers.ModelSerializer):
skill_ids = serializers.ListField(write_only=True)
def update(self, instance, validated_data):
# ...
validated_data['skill_ids'] = filter(None, validated_data['skill_ids'])
for skill_id in validated_data['skill_ids']:
skill = Skill.objects.get(pk=skill_id)
instance.skills.add(skill)
return instance
But I cannot make it return skill_ids in field. And work for read and write operations.
A few things to note.
First, you don't have an explicit through table in your example. Therefore you can skip that part.
Second, you are trying to use nested serializers which are far more complex than what you're trying to achieve.
You can simply read/write related id by using a PrimaryKeyRelatedField:
class MasterSerializer(serializers.ModelSerializer):
skills_ids = serializers.PrimaryKeyRelatedField(many=True, read_only=False, queryset=Skill.objects.all(), source='skills')
Which should be able to read/write:
{id: 123, first_name: "John", "skill_ids": [1, 2, 3]}
Note that the mapping from JSON's "skill_ids" to model's "skills" is done by using the optional argument source
I will try to bring some light in terms of design: in Django if you specify the model for a ManyToManyRelation, then the relation field on the model becomes read-only. If you need to alter the associations you do it directly on the through model, by deleting or registering new records.
This means that you may need to use a completely different serializer for the through model, or to write custom update/create methods.
There are some sets back with custom through model, are you sure you're not good enough with the default implementation of ManyToManyFields ?
tl;dr:
For a much simpler, one-liner solution for M2M, I sussed out a solution of the form:
serializer = ServiceSerializer(instance=inst, data={'name':'updated', 'countries': [1,3]}, partial=True)
if serializer.is_valid():
serializer.save()
For a more complete example, I have included the following:
models.py
from django.db import models
class Country(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
class Service(models.Model):
name = models.CharField(max_length=20, null=True)
countries = models.ManyToManyField('Country')
serializers.py
from rest_framework import serializers
from .models import *
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ('name',)
class ServiceSerializer(serializers.ModelSerializer):
class Meta:
model = Service
fields = ('name', 'countries',)
Make sure some dummy service and country instances are created for testing. Then you can update an instance in a function like so:
Update example
# get an object instance by key:
inst = ServiceOffering.objects.get(pk=1)
# Pass the object instance to the serializer and a dictionary
# Stating the fields and values to update. The key here is
# Passing an instance object and the 'partial' argument:
serializer = ServiceSerializer(instance=inst, data={'name':'updated', 'countries': [1,3]}, partial=True)
# validate the serializer and save
if serializer.is_valid():
serializer.save()
return 'Saved successfully!'
else:
print("serializer not valid")
print(serializer.errors)
print(serializer.data)
return "Save failed"
If you inspect the relevant tables, the updates are carried through including to the M2M bridging table.
To extend this example, we could create an object instance in a very similar way:
### Create a new instance example:
# get the potential drop down options:
countries = ['Germany', 'France']
# get the primary keys of the objects:
countries = list(Country.objects.filter(name__in=countries).values_list('pk', flat=True))
# put in to a dictionary and serialize:
data = {'countries': countries, 'name': 'hello-world'}
serializer = ServiceOfferingSerializer(data=data)
I have dealt with this issue for quite some time and I have found that the best way to solve the general problem of updating any many to many field is by working around it.
In my case there is a model called Listing and a user can make a Subscription(the other model) to an instance of the Listing model. The Subscription works with a Generic Foreign Key and the Listing imports the Subscriptions of the users via Many2Many.
Instead of making a PUT request to the Listing Model via API, I simply add the Subscription instance to the right model in the POST Method of the API View of Subscription. Here is my adjusted code:
#Model
class Listing(models.Model):
#Basics
user = models.ForeignKey(settings.AUTH_USER_MODEL)
slug = models.SlugField(unique=True, blank=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
#Listing
title = models.CharField(max_length=200)
price = models.CharField(max_length=50, null=True, blank=True)
subscriptions = models.ManyToManyField(Subscription, blank=True)
class Subscription(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
timestamp = models.DateTimeField(auto_now_add=True)
#Views
class APISubscriptionCreateView(APIView): #Retrieve Detail
def post(self, request, format=None):
serializer = SubscriptionCreateSerializer(data=request.data)
if serializer.is_valid():
sub = serializer.save(user=self.request.user)
object_id = request.data['object_id']
lis = Listing.objects.get(pk=object_id)
lis.subscriptions.add(sub)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I hope this will help, it took me a while to figure this out

DjangoRestFramework - Omit null fields when serializing objects

This is my Model:
class Post(models.Model):
user = models.ForeignKey(User)
post = models.CharField(max_length=400)
country = models.ForeignKey(Country, blank=True, null=True)
and this is my serializer:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('user', 'post', 'country',)
def create(self, validated_data):
post = Post(
user = User.objects.get(username='MyUser'),
post = validated_data['post'],
)
if validated_data.get('country', None):
post.country = validated_data['country']
return post
Is there any way for me to tell DRF that if the value of the field is null (because the 'country' field is optional and sometimes not provided) then to skip it and just serialize the other data? Or at least serialize it with a value of None?
I don't think I can use SerializerMethodField (http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield) because the 'country' field is not a read-only field (I write too it too, if it is provided).
I basically want to omit the field (or at least make the value None) when serializing an object If the field is null.
As of DRF 3.2.4, so long as you add
blank=True
to the models field like so:
class Post(models.Model):
country = models.ForeignKey(Country, blank=True)
then DRF will treat the field as optional when serializing and deserializing it (Note though that if there is no null=True on the model field, then Django will raise an error if you try to save an object to the database without providing the field).
See the answer here for more information: DjangoRestFramework - correct way to add "required = false" to a ModelSerializer field?
If you are using pre-DRF 3.2.4, then you can override the field in the serializer and add required=False to it. See the documentation here for more information on specifying or overriding fields explicitily: http://www.django-rest-framework.org/api-guide/serializers/#specifying-fields-explicitly
So something like this (Note that I did not fully test the code below but it should be something along these lines):
class PostSerializer(serializers.ModelSerializer):
country = serializers.PrimaryKeyRelatedField(required=False)
class Meta:
model = Post
fields = ('user', 'post', 'country',)
This thread might be useful:
https://stackoverflow.com/a/28870066/4698253
It basically says that you can override the to_representation() function with a slight modification.
I would have put this in the comments but I don't have enough points yet :(
Use allow_null=True:
allow_null - If set to True, the field will accept values of None or the empty string for nullable relationships. Defaults to False.
serializers.py
class PostSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(allow_blank=True)
class Meta:
model = Post

Issues with OneToOne Field in Serializer in django rest framework

I have model UserProfile related with User Model via one-to-one relationship.
UserProfileSerializer is defined correctly and it serializes userprofile object well.
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
country = models.CharField(max_length=255)
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('user','country')
But it gives error {'user':['This field is required']} on passing data.
>>> s = UserProfileSerializer(data = {'user':1,'country':'YY'} )
>>> s.is_valid()
False
>>> s.errrors
{'user':['This field is required']}
This might be too late to help, and I'm not sure exactly what you're trying to do, but try setting the user field in your serializer to use a PrimaryKeyRelatedField so that you can represent a User with an ID, or set the user field to be readonly if you only want to update UserProfile things. If you don't want to use a primary key, other relationship fields are here.
If you change the user field to be a PrimaryKeyRelatedField and you want the user data to return as it is now, you might want to make two UserProfile serializers - one for write operations, and one for reading. After a create or an update, you can switch to the read serializer to populate the response.

Categories