Nested Serializers with different queryset - python

How to create a nested serializer with it's own queryset?
In the following example I would like to replace an '#api_view' function with a class based view with serializers.
Simplified, I have the following code:
models.py
class Klass(models.Model):
name = models.TextField()
class Pupils(models.Model):
name = models.TextField()
klass = models.ForeignKey(Klass)
class Chapter(models.Model):
"""A Chapter in a school book."""
name = models.TextField()
class TestResult(models.Model):
"""TestResults for a Chapter."""
pupil = models.ForeignKey(Pupil)
chapter = models.ForeignKey(Chapter)
score = models.IntegerField()
view.py
#api_view
def testresults(request, klass_id, chapter_id):
pupils = Pupils.objects.filter(klas__id=klass_id)
tests = Tests.objects.filter(chapter__id=chapter_id)
ret_val = []
for pupil in pupils:
pu = {
"name": pupil.name,
"tests": [{"score": test.score,
"chapter": test.chapter.name} for test in tests.filter(pupil=pupil)]
}
ret_val.append(pu)
return Response({"pupils": ret_val})
url
/api/testresult/<klass_id>/<chapter_id>/

Related

get() returned more than one Modules -- it returned 2! (REST API - Django)

Here are my models:
class Modules(models.Model):
module_name = models.CharField(max_length=50)
module_duration = models.IntegerField()
class_room = models.IntegerField()
def __str__(self):
return self.module_name
class Students(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
grade = models.IntegerField()
modules = models.ManyToManyField(Modules)
def __str__(self):
return self.name
Here are my views:
class StudentsViewSet(viewsets.ModelViewSet):
serializer_class = StudentsSerializer
def get_queryset(self):
student = Students.objects.all()
return student
def create(self,request,*args, **kwargs):
data = request.data
new_student = Students.objects.create(name=data["name"], age=data['age'], grade=data["grade"])
new_student.save()
for module in data["modules"]:
module_obj = Modules.objects.get(module_name=module["module_name"])
new_student.modules.add(module_obj)
serializer = StudentsSerializer(new_student)
return Response(serializer.data)
class ModulesViewSet(viewsets.ModelViewSet):
serializer_class = ModudesSerializer
def get_queryset(self):
module = Modules.objects.all()
return module ```
When I post this:
*{
"name": "steve",
"age": 16,
"grade": 10,
"modules": [
{
"module_name": "math"
},
{
"module_name": "physics"
}
]
}*
It says:
MultipleObjectsReturned at /app_Five/student/
get() returned more than one Modules -- it returned 2!
I understand that get() only gets one element. But I think in thefor loop above, only one module_name exists for each loop. So each time the get() executes, there's only one result for it to get. Can anyone tell me what I did wrong there?
It seems that you expect the module name to be unique. You probably have multiple Modules entries in the database with the same module_name.
To ensure unique names when creating Module entries you could change the Modules model to enforce this:
class Modules(models.Model):
module_name = models.CharField(unique=True, max_length=50)
module_duration = models.IntegerField()
...

How to design ArrayField in django rest framework?

I am making API for cook book with Django Rest Framework. I don't know how to design ingredients model to make data to be like this:
{
"id": 1,
"name": "spaghetti",
"recipe": "recipe",
"ingredients": [
[{name:'pasta',amount:100},{name:'tomato',amount:200},{...}]
],
}
My model:
class Meal(models.Model):
name = models.TextField()
recipe = models.TextField()
ingredients = ?
Also how to serialize this field?
You can create a separate model for ingredient.
Many to many relation will be the best for me, because of one meal can have many ingredients and in the opposite way one ingredient can be used to make many meals.
According to django docs, in your case:
models.py
from django.db import models
class Ingredient(models.Model):
name = models.CharField(max_length=90)
amount = models.FloatField()
class Meta:
ordering = ['name']
def __str__(self):
return self.name
class Meal(models.Model):
name = models.CharField(max_length=100)
recipe = models.CharField(max_length=100)
ingredients = models.ManyToManyField(Ingredient)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
serializers.py
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
class MealSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(read_only=True, many=True)
class Meta:
model = Meal
fields = '__all__'
I believe what you are looking for is JsonBField
from django.contrib.postgres.fields.jsonb import JSONField as JSONBField
ingredients = JSONBField(default=list,null=True,blank=True)
this should do what you expect, have a nice day
edit: thanks for the update as #Çağatay Barın mentioned below
FYI, it is deprecated, Use django.db.models.JSONField instead.see the Doc
from django-3.1, Django comes with JSONField
class Meal(models.Model):
name = models.TextField()
recipe = models.TextField()
ingredients = models.JSONField()
There are two approaches to have this outcome [2] having another model:
using WritableNestedModelSerializer.
Overwriting the create() method of your serializer.
1st example, using WritableNestedModelSerializer:
# models.py --------------------------
class Ingredient(models.Model):
# model that will be related to Meal.
name = models.CharField(max_lenght=128)
amount = models.IntergerField()
def __str__(self):
return str(self.name)
class Meal(models.Model):
# meal model related to Ingredient.
name = models.TextField()
recipe = models.TextField()
ingredients = models.ForeigKey(Ingredient)
def __str__(self):
return str(self.name)
# serializers.py ----------------------
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
class MealSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
ingredients_set = IngredientSerializer(required=False, many=True)
class Meta:
model = Ingredient
fields = ["id", "name","recipe", "ingredients_set"]
2nd example rewriting the create() method:
# models.py --------------------------
# Follow the same approach as the first example....
# serializers.py ----------------------
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
class MealSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(required=False, many=True)
class Meta:
model = Meal
fields = ["id", "name","recipe", "ingredients"]
def create(self, validated_data):
# 1st step.
ingredients = validated_data('ingredients')
# 2nd step.
actual_instance = Meal.objects.create(**validated_data)
# 3rd step.
for ingredient in ingredients:
ing_objects = Ingredients.object.create(**ingredient)
actual_instance.ingredients.add(ing_objects.id)
actua_instance.save()
return actual_instance
What was done in the second example?
1st step: since you create a one-2-many relationship the endpoint will wait for a payload like this:
{
"name": null,
"recipe": null,
"ingredients": [],
}
ex of validated_data/ the data you sent:
{
"id": 1,
"name": "spaghetti",
"recipe": "recipe",
"ingredients": [
{name:'pasta',amount:100},{name:'tomato',amount:200},{...}
],
}
Therefore, since you are sending a payload with many ingredientes inside the ingredient array you will get this value from the validated_data.
For instance, if you make a print of the 'ingredients'(from the inside of the create() method) this is what you will get in your terminal:
[{name:'pasta',amount:100},{name:'tomato',amount:200},{...}]
2nd step: Alright, since you get the ingredients from validate_data it is time to create a Meal instance (where will be without the 'ingredients').
3rd step: You will loop all the ingredients objects from the 1st step you have done above and add them into the Meal.ingredients relationship saving the Meal instance.
-- about the extra model --
[2] Bear in mind that having a JSONField() allows anything to be added there even extra fields. Having a Meal model might be a better option if you want have a better control.

Handling ForeignKey in GraphQL (graphene)

I'm using Django-Graphene to build and API for my webapp. I've built this simple structure where I have Product that belong to a category:
from graphene_django import DjangoObjectType
import graphene
from .models import ProductModel, CategoryModel
class CategoryType(DjangoObjectType):
class Meta:
model = CategoryModel
class ProductType(DjangoObjectType):
category = graphene.Field(CategoryType)
class Meta:
model = ProductModel
class CategoryInput(graphene.InputObjectType):
title = graphene.String()
class ProductInput(graphene.InputObjectType):
title = graphene.String()
category = graphene.Field(CategoryInput)
class Query(graphene.ObjectType):
products = graphene.List(ProductType)
categories = graphene.List(CategoryType)
def resolve_products(self, info):
return ProductModel.objects.all()
def resolve_categories(self, info):
return CategoryModel.objects.all()
class CreateProduct(graphene.Mutation):
class Arguments:
product_data = ProductInput(required=True)
product = graphene.Field(ProductType)
#staticmethod
def mutate(self, info, product_data):
product = ProductModel.objects.create(**product_data)
product.save()
return CreateProduct(product=product)
class CreateCategory(graphene.Mutation):
class Arguments:
title = graphene.String()
category = graphene.Field(CategoryType)
def mutate(self, info, title):
category = CategoryModel(title=title)
category.save()
return CreateCategory(category=category)
class Mutations(graphene.ObjectType):
create_product = CreateProduct.Field()
create_category = CreateCategory.Field()
schema = graphene.Schema(query=Query, mutation=Mutations)
But when I try to mutate, I need to pass an instance of the CategoryModel, which I can't figure out how to do:
mutation X {
createProduct(productData: {title: "title", category: {}}) {
product {
id
}
}
}
How can I create products which require a title and a category?
I once had similar problem yesterday, but seems I haven't gotten a better grasp of GRAPHENE. But I wrote some funny codes which worked, tho there could be a more graphene way of solving it.
class ProductInput(graphene.InputObjectType):
name = graphene.String()
class ProductPayload(graphene.Mutation):
product = graphene.Field(ProductType)
class Meta:
input = ProductInput(required=True)
category_name = graphene.String(required=True)
def mutate(self, info, input=None, category_name=None):
cat = None
if category_name is not None:
cat = Category.objects.get(name=category_name)
product = Product.objects.create(name=input.name, category=cat)
return ProductPayload(product=product)
class ProductMutation(graphene.ObjectType):
create_product = ProductPayload.Field()
schema = graphene.Schema(mutation=ProductMutation
To make a mutation in your graphiql,
mutation {
createProduct(input: {name: "Your product name"}, category_name: "your category name") {
product {
name,
category {
name
}
}
}
}

How to define a nested list in django for json response

I am new to Django and so I thought of creating basic app that represents operations in a shipping company. I have a WorkOrder which contains Shipments. So my models.py contains the following :
class WorkOrder (models.Model):
status = models.CharField(max_length=300,default = "New")
source = models.CharField(max_length=300)
destination = models.CharField(max_length=
material = models.CharField(max_length=300)
shipmentlist = [] //PROBLEMATIC CODE
class Shipment (models.Model):
expected_startDate = models.DateTimeField(default=timezone.now)
expected_endDate = models.DateTimeField(default=timezone.now)
shipment_status = models.CharField(max_length=300,default = "Not Started")
I have 2 serializers WorkOrderSerializer and ShipmentSerializer which i have defined in serialzers.py. I want to return a list of shipment contained within a Work Order object.
class WorkOrderSerializer
generated_date = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=300, default="New")
source = models.CharField(max_length=300)
destination = models.CharField(max_length=300)
material = models.CharField(max_length=300)
shipmentlist = ShipmentSerializer(many=True)
class ShipmentSerializer
expected_startDate = models.DateTimeField(default=timezone.now)
expected_endDate = models.DateTimeField(default=timezone.now)
shipment_status = models.CharField(max_length=300, default="Not Started")
I am following the model specified here.
http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
In my views.py I am calling the serializer when I get the request like this
def workorder_operations(request,workorder_pk):
workorder = Work_Order.objects.filter(pk=workorder_pk)
serializer = Work_Order_Serializer(workorder)
What this produces is a json that looks like the following
{
"shipmentlist":[]
}
I am completely confused about two things :
why it shows ONLY shipment object and not the others even if it cannot parse the data or something.
why doesn't shipment get populated.
When I used the ModelSerializer instead and defined the serializer this way it all worked perfectly fine:
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields =('expected_startDate','expected_endDate','shipment_status')
class WorkOrderSerializer(serializers.ModelSerializer):
class Meta:
model = WorkOrder
fields =('request_id','generated_date','status', 'source','destination','material')
What I want to know is how do I represent a nested list of objects so that they can be serialized properly.
I want my json for a WorkOrder object to look something like this: (Note: the variable names in the json and models might have a mismatch so please overlook that as I have stripped some of the variables to not complicate the example here.)
{
"id": "WO20170912",
"source": "BBSR",
"destination": "RKL",
"customer_id": 1,
"material": "Granite",
"weight": 19,
"status": "ALLOCATED",
"shipments": [
{
"id":"SH01234",
"work_order_id": "WO20170912",
"source": "BBSR",
"destination": "RKL"
},
{
"id":"SH01255",
"work_order_id": "WO20170912",
"source": "BBSR",
"destination": "RKL"
}
]
}
Add a ManyToManyField in WorkOrder model,
class Shipment (models.Model):
expected_startDate = models.DateTimeField(default=timezone.now)
expected_endDate = models.DateTimeField(default=timezone.now)
shipment_status = models.CharField(max_length=300,default = "Not Started")
class WorkOrder (models.Model):
status = models.CharField(max_length=300,default = "New")
source = models.CharField(max_length=300)
destination = models.CharField(max_length=
material = models.CharField(max_length=300)
shipments = models.ManyToManyField(Shipment, related_name='shipments')
Serializers would be like,
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields = [f.name for f in model._meta.fields]
class WorkOrderSerializer(serializers.ModelSerializer):
shipments = ShipmentSerializer(many=True)
class Meta:
model = WorkOrder
fields = [f.name for f in model._meta.fields] + ['shipments']

Custom create method to prevent duplicates

I would like to add some logic to my serializer.py.
Currently it creates duplicate tags (giving a new ID to the item, but often it will match a tag name already).
In plain english
if exists:
# Find the PK that matches the "name" field
# "link" the key with Movie Class item
else:
# Create the "name" inside of Tag class
# "link" the key with Movie Class item
The data being posted looks like this:
{
"title": "Test",
"tag": [
{
"name": "a",
"taglevel": 1
}
],
"info": [
]
}
Models.py
class Tag(models.Model):
name = models.CharField("Name", max_length=5000, blank=True)
taglevel = models.IntegerField("Tag level", blank=True)
def __str__(self):
return self.name
class Movie(models.Model):
title = models.CharField("Whats happening?", max_length=100, blank=True)
tag = models.ManyToManyField('Tag', blank=True)
def __str__(self):
return self.title
Serializers
class MovieSerializer(serializers.ModelSerializer):
tag = TagSerializer(many=True, read_only=False)
class Meta:
model = Movie
fields = ('title', 'tag', 'info')
def create(self, validated_data):
tags_data = validated_data.pop('tag')
movie = Movie.objects.create(**validated_data)
for tag_data in tags_data:
movie.tag.create(**tag_data)
return movie
This will probably solve your issue:
tag = Tag.objects.get_or_create(**tag_data)[0]
movie.tag.add(tag)
get_or_create function returns tuple (instance, created), so you have to get instance with [0].
So the full code is:
def create(self, validated_data):
tags_data = validated_data.pop('tag')
movie = Movie.objects.create(**validated_data)
for tag_data in tags_data:
tag = Tag.objects.get_or_create(**tag_data)[0]
movie.tag.add(tag)
return movie
To make function case insensitive, you have to do "get or create" manually (the main part is to use __iexact in filter):
tag_qs = Tag.objects.filter(name__iexact=tag_data['name'])
if tag_qs.exists():
tag = tag_qs.first()
else:
tag = Tag.objects.create(**tag_data)
movie.tag.add(tag)

Categories