DRF - Nested Serialization Of m2m fields with through model - python

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
)

Related

How can I restrict the list of objects in API view once an object has been added to a relationship?

I am working in django-rest-framework and I have three models: Event, Performer, and Link. I have many-to-many relationships established on the Event and Performer models as 'links' pointing to the Link model. In the API view, when I am creating or updating an event or performer, I am given a list of all links. I would like them to be removed as options once they've been associated with another object, but I can't seem to figure out how to. Below is my code:
class Link(models.Model):
created = models.DateTimeField(auto_now_add=True)
address = models.URLField()
def __str__(self):
return f"{self.address}"
class Meta:
ordering = ['created']
class Performer(models.Model):
created = models.DateTimeField(auto_now_add=True)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
links = models.ManyToManyField(Link)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Meta:
ordering = ['created']
class Event(models.Model):
created = models.DateTimeField(auto_now_add=True)
sale_date = models.DateTimeField()
event_date = models.DateTimeField()
performer = models.ForeignKey(Performer, on_delete=models.CASCADE)
links = models.ManyToManyField(Link)
class Meta:
ordering = ['event_date']
and I'm using this for serializers:
class LinkSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Link
fields = ['url', 'address']
class PerformerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Performer
fields = ['url', 'first_name', 'last_name', 'links']
class EventSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Event
fields = ['url', 'performer', 'sale_date', 'event_date', 'links']
I thought about using
ManyToManyField.limit_choices_to
but I don't know what my selector would look like. I also thought I could use
Link.objects.exclude(...)
or
Link.objects.filter(...)
call somewhere but I just don't know where. Thanks to anyone who can help!
Edit: thought I’d add that what I thought would work is to use ‘limit_choices_to’ to filter out any links that are included in a relationship, but I couldn’t figure out how to test if an object was in a relationship (and since there’s multiple relationships only testing for one isn’t perfect either)
You should make use of the Serializer class' get_queryset method:
class LinkSerializer(serializers.HyperlinkedModelSerializer):
def get_queryset(self):
return super().get_queryset().filter(performer=None, event=None)
class Meta:
model = Link
fields = ['url', 'address']
I figured out what I was trying to accomplish with this: I needed to restrict the choices for the field at the model level, which I was able to do by passing a predetermined restriction to the 'limit_choices_to=' parameter. See code below and thank you to #anthony2261 for the suggestion, your filter section helped me to understand how to filter even though it wasn't the type of filtering I needed!
# create a dict of filter conditions(?)
restrict_choices = {'performer': None, 'event': None}
class Performer(...):
...
# refer to the restriction defined previously
# when defining the links relationship.
links = models.ManyToManyField(Link, limit_choices_to=restrict_choices)

AttributeError when trying to created nested serializer in Django REST Framework

I have two models of sets, and cards. Theses models have one to many relationship where there are many cards in a single set. These model are join by a foreign key - each card has set_id which is equal to the id of the set. These IDs are UUID.
I am trying to create a serializer using Django REST Framework where I return the details of the set, as well as including all the cards that are part of the set.
error
Got AttributeError when attempting to get a value for field `cards` on serializer `SetSerializers`.
The serializer field might be named incorrectly and not match any attribute or key on the `Set` instance.
Original exception text was: 'Set' object has no attribute 'cards'.
serializers.py
class CardSerializers(serializers.ModelSerializer):
class Meta:
model = Card
fields = ['id', 'number', 'name', 'set_id']
class SetSerializers(serializers.ModelSerializer):
cards = CardSerializers()
class Meta:
model = Set
fields = ['id', 'code', 'name', 'releaseDate','cards']
models.py
class Set(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...
objects = models.Manager()
def __str__(self):
return self.name
class Card(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...
set = models.ForeignKey(Set, on_delete=models.CASCADE, related_name='Cards', related_query_name='Cards')
objects = models.Manager()
def __str__(self):
return self.name
views.py
class SetsIndividualData(ListAPIView):
serializer_class = SetSerializers
def get_queryset(self):
setCode = self.kwargs.get('setCode')
queryList = Set.objects.filter(code=setCode.upper())
return queryList
There are a few mistakes in your code.
models.py:
In the Card model, the related_name of the FK should be lowercase as per below:
set = models.ForeignKey(Set, related_name="cards", on_delete=models.CASCADE)
serializers.py:
In your SetSerializers you had the right idea, but you need to be more specific with your CardSerializers, as you are describing a "to-many" relationship.
Please refer to the docs here: https://www.django-rest-framework.org/api-guide/relations/#nested-relationships
Moreover, from your views.py I can see you will only send GET requests, therefore you can have your cards to be read only.
So the serializer card attribute would look like the below:
cards = CardSerializers(many=True, read_only=True)
views.py:
It looks like you want to retrieve a set by id.
ListAPIView is the wrong generics, you should use RetrieveAPIView instead, as it provides a get method and is used to retrieve a single model instance.
Please refer to the docs for more information: https://www.django-rest-framework.org/api-guide/generic-views/#retrieveapiview

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

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