Django Rest Framework: writable nested serializer - django

I need to manage a JSON like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": [
{
"value": "Coca-Cola",
"synonyms": [
"coca cola",
"coke"
]
},
{
"value": "Beer",
"synonyms": [
"beer"
]
}
]
}
models.py:
class Entity(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class Value(models.Model):
entity = models.ForeignKey(Entity, related_name='values', on_delete=models.CASCADE)
value = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
class Synonymous(models.Model):
value = models.ForeignKey(Value, related_name='synonyms', on_delete=models.CASCADE)
synonymous = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
serializers.py:
class ValueSerializer(serializers.ModelSerializer):
synonyms = serializers.SlugRelatedField(
many=True,
slug_field='synonymous',
queryset=Synonymous.objects.all()
)
class Meta:
model = Value
fields = ('value', 'synonyms')
def create(self, validated_data):
synonyms_data = validated_data.pop('synonyms')
value = Value.objects.create(**validated_data)
for synonyomous_data in synonyms_data:
Synonymous.objects.create(value=value, **synonyomous_data)
return value
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id', 'name', 'description', 'values')
def create(self, validated_data):
values_data = validated_data.pop('values')
entity = Entity.objects.create(**validated_data)
for value_data in values_data:
# How can I call the create method of values?
pass
return entity
views.py
#api_view(['GET', 'POST'])
def entity_list(request, format=None):
"""
List all entities, or create a new entity.
"""
if request.method == 'GET':
entities = Entity.objects.all()
serializer = EntitySerializer(entities, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = EntitySerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The serializers works fine when the GET view is called, but when I try to create a new Entity using the POST view, I'm not able to call the create method of ValueSerializer Class and the data created is like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": []
}
Someone can help me?
Thank you!

class SynonymousSerializer(serializers.ModelSerializer):
class Meta:
model = Synonymous
fields = '__all__'
class ValueSerializer(serializers.ModelSerializer):
entity = serializers.PrimaryKeyRelatedField(queryset = Entity.objects.all())
synonyms = ValueSerializer(many=True)
class Meta:
model = Value
fields = ('id', 'synonyms')
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id','values')
q = Entity.objects.all()
serializer = EntitySerializer(q)
print serializer.data

Related

nested one to many serializer model django

I am trying to create a relationship between databases
result : =>
[
{
"id":1,
"title":"mobile",
"category_two":[
{
"id":3,
"title":"brand"
},
{
"id":4,
"title":"memory"
}
]
}
]
and i expect : =>
[
{
"id":1,
"title":"mobile",
"category_two":[
{
"id":3,
"title":"brand",
"category_three":[
{
"id":1,
"title":"samsung"
},
{
"id":2,
"title":"apple"
}
]
},
{
"id":4,
"title":"memory",
"category_three":[
{
"id":1,
"title":"32gb"
},
{
"id":2,
"title":"64gb"
}
]
}
]
}
]
// views
class get_Category(APIView):
def get(self, request):
category = CategoryOne.objects.all()
serializer = CategoryTwoSerializer(category, many=True)
return Response(serializer.data)
//serializers
class CategoryOneSerializer(serializers.ModelSerializer):
class Meta:
model = CategoryOne
fields = '__all__'
class CategoryTwoSerializer(serializers.ModelSerializer):
category_two= CategoryOneSerializer(many=True,read_only=True)
class Meta:
model = CategoryTwo
fields = '__all__'
depth=5
class CategoryThreeSerializer(serializers.ModelSerializer):
category_three = CategoryTwoSerializer(many=True,read_only=True)
class Meta:
model = CategoryThree
fields = '__all__'
depth=5
// models
class CategoryOne(models.Model):
title = models.CharField(max_length=225)
def __str__(self):
return self.title
class CategoryTwo(models.Model):
title = models.CharField(max_length=255)
categoryone = models.ForeignKey(CategoryOne,related_name='category_two',on_delete=models.SET_NULL,null=True)
def __str__(self):
return self.title
class CategoryThree(models.Model):
title = models.CharField(max_length=255)
categorytwo = models.ForeignKey(CategoryTwo,related_name='category_three',on_delete=models.SET_NULL,null=True)
def __str__(self):
return self.title
Try
class CategoryOneSerializer(serializers.ModelSerializer):
category_two= CategoryTwoSerializer(many=True,read_only=True)
class Meta:
model = CategoryOne
fields = '__all__'
class CategoryTwoSerializer(serializers.ModelSerializer):
category_three = CategoryThreeSerializer(many=True,read_only=True)
class Meta:
model = CategoryTwo
fields = '__all__'
class CategoryThreeSerializer(serializers.ModelSerializer):
class Meta:
model = CategoryThree
fields = '__all__'
// views
class get_Category(APIView):
def get(self, request):
category = CategoryOne.objects.prefetch_related("category_two", "category_two__category_three")
serializer = CategoryOneSerializer(category, many=True)
return Response(serializer.data)
If you want category_two rows related to a specific category_one to be serialized with it, you essentially want to add a non existing data to CategoryOne.
Since means you need two add it to the serializer and specify how to serialize it.
Here you can access this data by using a related_name, therefore adding a field with the proper related_name will suffice for django to find the data you want.
The same wan be done the serializer CategoryThree tied the a specific CategoryTwo row.
Howerver doing this will produce a LOT of SQL queries since each time my_category_on.category_two.all() is called by the serializer, an sql query is needed.
The prefetch_related() is here to solve the issue

Need to add one model fields in the another model serializers but throwing error while POST the request

models.py
class Product(models.Model):
product_id = models.AutoField(unique=True, primary_key=True)
product_name = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "product_master"
def __str__(self):
return self.product_name
class Organisation(models.Model):
"""
Organisation model
"""
org_id = models.AutoField(unique=True, primary_key=True)
org_name = models.CharField(max_length=100)
org_code = models.CharField(max_length=20)
org_mail_id = models.EmailField(max_length=100)
org_phone_number = models.CharField(max_length=20)
org_address = models.JSONField(max_length=500, null=True)
product = models.ManyToManyField(Product, related_name='products')
org_logo = models.ImageField(upload_to='org_logo/')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "organisation_master"
def __str__(self):
return self.org_name
serializers.py
class Product_Serializers(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('product_id', 'product_name',)
class Organisation_Serializers(serializers.ModelSerializer):
product = Product_Serializers(many=True)
class Meta:
model = Organisation
fields = ('org_id', 'org_name', 'org_address', 'org_phone_number', 'org_mail_id','org_logo','org_code','product')
depth = 1
"
While i tried to do POST method for the organisation model I have tried giving the input for product as "product: 5" and "product: {"product_id": 5,"product_name": "time"} in the postman form data but it is showing as
{
"status": "error",
"code": 400,
"data": {
"product": [
"This field is required."
]
},
"message": "success"
}
Views.py
class Organisation_Viewset(DestroyWithPayloadMixin,viewsets.ModelViewSet):
renderer_classes = (CustomRenderer, ) #ModelViewSet Provides the list, create, retrieve, update, destroy actions.
queryset=models.Organisation.objects.all()
parser_classes = [MultiPartParser, FormParser]
serializer_class=serializers.Organisation_Serializers
def create(self, request, *args, **kwargs):
data = request.data
new_organisation= models.Organisation.objects.create(org_name=data["org_name"],org_code = ["org_code"], org_mail_id =data["org_mail_id"],org_phone_number= data["org_phone_number"], org_address=data["org_address"],org_logo = data["org_logo"])
new_organisation.save()
for product in data["product"]:
product_id = models.Product.objects.get(product_id=product["product_id"])
new_organisation.products.add(product_id)
serializer = serializers.Organisation_serializers(new_organisation)
return Response(serializer.data)
I need to post like this product: {"product_id": 5,"product_name": "time"}, what fields are available in the product model it should be posted on this product field.
Can you please suggest me a way as i tried many ways as per my knowledge but it dosen't worked.
you are using a tuple for fields, put a comma behind product in youre fields. If you use a list then dont use an end comma
fields = ('org_id', 'org_name', 'org_address', 'org_phone_number', 'org_mail_id','org_logo','org_code','product',)
depth = 1
Update your serializers to:
class Product_Serializers(serializers.Serializer):
product_id = serializers.IntegerField()
product_name = serializers.CharField(max_length=100)
class Organisation_Serializers(serializers.ModelSerializer):
product = Product_Serializers(many=True)
class Meta:
model = Organisation
fields = (
'org_id',
'org_name',
'org_address',
'org_phone_number',
'org_mail_id',
'org_logo',
'org_code',
'product'
)
depth = 1
Update your views as:
class Organisation_Viewset(ModelViewSet):
# ModelViewSet Provides the list, create, retrieve, update, destroy actions.
renderer_classes = (CustomRenderer,)
queryset = Organisation.objects.all()
parser_classes = [MultiPartParser, FormParser, JSONParser]
serializer_class = Organisation_Serializers
def create(self, request, *args, **kwargs):
serializer = Organisation_Serializers(data=request.data)
serializer.is_valid(raise_exception=True)
product_data = serializer.validated_data.pop('product')
does_not_exist = []
product_instances = []
for product in product_data:
try:
product_instance = Product.objects.get(
product_id=product['product_id'],
product_name=product['product_name']
)
product_instances.append(product_instance)
except Product.DoesNotExist:
does_not_exist.append(product)
if len(does_not_exist) > 0:
return Response({
'error': 'Product does not exist',
'does_not_exist': does_not_exist
}, status=400)
organization = Organisation.objects.create(**serializer.validated_data)
for product in product_instances:
organization.product.add(product)
organization.save()
return Response(Organisation_Serializers(organization).data, status=201)
Now we can send the list of product objects for the create API:
curl --location --request POST 'http://localhost:8000/api/organization/' \
--header 'Content-Type: application/json' \
--data-raw '{
"org_id": "12345",
"org_name": "test organization",
"org_address": "test",
"org_phone_number": "12345",
"org_mail_id": "test#te.st",
"org_code": "12345",
"product": [
{
"product_id": 1,
"product_name": "test p1"
},
{
"product_id": 2,
"product_name": "test p2"
}
]
}'

How to solve PUT method is not allowed in drf?

I've a model:
class ListingPrice(Timestamps):
price = models.ForeignKey("Price", on_delete=models.CASCADE)
location = models.ForeignKey("location", on_delete=models.CASCADE)
class Meta:
unique_together = ["price", "location"]
class Price(Timestamps):
package = models.ForeignKey("products.Package", on_delete=models.CASCADE)
locations = models.ManyToManyField("location", through="ListingPrice")
price = models.DecimalField(max_digits=11, decimal_places=3)
with a serializer:
class LocationSerializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = location
fields = ['id', 'name']
class PriceSerializer(serializers.ModelSerializer):
locations = LocationSerializer(many=True, read_only=True)
class Meta:
model = Price
fields = ['package', 'locations', 'price']
def create(self, validated_data):
print("validated_data, validated_data)
and viewset:
class PriceViewSet(ModelViewSet):
queryset = Price.objects.all()
serializer_class = PriceSerializer
ordering = ['id']
permissions = {
"GET": ["view_minimum_listing_price", ],
"POST": ["add_minimum_listing_price", ],
'PUT': ['update_minimum_listing_price', ],
'DELETE': ['delete_minimum_listing_price', ],
}
In testing I'mm using the following:
data = {
"price": 15,
}
response = self.client.put(path=self.url, data=data, format='json', args=[1])
I'm trying to update the price in the instance with id 1, but neither put or update is not allowed? How to overcome this and update it?
edit: urls.py
router = SimpleRouter()
router.register('listing_price', PriceViewSet, basename='listing_price')

Django in one serialize pull out child objects

This my models
class Dictionary(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
parentId = models.UUIDField(editable=True, null=True)
name = models.CharField(max_length=100)
date_create = models.DateTimeField(auto_now=True)
date_end = models.DateTimeField(auto_now=False, null=True)
class Teacher(models.Model):
name = models.CharField(max_length=100)
message = models.CharField(max_length=300)
status = models.OneToOneField(Dictionary, on_delete=models.CASCADE)
this is my urls
from django.urls import path
from . import views
urlpatterns = [
path('get', views.GetViewSet.as_view({'get': 'list'})),
]
This is ViewSet
class GetViewSet(viewsets.ModelViewSet):
MyApiObj = null
#property
def api_object(self):
return namedtuple("ApiObject", self.request.data.keys())(*self.request.data.values())
def get_serializer_class(self):
GeneralSerializer.Meta.model = apps.get_model(app_label=self.MyApiObj.app, model_name=self.MyApiObj.object)
return GeneralSerializer
def post(self, request):
self.MyApiObj = self.api_object
return self.select_api()
def select_api(self):
queryset = QueryHelper.select(self.MyApiObj)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Serializer
class GeneralSerializer(serializers.ModelSerializer):
class Meta:
model = None
fields = '__all__'
My post parameters to django
{
"app":"leads",
"object":"Teacher",
"settings":{
},
"data":{
}
}
answer:
[
{
"id": 1,
"name": "John",
"message": "Hi everyone",
"status": "e3b86ed4-8794-413b-994c-b1ec0a43eebe"
}
]
Problem is Dictionary(status) model give me id(uuid) but i need whole object without creating new serializer for Dictionary. i do univeral serializer for all models in my app
Try this:
class DictionarySerializer(serializers.ModelSerializer):
class Meta:
model = Dictionary
fields = '__all__'
class GeneralSerializer(serializers.ModelSerializer):
status = DictionarySerializer(required=True)
class Meta:
model = None
fields = '__all__'
But it is not good for me because 1) Without other serializer 2) I need Universal serializer for all models and with child model in all models of my project. Help me please)
I need something like this
[
{
"id": 1,
"status": {
"id": "e3b86ed4-8794-413b-994c-b1ec0a43eebe",
"parentId": "dc6cf7da-b82c-11e9-a2a3-2a2ae2dbcce4",
"name": "Spravochnik1",
"date_create": "2019-08-06T09:30:49.355439Z",
"date_end": "2019-08-06T09:29:57Z"
},
"name": "John",
"message": "Hi everyone"
}
]
for nested serialization check full ref here
and for your case add depth = 1
class GeneralSerializer(serializers.ModelSerializer):
status = DictionarySerializer(required=True)
class Meta:
model = None
fields = '__all__'
depth = 1

Django nested Serializer filter to only one field, not all fields

I have two serializers like below. The output for the below snippet is Workers and with associated Ticket Counter details with all fields (ticket_counter,ticket_counter_name,worker). But I just need only one field that is ticket_counter_name.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = WorkerToCounterSerializer(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
class WorkerToCounterSerializer(serializers.ModelSerializer):
ticket_counter = SerializerMethodField()
ticket_counter_name = serializers.CharField(source='ticket_counter.ticket_counter_name')
class Meta:
model = WorkerToTicketCounter
list_serializer_class = FilteredListSerializer
fields = (
'ticket_counter',
'ticket_counter_name',
'worker',
)
def get_ticket_counter(self, obj):
return obj.ticket_counter.pk
class FilteredListSerializer(ListSerializer):
def to_representation(self, data):
data = data.filter(worker_to_ticket_counter_is_deleted=False)[:1]
return super(FilteredListSerializer, self).to_representation(data)
What above snippet outputs
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
{
"ticket_counter": 7,
"ticket_counter_name": "Entrance Counter",
"worker": 4,
}
]
}
But What I want is
{
"username": "xxxxxxxxxxx",
"ticket_counter": "Entrance Counter"
}
I just need the name of the ticket_counter_name. In my case, there can't be two ticket_counters for a worker. Obviously, it gives only one ticket_counter. Is it possible?
EDIT: using string StringRelatedField
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
"Entrance Counter",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx"
]
}
EDIT: WorkerToTicketCounter Model
class WorkerToTicketCounter(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ticket_counter = models.ForeignKey(TicketCounter, related_name="workers")
worker = models.ForeignKey(User, related_name='ticket_counter')
worker_to_ticket_counter_is_deleted = models.BooleanField(default=False)
If I'm understood correctly, you only need a SerializerMethodField to perform both filtering and string represantion.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = serializers.SerializerMethodField(read_only=True)
def get_ticket_counter(self, user):
qs = user.ticket_counter.filter(worker_to_ticket_counter_is_deleted=False)
if qs.exists() and hasattr(qs.first().ticket_counter, 'ticket_counter_name'):
return qs.first().ticket_counter.ticket_counter_name
return None
class Meta:
model = User
fields = ('username', 'ticket_counter',)
You can use StringRelatedField:
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = StringRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
Note to use StringRelatedField you should add __str__ method to your WorkerToTicketCounter model:
class WorkerToTicketCounter:
...
def __str__(self):
return self.ticket_counter.ticket_counter_name