DjangoRestFramework - Omit null fields when serializing objects - python

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

Related

How to use model field in it's serializer

I would like to have my own error messages, which I've implemented in serializer like this:
class TransactionsValuesSerializer(serializers.ModelSerializer):
class Meta:
model = Translations
fields = ('id', 'value')
extra_kwargs = {"value": {"error_messages": {"blank": f"Error"}}}
It's model
class Translations(models.Model):
class Meta:
db_table = 'merchants__translations'
value = models.TextField()
key = models.ForeignKey(
TranslationsKeys,
on_delete=models.CASCADE,
related_name='translations'
)
translation_language = models.ForeignKey(
TranslationLanguages,
on_delete=models.CASCADE,
related_name='translations'
)
Now, if user do not enter some of the fields for translations, it will show error message 'Error'.
Image
Is there a way to output error message like 'Error in {key}'?
I never saw that usage of extra_kwargs for this. Maybe you should go like this.
Serializers already raise an error for required fields when they're empty. It shows the field name automatically.
The default behaviour is already required=True for serializer fields.
So, I think your serializer is good for now. Then, when you make a request without including value field, serializer should raise something like this.
HTTP 400 BAD REQUEST
{'value': ['This field may not be blank.']}
I think you already have the key name value which is what you want.

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

DRF - Nested Serialization Of m2m fields with through model

I am using a m2m field with a through model in DRF. Everything is working fine, EXCEPT when I try to nest the membership serializer.
models.py
class SweepStakes(models.Model):
name = models.CharField(max_length=255)
class Event(models.Model):
sweepstakes = models.ManyToManyField(SweepStakes, through='EventSweepStakesMembership')
class EventSweepStakesMembership(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
sweepstakes = models.ForeignKey(SweepStakes, on_delete=models.CASCADE)
enabled = models.BooleanField(default=False)
serializers.py
class EventSweepStakesSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source='sweepstakes.name')
class Meta:
model = EventSweepStakesMembership
fields = ('name', 'enabled',)
class EventSerializer(BaseTenantSerializer):
sweepstakes = EventSweepStakesSerializer(many=True, read_only=True)
class Meta:
model = Event
fields = ('sweepstakes',)
At this point, if I hook the EventSweepStakesMembership model and the EventSweepStakesSerializer up to a view, I get back exactly what I expect, output like this:
{"name": "thingy", "enabled" true}
However, when I hook the Event model and EventSerializer serializer into a view, the sweepstakes field returns an empty dictionary instead of the nested representation, like so:
{"sweepstakes": [{}]}
Note that it is NOT an empty array, in other words it does see the related through model, but simply does not serialize it correctly when displaying.
There is no error, it is just empty. I have tried increasing the depth of the Event serializer to no avail.
Am I missing something or maybe even going about this all wrong?
Thanks!
Got it, thanks to this answer:
https://stackoverflow.com/a/17263583/1366989
The missing element here was the source kwarg on the EventSerializer. So, it now looks like this, and is working as expected:
class EventSerializer(BaseTenantSerializer):
sweepstakes = EventSweepStakesSerializer(
source='eventsweepstakesmembership_set', many=True, read_only=True
)

How does Django Rest Framework deserialize foreign key relationships?

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.

How to display all model fields with ModelSerializer?

models.py:
class Car():
producer = models.ForeignKey(Producer, blank=True, null=True,)
color = models.CharField()
car_model = models.CharField()
doors = models.CharField()
serializers.py:
class CarSerializer(ModelSerializer):
class Meta:
model = Car
fields = Car._meta.get_all_field_names()
So, here I want to use all fields. But I have an error:
Field name producer_id is not valid for model Car.
How to fix that?
Thanks!
According to the Django REST Framework's Documentation on ModelSerializers:
By default, all the model fields on the class will be mapped to a corresponding serializer fields.
This is different than Django's ModelForms, which requires you to specify the special attribute '__all__' to utilize all model fields. Therefore, all that is necessary is to declare the model.
class CarSerializer(ModelSerializer):
class Meta:
model = Car
Update (for versions >= 3.5)
The behaviour described above was deprecated in version 3.3, and forbidden since version 3.5.
It is now mandatory to use the special attribute '__all__' to use all fields in the Django REST Framework, same as Django Forms:
Failing to set either fields or exclude raised a pending deprecation warning in version 3.3 and raised a deprecation warning in 3.4. Its usage is now mandatory.
So now it must be:
class CarSerializer(ModelSerializer):
class Meta:
model = Car
fields = '__all__'
You could use fields = '__all__' to get all your fields or you could specify if you want a limited number of fields to be returned. See documentation.
But this returns the id value for the foreign key field i.e. producer in your case. To get all the fields for producer, you need to create a serializer class for that too. See here.
So your updated serializers.py should be:
class ProducerSerializer(ModelSerializer):
class Meta:
model = Producer
class CarSerializer(ModelSerializer):
producer= ProducerSerializer(read_only=True)
class Meta:
model = Car
fields = ('producer', 'color', 'car_model', 'doors', )
if you want all fields to be included in the serializer you can use fields = '__all__':
class CarSerializer(serializer.ModelSerializer):
class Meta:
fields = '__all__'
model = Car
But this approach is not recommended. We should always explicitly specify all fields. This is because it gives us control over fields displayed. If we don't want a field's data to be displayed, we can avoid that.
class CarSerializer(serializer.ModelSerializer):
class Meta:
fields = ['name', 'color', 'company', 'price', ]
model = Car

Categories