Django Rest Framework - Serialize nested objects - django

I have this JSON input which I'd like to express properly in the Django Model in order to be correctly serialized by Django Rest Framework.
the MainObject has name property and a list of conditions;
each Condition is composed exactly by 2 blocks: left and right;
each Block is composed by 1 field title and a list of user_params.
{
"name": "The Main Object Name",
"conditions": [
{
"left": {
"title": "Title1",
"user_params": [
{
"name": "name_param_X",
"value": "100"
}
]
},
"right": {
"title": "Title2",
"user_params": [
{
"name": "name_param_Y",
"value": "200"
}
]
}
}
]
}
And here my models:
class MainObject(models.Model):
main_object_id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=64)
class Condition(models.Model):
condition_id = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False, unique=True)
main_object = models.ForeignKey(MainObject, related_name="conditions",
on_delete=models.CASCADE)
left = models.OneToOneField(Block, on_delete=models.CASCADE, null=True, related_name='left')
right = models.OneToOneField(Block, on_delete=models.CASCADE, null=True, related_name='right')
class Block(models.Model):
title = models.CharField(max_length=16, primary_key=True)
class BlockParam(models.Model):
name = models.CharField(max_length=32)
value = models.IntegerField()
block_title = models.ForeignKey(Block, related_name="user_params",
on_delete=models.CASCADE)
My serializers:
class ConditionSerializer(serializers.ModelSerializer):
condition_id = serializers.UUIDField(required=False)
class Meta:
model = Condition
fields = ('condition_id', 'left', 'right')
class MainObjectSerializer(serializers.ModelSerializer):
conditions = ConditionSerializer(many=True)
def create(self, validated_data):
conditions_data = validated_data.pop("conditions")
main_object = MainObject.objects.create(**validated_data)
for condition_data in conditions_data:
Condition.objects.create(main_object=main_object, **condition_data)
return main_object
The issue: when I do a POST request to create a MainObject with the aforementioned JSON input, I receive 400 Bad response with this message:
{
"conditions": [
{
"left": [
"Invalid pk \"{'title': 'Title1', 'user_params': [{'name': 'name_param_x', 'value': '100'}]}\" - object does not exist."
],
"right": [
"Invalid pk \"{'title': 'Title2', 'user_params': [{'name': 'name_param_y', 'value': '200'}]}\" - object does not exist."
]
}
]
}
My questions:
Are the models correctly modelled according to the desired JSON input structure?
Do I need to modify the serializers in order to get it work?

Related

How to show subcategories under category type

I need to get the child list under the parent list as a group.
class ServiceSerializer(serializers.ModelSerializer):
cleaning_type = serializers.CharField(source='cleaning_type.cleaning_type_name')
class Meta:
model = Service
fields = ('id', 'cleaning_type','service_name')
class ServiceTypeViewSet(ModelViewSet):
serializer_class = ServiceSerializer
http_method_names = ["get"]
queryset = Service.objects.all()
def get_queryset(self):
"""
This view should return a list of all the service types.
"""
servicename_list = Service.objects.all()
return servicename_list
It shows:
[
{
"id": 1,
"cleaning_type": "Lite service",
"service_name": "Floors",
},
{
"id": 2,
"cleaning_type": "Lite service",
"service_name": "Bathrooms",
},
{
"id": 3,
"cleaning_type": "Lite service",
"service_name": "Kitchen",
}
]
I want this to be in the following format:
[
{
id: 1,
cleaning_type: 'Lite service',
service_name: ['Floors', 'bathroom', 'kitchen'],
},
{
id: 2,
cleaning_type: 'Moving cleaning',
service_name: ['Kitchen Including All Appliances And Cabinets'],
},
]
That means all child elements will be under a separate parent list. Not separate by separate.
models.py is here:
Cleaning Type Model:
class CleaningType(models.Model):
cleaning_type_name = models.CharField(
_("Select Cleaning Type"), blank=True, null=True, max_length=255)
price = models.DecimalField(default=0,max_digits=6, decimal_places=2)
def __str__(self):
return self.cleaning_type_name
Service Model:
class Service(models.Model):
cleaning_type = models.ForeignKey(
CleaningType, on_delete=models.CASCADE)
service_name = models.CharField(
_("Service Name"), blank=True, null=True, max_length=255)
#string type added
def __str__(self):
return str(self.service_name)
I want sub categories under parent caterories. Here cleaning_type is the parent category and service is the child category of cleaning_type. i.e : cleaning_type >> service_type
I'd create a view for the parent category and then get child categories for each parent category. First, you should create a serializer for CleaningType model:
class CleaningTypeSerializer(serializers.ModelSerializer):
service_types = serializers.SerializerMethodField('get_service_types')
def get_service_types(self, cleaning_type_name):
return Service.objects.filter(cleaning_type=cleaning_type_name).values_list("service_name", flat=True)
class Meta:
model = CleaningType
fields = "__all__"
Then, create a view using the new serializer:
class CleaningTypesViewSet(ModelViewSet):
serializer_class = CleaningTypeSerializer
http_method_names = ["get"]
queryset = CleaningType.objects.all()
The response looks something like this:
[
{
"id": 1,
"service_types": [
"Moving Service 1",
"Moving Service 2",
"Moving Service 3"
],
"cleaning_type_name": "Moving Cleaning",
"price": "200.00"
},
{
"id": 2,
"service_types": [
"Another Service 1",
"Another Service 2"
],
"cleaning_type_name": "Another Cleaning",
"price": "300.00"
}
]

How to add model instances to a Foreign Key field in Django Rest Framework

I need to get the ItemModel instances from the item model and store them in the foreign key field in the OrderModel but I am not sure how. I've tried to iterate through the item order list and add one but it doesn't display correctly. Any help would be much appreciated.
My goal is to display the data like this:
{
"payment_method": "test",
"confirmation": "14087147WA285750M",
"total_price": "15.00",
"is_paid": "True",
"order_created": "2021-07-09T19:51:18Z",
"item_order": [
{
"id": 2,
"name": "Carrots",
"image": "image link",
"slug": "carrots",
"price": "5.00",
"itemID": "ct1",
"quantity": 1
},
{
"id": 8,
"name": "Dog Food",
"image": "image link",
"slug": "dog-food",
"price": "10.00",
"itemID": "df4",
"quantity": 1
}
]
}
View:
#api_view(['POST'])
def create_order(request):
user = request.user
order = request.data
order_item = OrderModel.objects.create(
user=user,
payment_method=order['payment_method'],
confirmation=order['confirmation'],
total_price=order['total_price'],
is_paid=order['is_paid'],
order_created=order['order_created'],
item_order= """ The item model instance """
)
ordered_items = OrderSerializer(order_item, many=True).data
return Response(ordered_items)
Order Model:
class OrderModel(models.Model):
user = models.ForeignKey(CustomerModel, on_delete=models.CASCADE)
item_order = models.ForeignKey(
ItemModel, on_delete=models.CASCADE)
payment_method = models.CharField(max_length=50)
confirmation = models.CharField(max_length=255)
total_price = models.DecimalField(
max_digits=5, decimal_places=2)
is_paid = models.BooleanField(default=False)
has_been_sent = models.BooleanField(default=False)
order_created = models.DateTimeField()
def __str__(self):
return str(self.id)
Order Serializer:
class OrderSerializer(serializers.ModelSerializer):
item_order = ItemSerializer(many=True)
class Meta:
model = OrderModel
fields = ['payment_method', 'confirmation',
'total_price', 'is_paid', 'has_been_sent', 'order_created',
'item_order']
read_only_fields = ['id']

Get every m2m relation in model as different object in DRF serializer

I have a model Jurisdiction that has M2M relation with Franchise. And I use ModelViewSet on Jurisdiction.
models.py
class Franchise(models.Model):
name = models.CharField('Name', max_length=255, db_index=True)
class Jurisdiction(models.Model):
franchise = models.ManyToManyField(
Franchise,
verbose_name='Franchise',
related_name='jurisdictions',
blank=True
)
name = models.CharField(
"Jurisdiction name",
max_length=255,
db_index=True,
unique=True,
)
phone_number = models.CharField(
"Phone number",
max_length=12,
validators=[phone_number_regex],
unique=True,
)
My views.py
class JurisdictionViewSet(viewsets.ModelViewSet):
queryset = Jurisdiction.objects.all().prefetch_related('franchise')
serializer_class = JurisdictionSerializer
serializers.py
class JurisdictionSerializer(serializers.ModelSerializer):
franchise = FranchiseSerializer(many=True)
class Meta:
model = Jurisdiction
fields = (
'id', 'name', 'phone_number', 'franchise',
)
And serializer shows me:
{
"id": 1,
"name": "Test juris 1",
"phone_number": "200-000-1233",
"franchise": [
{
"id": 1,
"name": "Test franchise 1",
},
{
"id": 2,
"name": "Test franchise 2",
}
}
How I can get a list view of every m2m relation as a different object in serializer? I tried to change the to_representation method of serializer but nothing works
expected output:
[
{
"id": 1,
"name": "Test juris 1",
"phone_number": "200-000-1233",
"franchise_name": "Test franchise 1"
},
{
"id": 1,
"name": "Test juris 1",
"phone_number": "200-000-1233",
"franchise_name": "Test franchise 2"
}
]
You can customize the ListSerializer of your ModelSerializer. Specifically the to_representation method. For example:
from rest_framework import serializers
class CustomJurisdictionListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
response = []
for item in iterable:
item_representation = self.child.to_representation(item)
for franchise in item.franchise.all():
representation = item_representation.copy()
representation['franchise_name'] = franchise.name
response.append(representation)
return response
Then remove franchise from JurisdictionSerializer and set the custom list serializer:
class JurisdictionSerializer(serializers.ModelSerializer):
class Meta:
model = Jurisdiction
fields = (
'id', 'name', 'phone_number',
)
list_serializer_class = CustomJurisdictionListSerializer

How to count the distinct of model_set in Django?

I've looked into this question, but I think it's different.
Let me explain a bit further. I have a serializer called DetailTrackSerializer to serialize my Track model, and I've nested a TaggedSerializer in DetailTrackSerializer.
class DetailTrackSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=120)
link = serializers.URLField(max_length=120)
tagged_set = TaggedSerializer(many=True)
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('id', 'artist', 'title', 'link', 'tagged_set',)
class TaggedSerializer(serializers.ModelSerializer):
tag = TagSerializer()
class Meta:
model = Tagged
fields = ('tag',)
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('name',)
Currently, this DetailTrackSerializer is returning a json like this
{
"tracks": [
{
"id": 168,
"artist": {
"id": 163,
"name": "Gob"
},
"title": "Face the Ashes",
"link": "",
"tagged_set": [
{
"tag": {
"id": 1356,
"name": "punk rock"
}
},
{
"tag": {
"id": 1356,
"name": "punk rock"
}
},
{
"tag": {
"id": 1356,
"name": "punk rock"
}
},
...
The list goes on, if there are 100 "punk rock" tag in this track, it will shows up 100 times and there may be another tag also not only "punk rock". What I need is something like this
{
"tracks": [
{
"id": 168,
"artist": {
"id": 163,
"name": "Gob"
},
"title": "Face the Ashes",
"link": "",
"tagged_set": [
{
"tag": {
"id": 1356,
"name": "punk rock"
},
"frequency": 100,
},
{
"tag": {
"id": 546,
"name": "pop"
},
"frequency": 236,
},
...
Each tag only appears once, and has its frequency.
Note: I'm using Django Rest Framework as well
Edit: models.py
class Tagged(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
track = models.ForeignKey(Track, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
class Track(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
link = models.URLField(max_length=255, blank=True)
tags = models.ManyToManyField(Tag, through='Tagged', blank=True)
From your Tagged I understood that there are big chances of Data Redundancy, that's why your tagged_set is showing multiple times.
What I'm trying to say is, this is not a Representation Problem with your serializer, rather than it's an Implementation Problem with your Models.
So, unique_together attribute will solve the problem, as
class Tagged(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
track = models.ForeignKey(Track, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
class Meta:
unique_together = ('track', 'tag')
After changing the models, please do makemigrations and migration.
Note: While doing migration you may come acrross django.db.utils.IntegrityError: UNIQUE constraint failed exception. So, delete all entries in the Tagged model
After reading through Django's docs about querysets, this is the solution I came up with
class DetailTrackSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=120)
link = serializers.URLField(max_length=120)
tags_frequency = serializers.SerializerMethodField()
artist = ArtistSerializer()
def get_tags_frequency(self, track):
tags = track.tags.all()
return tags.values('id', 'name').annotate(Count('id'))
class Meta:
model = Track
fields = ('id', 'artist', 'title', 'link', 'tags_frequency',)
which will give me json representation like this
{
"tracks": [
{
"id": 168,
"artist": {
"id": 163,
"name": "Gob"
},
"title": "Face the Ashes",
"link": "",
"tags_frequency": [
{
"name": "punk rock",
"id": 1356,
"id__count": 100
},
{
"name": "punk",
"id": 1357,
"id__count": 60
}
]
},
{
"id": 169,
"artist": {
"id": 164,
"name": "Jeff And Sheri Easter"
},
"title": "The Moon And I (Ordinary Day Album Version)",
"link": "",
"tags_frequency": []
},
Edwin Harly, you have some data overlap between Track, Tag and Tagged model. If you accept, I suggest you remove Tagged model. and if you want to save which user create tag, add user field in Tag model.
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
class Track(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
link = models.URLField(max_length=255, blank=True)
tags = models.ManyToManyField(Tag, through='Tagged', blank=True)
Then, you can your serializers like this:
class DetailTrackSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=120)
link = serializers.URLField(max_length=120)
tags = TagSerializer(many=True)
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('id', 'artist', 'title', 'link', 'tags',)
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('name',)

Django - Retrieve nested fields multiple levels deep using foreign keys

I'm struggling to write a Django GET that returns the following looking response:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876,
"item": {
"id": 9876,
"name": "item1",
"location": "California"
}
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484,
"item": {
"id": 2484,
"name": "item2",
"location": "California"
}
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Given a list_id, the response returns the basic information on the list ("id", "updated_date"), as well as the order of items in the list. Inside each item in the list order, it also grabs the related item details (nested in "item"). I'm able to get this response without the "item" details ("id", "name", "location" fields) and with no error:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Again there is no error, and I can retrieve the first nested level without any issue. The problem is getting the "item" information to show within each "list_order". Below are my models, serializers, and views.
models.py
class Lists(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
updated_date = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_lists'
class Items(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.TextField(blank=True, null=True)
location = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_items'
class ListOrder(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
list_id = models.ForeignKey(Lists, db_column='list_id', related_name='list_order')
item_id = models.ForeignKey(Items, db_column='item_id', related_name='items')
order = models.BigIntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_list_order'
serializers.py
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = '__all__'
class ListOrderSerializer(serializers.ModelSerializer):
item = ItemsSerializer(many=False, read_only=True)
class Meta:
model = ListOrder
fields = '__all__'
class ListsSerializer(serializers.ModelSerializer):
list_order = ListOrderSerializer(many=True, read_only=True)
class Meta:
model = Lists
fields = '__all__'
views.py
class ListsViewSet(generics.ListCreateAPIView):
"""
API endpoint that returns a list with its meta-information
"""
queryset = Lists.objects.all()
serializer_class = ListsSerializer
def get_queryset(self):
list_id = self.kwargs['list_id']
filters = [Q(id=list_id)]
return Lists.objects.filter(*filters)
def list(self, request, list_id):
queryset = self.get_queryset()
list_serializer = ListsSerializer(queryset, many=True)
return Response({ 'lists': list_serializer.data })
I'm pretty new to Django and like what it offers so far, though maybe I'm thinking of doing this in too much of a "SQL" way. I've read about select_related() and prefetch_related(), but not sure how I would apply it to this case. Any assistance is greatly appreciated and let me know if there's any other information I can provide.
In your ListOrderSerializer you are trying to serialize item. while in ListOrder model you used the field name item_id
Solution:
In ListOrderSerializer:
class ListOrderSerializer(serializers.ModelSerializer):
item_id = ItemsSerializer(many=False, read_only=True)
...