I'm not really sure how I'm able to access the data of a nested serializer, with a one-to-many relation.
Here's my models:
class Album(models.Model):
id = models.CharField(max_length=255, null=True, blank=True)
name = models.CharField(max_length=255, null=True, blank=True)
class Title(models.Model):
name = models.CharField(max_length=255, null=True, blank=True)
album = models.ForeignKey(
Album,
related_name='titles'
)
then I have 2 serializers:
class AlbumSerializer(serializers.ModelSerializer):
titles = TitleSerializer(many=True)
class Meta:
model = Album
fields = ['id', 'name', 'titles']
def create(self, validated_data):
album = Album.objects.create(
id=validated_data.get('id'),
name=validated_data.get('name')
)
titles = validated_data.pop('titles')
for title in titles:
title['album'] = album
_title = Title(**title)
_title.save()
return album
class TitleSerializer(serializers.ModelSerializer):
class Meta:
model = Title
fields = ['name']
To deserialize and save I run the following:
album = AlbumSerializer(data=input_json)
album.is_valid()
album.save()
my problem is now, that I'm unable to access the items. Accessing the type of album.instance.titles gets me <class 'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager'>.
How can I get the titles out of it, or what am I doing wrong that I do not get a list of Titles in there?
You need to call all() on a RelatedManager to execute the DB query and get the results
album.instance.titles.all()
A RelatedManager is just like a normal model manager (Model.objects) that retrieves objects filtered by the relation
Related
I have a model like this:
class Cart(models.Model):
id = models.UUIDField(primary_key=True, default=uuid4)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, null=True)
class Reception(models.Model):
PAYMENT_STATUS_PENDING = 'P'
PAYMENT_STATUS_COMPLETE = 'C'
PAYMENT_STATUS_FAILED = 'F'
PAYMENT_STATUS_CHOICES = [
(PAYMENT_STATUS_PENDING, 'Pending'),
(PAYMENT_STATUS_COMPLETE, 'Complete'),
(PAYMENT_STATUS_FAILED, 'Failed')
]
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, null=True)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
entry_date = models.DateField()
total_price = models.IntegerField()
payment_status = models.CharField(
max_length=1,
choices=PAYMENT_STATUS_CHOICES,
default=PAYMENT_STATUS_PENDING
)
My question is:
How can I get a particular Cart record from the Reception model?
I tried using this serializer:
class ReceptionSerializer(serializers.ModelSerializer):
class Meta:
model = Reception
fields = ['id', 'customer', 'entry_date', 'payment_status', 'cart']
but it only returns the id of a cart. I want to return the whole object of that specific cart.
How can I do that?
If you want to utilize nested serialization, one possible solution would be to define CartSerializer and override the cart field of the ReceptionSerializer class as follows
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = Cart
fields = ['id', 'customer']
class ReceptionSerializer(serializers.ModelSerializer):
cart = CartSerializer(many=True)
class Meta:
model = Reception
fields = ['id', 'customer', 'entry_date', 'payment_status', 'cart']
However, please note that the cart field will become read-only this way.
I have a serializer in my Django app that is meant for updating a nested object. Updating works, but I'm facing another problem: I can't delete objects that are not in validated_data['events] because I don't have the id to be compared with my instance id's.
For reference, these are my Models:
class Plan(models.Model):
planId = models.CharField(primary_key=True, max_length=100, unique=True)
name = models.CharField(max_length=200)
class PlanEvent(models.Model):
plan = models.ForeignKey(Plan, on_delete=models.CASCADE)
id = models.CharField(primary_key=True, max_length=100, unique=True, blank=False, null=False)
done = models.BooleanField()
title = models.CharField(max_length=100, blank=True)
This is my PlanEventUpdateSerializer:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = PlanEvent
fields = ('done', 'title')
Is there some way to include the id, so I could compare the id's like this in my update method:
class PlanUpdateSerializer(serializers.ModelSerializer):
events = PlanEventUpdateSerializer(many=True)
class Meta:
model = Plan
fields = ('name',)
....
def update(self, instance, validated_data):
events_validated_data = validated_data.pop('events')
events = (instance.events.all())
events = list(events)
event_ids = [item['id'] for item in events_validated_data]
for event in events:
if event.id not in event_ids:
event.delete()
I found a solution. I defined the id as a optional field in the serializer and then I was able to include it in the fields. Sending POST and PUT requests works now and I'm also able to delete objects when updating:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
id = serializers.CharField(source='pk', required=False)
class Meta:
model = PlanEvent
fields = ('id', 'done', 'title')
I want to post a movie into the collection's movie field( list of movies).
I define the model as
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ForeignKey(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
this is how i am using the viewset
class CollectionViewSet(viewsets.ModelViewSet):
queryset = models.Collection.objects.all()
serializer_class = serializers.CollectionSerializer
but i am not able to enter values for the movie field
enter image description here
also my serializer
class CollectionSerializer(serializers.ModelSerializer):
class Meta:
model = Collection
fields = '__all__'
By default, DRF will represent the relationship with a PrimaryKeyRelatedField, thus expecting a movie ID.
To achieve what you want (create an instance of movie with a collection), you need to overwrite the foreign key field in your serializer with your own Movie serializer.
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer()
class Meta:
model = Collection
fields = '__all__'
def create(self, validated_data):
movie = validated_data.pop('movie')
movie = Movie .objects.create(**movie )
collection = Collection.objects.create(movie=movie, **validated_data)
return collection
You need to overwrite the create method so when creating a Collection, you also create a movie.
However, I am not sure the foreign key is set in the right model in your model. (a movie belongs to many collection but not the other way around?) If that's not what you want, just reverse the logic for the serializer.
Edit:
Sending the following should work fine:
{ "uuid": "1001",
"title": "Action",
"description": "Action Movies",
"movie": { "title": "The Burkittsville 7",
"description": "The story of Rustin Parr.",
"genres": "Horror",
"uuid": "5e904"
}
}
The only problem as I mentionned earlier is in your model you defined the foreign key field in collection. So it expects one single movie instance and not a list, thus I took off the brackets you put around movie. Maybe you should consider setting the foreign key in the Movie model, or use a Many to many relationship.
#models.py
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ManyToManyField(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
serializers.py:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer(read_only=True, many=True)
class Meta:
model = Collection
fields = '__all__'
hope this will give you better unserstand this will work for you
Looking for solution of this problem I encountered some similar threads, but referring to older versions of Django/DRF and thus not working in my case.
There are these two models:
class CsdModel(models.Model):
model_id = models.CharField("Item ID", max_length=8, primary_key=True)
name = models.CharField("Item Name", max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class CsdListing(models.Model):
model_id = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='m_id')
name = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='m_name')
(...)
EDIT: Serializers are defined this way:
class CsdModelSerializer(serializers.ModelSerializer):
model_id = serializers.RegexField(regex='^\w{2}\d{3}$', allow_blank=False)
name = serializers.CharField(min_length=6, max_length=50, allow_blank=False)
class Meta:
model = CsdModel
fields = '__all__'
class CsdListingSerializer(serializers.ModelSerializer):
session_id = serializers.RegexField(regex='^s\d{2}$', allow_blank=False)
def validate_session_id(self, value):
(...)
class Meta:
model = CsdListing
fields = '__all__'
What I'd like to see, is model_id and name from CsdModel displayed inside a form created based on CsdListing model. But instead, the ID is duplicated:
How should I rebuild the model(s) to have both ID and name displayed in the form?
You should have only one foreign key. But the listing serializer should then reference the model as a nested serializer.
class CsdListing(models.Model):
model = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='listing')
class CsdListingSerializer(serializers.ModelSerializer):
model = CsdModelSerializer()
session_id = serializers.RegexField(regex='^s\d{2}$', allow_blank=False)
I have the following models:
class Category(models.Model):
name = models.CharField()
... # fields omitted
class Prediction(models.Model):
conversation = models.ForeignKey(Conversation)
category = models.ForeignKey(Category)
... # fields omitted
class Conversation(models.Model):
sid = models.CharField()
... # fields omitted
Now I'm trying to create a model serializer for Category that would return me the following serialized object:
{
"name":"blah",
"conversations":[
"2af22188c5c97256", # This is the value of the sid field
"073aef6aad0883f8",
"5d3dc73fc8cf34be",
]
}
Here is what I have in my serializer:
class CategorySerializer(serializers.ModelSerializer):
conversations = serializers.SlugRelatedField(many=True,
read_only=True,
source="prediction_set",
slug_field='conversation.sid')
class Meta:
model = models.Class
fields = ('class_name', 'conversations')
However, this doesn't work because somehow django doesn't allow me to set slug_field to be a field that's within an object field. Any suggestions on how to accomplish this?
You are modelling a Many-to-Many relationship between Categorys and Conversations with a explicit table called Prediction. The django way of doing this would be to explicitly state the Many-to-Many on either side of the relationship and specify Prediction as the "through-model":
Shamelessly stolen example from this question:
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=255, blank=True,default=None)
desc = models.TextField(blank=True, null=True )
...
class Post(models.Model):
title = models.CharField(max_length=255)
pub_date = models.DateTimeField(editable=False,blank=True)
author = models.ForeignKey(User, null=True, blank=True)
categories = models.ManyToManyField(Category, blank=True, through='CatToPost')
...
class CatToPost(models.Model):
post = models.ForeignKey(Post)
category = models.ForeignKey(Category)
...
This shows a good way to set up the relationship.
Equally shamelessly stolen from the answer by #Amir Masnouri:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('name','slug')
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id','{anything you want}','categories')
depth = 2
This shows a nice way of achieving the nested serialization behavior that you would like.