I'm attempting to build a nested relationship using Django Rest Framework 3.0. I've created my serializers and have attempted to override the create() function. My models are defined as follows:
class Item(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
name = models.CharField(max_length=200)
description = models.CharField(max_length=1000)
categories = models.ManyToManyField(Category, null=True, blank=True)
class Price(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
item = models.ForeignKey(Item, related_name='prices')
name = models.CharField(max_length=100)
cost = models.FloatField()
As you'll note, I can have multiple prices for my items. My serializers are defined as follows:
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
owner = serializers.Field(source='owner.username')
exclude = ('user',)
class ItemSerializer(serializers.ModelSerializer):
prices = PriceSerializer(many=True, required=False)
categories = CategorySerializer(many=True, required=False)
class Meta:
model = Item
owner = serializers.Field(source='owner.username')
fields = ('id', 'name', 'description', 'prices', 'categories')
def create(self, validated_data):
user = validated_data.get('user')
# Get our categories
category_data = validated_data.pop('categories')
# Create our item
item = Item.objects.create(**validated_data)
# Process the categories. We create any new categories, or return the ID of existing
# categories.
for category in category_data:
category['name'] = category['name'].title()
category, created = Category.objects.get_or_create(user=user, **category)
item.categories.add(category.id)
item.save()
return item
When I try and POST a new item:
{
"name": "Testing",
"description": "This is a test",
"categories": [
{
"name": "foo"
},
{
"name": "bar"
}
],
"prices": [
{
"name": "Red",
"cost": 10
}
]
}
I get the following error:
{
"prices": [
{
"item": [
"This field is required."
]
}
]
}
Presumably because the Price serializer has no idea what the ID of the new item is. I've tried overriding this functionality in the create() function of my serializer, but it appears as though the serializer's validation is being hit before I have the opportunity to create the item and associate it with the price.
So - How do I create a new item, get the item ID, and then create each of the new prices?
The problem is that your PriceSerializer is looking for the item key because it is specified on the Price model. This isn't immediately obvious because you are using Meta.exclude instead of Meta.fields.
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
exclude = ('user',)
Is the same as writing
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
fields = ('id', 'item', 'name', 'cost', )
Which makes it very clear what your issue is. Because your item field on the model does not have empty=True (or null=True) set, Django REST Framework automatically generates it as a PrimaryKeyRelatedField with required=True. This is why you are getting the This field is required is required error, because Django REST Framework cannot automatically detect that it is coming from a parent serializer which already has that field.
You can get around this by removing the field from the serializer, as it doesn't appear to ever be needed.
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = Price
fields = ('id', 'name', 'cost', )
This will no longer display the item field though, but I suspect this isn't actually an issue for you.
Related
: purchased_products = validated_data.pop("products") KeyError: 'products'
I have a M2M relationship between Product and Purchase. What I am trying to achieve is when a purchase is made, to also fill the PurchasedProduct(the through model) model. But every time I send the data to the API and I try to access the products key in the serializer from the validated_data a keyError exception is thrown but if I return the validated_data for the purpose of debugging the product key is part of the response.
djangorestframework==3.11.0
django==2.2.10
class Product(Model):
name = CharField(max_length=20, unique=True)
date_added = DateTimeField(default=now)
models.py
class Purchase(Model):
manager = ForeignKey('users.User', on_delete=PROTECT, related_name='purchases')
quantity = DecimalField(max_digits=6, decimal_places=2)
products = ManyToManyField('branches.Product', through='PurchasedProduct',
through_fields=('purchase', 'product'))
amount_fc = IntegerField(default=0)
amount_usd = IntegerField(default=0)
total_amount = IntegerField(default=0)
date_purchased = DateTimeField(default=now)
class PurchasedProduct(Model):
purchase = ForeignKey('Purchase', on_delete=CASCADE, related_name="to_products", blank=True)
product = ForeignKey('branches.Product', on_delete=CASCADE, related_name='purchases')
unit_price = DecimalField(max_digits=12, decimal_places=4, default=0.00)
quantity = DecimalField(max_digits=5, decimal_places=2)
amount_fc = DecimalField(max_digits=10, decimal_places=2)
date_purchased = DateTimeField(default=now)
serializer.py
class PurchasedProductSerializer(ModelSerializer):
class Meta:
model = PurchasedProduct
fields = [
"id",
"purchase",
"product",
"unit_price",
"quantity",
"amount_fc",
"date_purchased"
]
class PurchaseSerializer(ModelSerializer):
# https://github.com/encode/django-rest-framework/issues/5403
products = PurchasedProductSerializer(source="to_products", many=True)
class Meta:
model = Purchase
fields = [
"id",
"manager",
"quantity",
"amount_fc",
"amount_usd",
"total_amount",
"products",
"date_purchased"
]
def create(self, validated_data):
purchased_products = validated_data.pop("products")
manager = validated_data.pop('manager')
quantity = validated_data.pop('quantity')
amount_fc = validated_data.pop('amount_fc')
amount_usd = validated_data.pop('amount_usd')
total_amount = validated_data.pop('total_amount')
purchase = Purchase.objects.create(
manager=manager,
quantity=quantity,
amount_fc=amount_fc,
amount_usd=amount_usd,
total_amount=total_amount
)
for purchased_product in purchased_products:
product = Product.objects.get(pk=purchased_product.pop("product"))
purchase.products.add(product, through_default=purchased_product)
return purchase
view.py
class PurchasesListView(ListCreateAPIView):
queryset = Purchase.objects.all()
serializer_class = PurchaseSerializer
permission_classes = [AllowAny]
filterset_fields = ['date_purchased', 'manager']
data
{
"amount_fc": 13303340.0,
"amount_usd": 1500,
"manager": 2,
"quantity": 100,
"total_amount": 1230945,
"products": [
{
"amount_fc": 1200334,
"product": 8,
"quantity": 120,
"unit_price": 10003.34
},
{
"amount_fc": 1600334,
"product": 6,
"quantity": 100,
"unit_price": 16003.34
}
]
}
Error:
purchased_products = validated_data.pop("products") KeyError: 'products'
But when I change purchased_products = validated_data.pop("products") to purchased_products = validated_data.pop("products", []) It works but it does not fill the through model(PurchasedProduct)
What I have tried
DRF example
DRF doc exampe
drf-writable-nested
drf-writable-nested does not support M2M with a through model
removing source
products = PurchasedProductSerializer(many=True) added write_only=True read_only=False. I have also tried to suppress the uniqueValidator on the Product model but still does not work.
After a bit of googling plus some try and error. I found two ways of solving this problem.
the first one is to get the key directly from self.context.
purchased_products = self.context['request'].data['products']
and it works fine. But I still don't understand why when trying to get products from validated_data it's throwing KeyError.
the second option is to use drf-nested.
shout out to the team.🎊🎊🎉🎉
the package provides an implementation of .create() and .update() and many more for your serializer and the most important thing is they support M2M relationships with a through model. their implementation examples
This question already has answers here:
Include intermediary (through model) in responses in Django Rest Framework
(4 answers)
Closed 3 years ago.
I have a M2M relationship between the Entity and EntityGroup and I want to save the corresponding entity index to the EntityGroup just like an entity array to database.
Since I used a custom through Model with additional field index inside, I need to serialize the index to the corresponding entity to the response, how should I implement that?
I'm new to django and django-rest-framework and it seems there are not similar M2M examples after a few google search. Here is my thought, a serializer could only serialize the only one model's fields with ForeignKey relationship, a viewset could have a custom model based on queryset which could merge the relationships for a few models. So I need to implement a more expandable viewset with a custom queryset inside?
Here is my code:
models.py
class Entity(models.Model):
uuid = models.CharField()
name = models.CharField()
class EntityGroup(models.Model):
name = models.CharField()
entities = models.ManyToManyField(Entity,
through='EntityGroupRelationship',
through_fields=('group', 'entity'),
related_name='groups'
)
class EntityGroupRelationship(models.Model):
entity = models.ForeignKey(Entity, on_delete=models.CASCADE)
group = models.ForeignKey(EntityGroup, on_delete=models.CASCADE)
index = models.PositiveIntegerField()
serializers.py
class EntitySerializer(serializers.ModelSerializer):
class Meta:
model = Entity
fields = '__all__'
class EntityGroupRelationshipSerializer(serializers.ModelSerializer):
class Meta:
model = EntityGroupRelationship
fields = '__all__'
class EntityGroupSerializer(serializers.ModelSerializer):
entities = EntitySerializer(many=True)
class Meta:
model = EntityGroup
fields = '__all__'
views.py
class EntityGroupViewSet(BaseModelViewSet):
queryset = EntityGroup.objects.all()
serializer_class = EntityGroupSerializer
class EntityGroupRelationshipViewSet(BaseModelViewSet):
queryset = EntityGroupRelationship.objects.all()
serializer_class = EntityGroupRelationshipSerializer
Current response
[
{
"id": 1,
"entities": [{
"id": 1,
"name": "",
}]
},
...
]
Expected respponse
[
{
"id": 1,
"entities": [
{
"index": 1,
"info": {
"id": 1,
"name": "",
}
}
]
},
...
]
You are using incorrect serializer for that. Use EntityGroupRelationshipSerializer instead of EntitySerializer. Also you need to pass correct fields
class EntitySerializer(serializers.ModelSerializer):
class Meta:
model = Entity
fields = '__all__'
class EntityGroupRelationshipSerializer(serializers.ModelSerializer):
entity = EntitySerializer()
class Meta:
model = EntityGroupRelationship
fields = ('index', 'entity')
class EntityGroupSerializer(serializers.ModelSerializer):
entities = EntityGroupRelationshipSerializer(many=True) # here should be EntityGroupRelationshipSerializer
class Meta:
model = EntityGroup
fields = '__all__'
I'm trying to use the Django Rest-Framework to produce some JSON that shows all the user's posts, but also shows the images for that post. Image is a foreign key to Post. Here are the models:
models.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=200)
class Image(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
img = models.CharField(max_length=120)
views_count = models.IntegerField(default=0)
views.py
class GetPosts(ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
requested_user = get_requested_user(self)
return Post.objects.filter(user=requested_user).order_by('-created_at')
def get_requested_user(self):
filter_kwargs = {'username': self.kwargs['username']}
return get_object_or_404(User.objects.all(), **filter_kwargs)
serializers.py
class PostSerializer(serializers.ModelSerializer):
image_img = serializers.RelatedField(source='Image', read_only=True)
class Meta:
model = Post
fields = ('status', 'image_img ')
In the serializers.py, I'd like to show all of the fields for Image (img, views_count) What I get with my current code is this:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1"
}
]
}
Which contains the user's posts, but not the user's posts and each post's images. Note: Query url looks like this: /api/posts/user/
You should use Nested serializer here,
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('img',)
class PostSerializer(serializers.ModelSerializer):
image_img = ImageSerializer(source='image_set', many=True)
class Meta:
model = Post
fields = '__all__'
Hence the response will be like,
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1",
"image_img": [
{"img": "image_url"},
{"img": "image_url"},
....
]
}
]
}
How to display all field of model class in serializer?
From the doc,
You can also set the fields attribute to the special value '__all__' to indicate that all fields in the model should be used.
Reference
1. DRF- Nested Realtions
2. source argument
3. Specifying which fields to include
In my app which use DRF, I want to use model serializer with multiple related objects.
models.py:
class JobType(models.Model):
name = models.CharField(null=False, max_length=250)
class Offer(models.Model):
user = models.ForeignKey(User, null=False)
job_type = models.ForeignKey(JobType, null=False)
salary = models.DecimalField(null=False, max_digits=8,
decimal_places=2)
serializers.py:
class JobTypeSerializer(serializers.ModelSerializer):
class Meta:
model = JobType
fields = ('id', 'name')
class OfferSerializer(serializers.ModelSerializer):
job_type = JobTypeSerializer()
class Meta:
model = Offer
fields = (
'salary', 'job_type', 'user'
)
views.py:
class SalaryViewSet(viewsets.ModelViewSet):
queryset = Salary.objects.all()
serializer_class = SalaryFullSerializer
What I want to achieve:
when I do GET request on my api/offers I want to have something like:
[
{
"salary": 1000,
"user: 1,
"job_type": {
"id": 1,
"name": "Developer",
}
}
]
so, basically, when GET offers is made, I want to have nested related object with all it's properties.
On other hand, when POST offers is made, I want to limit JobType choices.
When I've removed job_type = JobTypeSerializer() from OfferSerializer I had nice dropdown with available choices (in DRF debug). But it caused that GET on offers returned only JobOffer's ID in results.
How can I achieve desired behavior?
You can use different serializer for POST and GET requests.
Override get_serializer_class
def get_serializer_class(self):
if self.request.method == 'POST':
return SalaryPostSerializer
return SalaryFullSerializer
Related to this Topic
Hi,
I cannot follow the answer at the attached topic, because an ID is missing after serialization.
Model.py
class Owner(models.Model):
name = models.CharField(db_index=True, max_length=200)
class Car(models.Model):
name = models.CharField(db_index=True, max_length=200)
LCVS = models.ForeignKey(Owner)
View.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
Serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = OwnerSerializer()
class Meta:
model = Car
fields = ('id', 'name', 'owner')
def create(self, validated_data):
tmp_owner = Owner.objects.get(id=validated_data["car"]["id"])
car = Car.objects.create(name=self.data['name'],owner=tmp_owner)
return car
Now i send the following request :
Request URL:http://localhost:9000/api/v1/cars
Request Method:POST
Request Paylod :
{
"name": "Car_test",
"ower": {
"id":1,
"name": "Owner_test"
}
}
But, here the validated_data don't contain the owner ID !
Traceback | Local vars
validated_data {u'Owner': OrderedDict([(u'name', u'Owner_test')]), u'name': u'Car_test'}
#Kevin Brown :
Workful ! Thanks
I'll validate your answer but I get a new problem...
Now when I try to put a new Owner, an error raise :
{
"id": [
"This field is required."
]
}
I had to create a new serializer ?
Any AutoFields on your model (which is what the automatically generated id key is) are set to read-only by default when Django REST Framework is creating fields in the background. You can confirm this by doing
repr(CarSerializer())
and seeing the field generated with read_only=True set. You can override this with the extra_kwargs Meta option which will allow you to override it and set read_only=False.
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
"id": {
"read_only": False,
"required": False,
},
}
This will include the id field in the validated_data when you need it.