django rest framework - foreign key fields are read only - python

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

Related

How to add 'id' to the fields in HyperlinkedModelSerializer in DRF

When using the HyperlinkedModelSerializer from Django REST Framework, the field id is not included in the fields by default. This question has an answer that explains that well.
However I have a problem I'd like to solve in a particular way.
I have a model with custom ID and few dozens of other fields:
class Foo(models.Model):
id = models.IntegerField(primary_key=True)
# 20-30 fields
In the serializers.py I'd like to include all fields from the model:
class FooSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Foo
fields = '__all__'
However this doesn't include the field id. Defining id = serializers.ReadOnlyField() doesn't help me either, as id shoud be editable.
Specifying all the fields manually like this:
fields = ('id', # all other fields)
would be a solution I'm trying to circumvent because the model class has a lot of fields and they might change in future.
Is there an elegant possibility to add the field id? Maybe overriding the __init__ method?
Add id attribute in FooSerializer serializer as:
class FooSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = Foo
fields = '__all__'
You can create your custom HyperlinkedModelSerializer and override get_default_field_names to include the id like normal ModelSerializer does.
Example:
class CustomHyperlinkedModelSerializer(HyperlinkedModelSerializer ):
def get_default_field_names(self, declared_fields, model_info):
return (
[model_info.pk.name] +
[self.url_field_name] +
list(declared_fields) +
list(model_info.fields) +
list(model_info.forward_relations)
)
Note : this is just an idea. I have not tested it yet.
HyperlinkedModelSerializer doesn't include the id field by default. You can include id by adding it to the serializer as an attribute like this:
class FooSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField()
class Meta:
model = Foo
fields = '__all__'

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

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
)

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

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