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.
Related
This question has been asked before but I cannot use any of the answers to my case.
I'm trying to have the equivalent of this, to show the results on the API.
SELECT denom_name,retail_name,retail_adr
FROM denomination d INNER JOIN Retailer r
ON r.id = d.retailer.id
These are my models (models.py):
class Retailer(models.Model):
retail_name = models.CharField(max_length=30)
retail_addr = models.CharField(max_length=300,null=True)
def __str__(self):
return self.retail_name
class Denomination(models.Model):
denom_name = models.CharField(max_length=1000)
retailer = models.ForeignKey(Retailer, on_delete=models.CASCADE)
I've created a viewset on the views.py
class DenomRetailViewset(viewsets.ModelViewSet):
queryset = Denomination.objects.select_related('Retailer')
serializer_class = DenomRetailSerializer
But here lies the issue, at least one of them.
I'm creating the serializer through the serializer.py
class DenomRetailSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model= retailer,denomination
fields = ('denom_name','retail_name','retail_adr')
But as you can see, the serializer cannot accept two models. And beside, I have doubts about the viewset, queryset = Denomination.objects.select_related('Retailer').
Any tips are more than welcomed as I'm starting to lose my sanity.
Thanks.
Use source--DRF doc argument
class DenomRetailSerializer(serializers.HyperlinkedModelSerializer):
retail_name = serializers.CharField(source='retailer.retail_name')
retail_adr = serializers.CharField(source='retailer.retail_adr')
class Meta:
model = Denomination
fields = ('denom_name', 'retail_name', 'retail_adr')
Also, it should be .select_related('retailer') instead of .select_related('Retailer')
You can use "depth" in this case:
class DenomRetailSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model= denomination
fields = ('denom_name','retail')
depth = 1
The response will return an inner join like that:
{
"id": 1,
"denom_name ": "...",
"retail": {
"id" : 1,
"retail_name" : "...",
"retail_addr" : "..."
}
}
I have 2 models
class Tag(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Question(models.Model):
ques_id = models.IntegerField(default=0)
name = models.CharField(max_length=255)
Tag_name = models.ManyToManyField(Tag)
class Meta:
ordering = ['ques_id']
def __str__(self):
return self.name
searlizers.py
class TagSerializers(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class QuestionSerializers(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
This is my searilzers class.
I want the response like
{
"id": 1,
"name": "QUES 1",
"tags": [{
"id": 1,
"name": "Abcd"
}]
}
what will be query to get Fetch 10 questions, given some input tag ids
e.g Tag_id = 1 or 2 or 3.
You need to add tags field as another serializer data to your QuestionSerializer.
Your QuestionSerializer code should look like that:
class QuestionSerializers(serializers.ModelSerializer):
Tag_name = TagSerializer(many=True)
class Meta:
model = Question
fields = '__all__'
If you want exactly tags name in response, you can specify SerializerMethodField like that:
class QuestionSerializer(serializers.ModelSerializer):
tags = serializers.SerializerMethodField()
get_tags(self, instance):
return TagSerializer(instance.Tag_name, many=True).data
class Meta:
model = Question
fields = ('ques_id', 'name', 'tags')
First: I would suggest that you refactor your Question Model, since it has a ques_id, and I think it is considered a duplicate (since Django already creates an id field by default)
Then You need to change your ManyToManyField's name to tags, and makemigrations, then migrate
class Question(models.Model):
name = models.CharField(max_length=255)
tags = models.ManyToManyField(Tag)
class Meta:
ordering = ['id']
def __str__(self):
return self.name
# run make migrations
python manage.py makemigrations <<<YOUR QUESTIONS APP NAME>>>
# It will prompt you to check if you change the many to many relationship say yes
Did you rename question.Tag_name to question.tags (a ManyToManyField)? [y/N] y
# Then Run migrate
python manage.py migrate
Second: Update your QuestionSerializers to make the tags field serialize the relation
class QuestionSerializers(serializers.ModelSerializer):
tags = TagSerializers(many=True)
class Meta:
model = Question
fields = '__all__'
This way you make your code cleaner. And you are good to go.
Answer Updated (Filtering, and Pagination)
Now if you wanted to filter questions based on provided tag ids.
You need to use PageNumberPagination for your view, and for filtering use DjangoFilterBackend.
I recommend you to make them the default of DRF settings.
Make sure you have django-filter installed.
# In your settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
'PAGE_SIZE': 10
}
Now create your custom filter
# Inside filters.py
import re
import django_filters
from questions.models import Question
class QuestionsFilterSet(django_filters.FilterSet):
tags = django_filters.CharFilter(field_name='tags__id', method='tags_ids_in')
def tags_ids_in(self, qs, name, value):
ids_list = list(map(lambda id: int(id), filter(lambda x: x.isdigit(), re.split(r'\s*,\s*', value))))
return qs.filter(**{f"{name}__in": ids_list}).distinct()
class Meta:
model = Question
fields = ('tags',)
Now use ListAPIView to list your questions
class QuestionsListAPIView(ListAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializers
filter_class = QuestionsFilterSet
Now if you hit
http://<PATH_TO_VIEW>?tags=1,2,3
You will receive all Questions that have tag id 1, 2, or 3.
and all of them will be paginated 10 results at a time.
You just need to write a field tags like this in your QuestionSerializers
class QuestionSerializers(serializers.ModelSerializer):
tags = TagSerializers(source='Tag_name', many=True)
class Meta:
model = Question
fields = ('id', 'name', 'tags')
It will give you response like this
{
"id": 1,
"name": "QUES 1",
"tags": [{
"id": 1,
"name": "Abcd"
}]
}
Your query to fetch on the basis of tags will be like this
Question.objects.filter(Tag_name__in=[1, 2, 4])
I am beginner in Django Rest framework. I want to implement One to Many object mapping like following json schema:
{
"from_date": "2017-08-06T12:30",
"to_date": "2017-08-06T12:30",
"coupon_name": "WELCOME100",
"min_booking_value": 150,
"applicable_days": [
{
"from_time": "13:00",
"to_time": "15:00",
"applicable_day": 2
},
{
"from_time": "16:00",
"to_time": "18:00",
"applicable_day": 3
}
]
}
For above json schema, I have created following Django Model classes:
class Coupon(models.Model):
coupon_id = models.AutoField(primary_key=True)
from_date = models.DateTimeField()
to_date = models.DateTimeField()
coupon_name = models.TextField()
min_booking_value = models.FloatField()
def __unicode__(self):
return 'Coupon id: ' + str(self.coupon_id)
class CouponApplicableDays(models.Model):
from_time = models.TimeField()
to_time = models.TimeField()
applicable_day = models.IntegerField()
And following serializer class above models:
class CouponApplicableDaysSerializer(serializers.ModelSerializer):
class Meta:
model = CouponApplicableDays
fields = ('from_time', 'to_time', 'applicable_day')
class CouponSerializer(serializers.ModelSerializer):
coupon_applicable_days = CouponApplicableDaysSerializer(required=True, many=True)
class Meta:
model = Coupon
fields = ('coupon_id', 'from_date', 'to_date', 'coupon_name', 'min_booking_value', 'coupon_applicable_days',)
def create(self, validated_data):
coupon_applicable_days_data = validated_data.pop("coupon_applicable_days")
coupon = Coupon.objects.create(**validated_data)
CouponApplicableDays.objects.create(coupon=coupon, **coupon_applicable_days_data)
return coupon
When I save data using coupon-serializer. It saves only in Coupon table not in CouponApplicableDays.
I know, I have messed up somewhere but I don't know where. Can you guys please look into above code and tell me how can I solve this?
You have a list here
coupon_applicable_days_data = validated_data.pop("coupon_applicable_days")
Either iterate over the list and create the objects, like this:
for applicable_day in coupon_applicable_days_data:
CouponApplicableDays.objects.create(coupon=coupon, **applicable_day)
or use the bulk_create method
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#bulk-create
CouponApplicableDays.objects.bulk_create(
[CouponApplicableDays(coupon=coupon, **aplicable_day)
for applicable_day in coupon_applicable_days_data]
)
Be aware that the bulk_create will not trigger pre_save/post_save signals.
I am trying to create a reference app in DRF 3 to demonstrate a nested serializer that can create/update models. The sample code below bombs with "*create() argument after ** must be a mapping, not list*" when trying to create the nested models. It is also no clear to me how I'd handle the .update() as in some cases I just want to be establish additional relationships (Persons).
The sample models:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
persons = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
And the serializers and viewsets:
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from app.models import Group, Person
class PersonSerializer(ModelSerializer):
class Meta:
model = Person
class GroupSerializer(ModelSerializer):
persons = PersonSerializer(many=True)
def create(self, validated_data):
persons = validated_data.pop('persons')
group = Group.objects.create(**validated_data)
if persons: # Bombs without this check
Person.objects.create(group=group, **persons) # Errors here
return group
class Meta:
model = Group
class PersonModelViewSet(ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
class GroupModelViewSet(ModelViewSet):
serializer_class = GroupSerializer
queryset = Group.objects.all()
I am trying to POST some JSON that inserts a Group with two (related) Persons:
{
"persons": [
{ "name" : "name 1" },
{ "name" : "name 2" }
],
"name": "group name 1"
}
I have no clue if there is an easier way, but the only way I managed to get this to work is to reference the 'through' model "memberships" in the Group serializer and write custom code for .create() and .update(). This seems like a lot of work to just set M2M FK's. If someone knows a better way I'd love to hear it.
class GroupMembershipSerializer(ModelSerializer):
class Meta:
model = Membership
fields = ('person',)
class GroupCreateSerializer(ModelSerializer):
memberships = GroupMembershipSerializer(many=True, required=False)
def create(self, validated_data):
person_data = validated_data.pop('memberships')
group = Group.objects.create(**validated_data)
for person in person_data:
d=dict(person)
Membership.objects.create(group=group, person=d['person'])
return group
def update(self, instance, validated_data):
person_data = validated_data.pop('memberships')
for item in validated_data:
if Group._meta.get_field(item):
setattr(instance, item, validated_data[item])
Membership.objects.filter(group=instance).delete()
for person in person_data:
d=dict(person)
Membership.objects.create(group=instance, person=d['person'])
instance.save()
return instance
class Meta:
model = Group
class GroupCreateModelViewSet(ModelViewSet):
serializer_class = GroupCreateSerializer
queryset = Group.objects.all()
So you can create a new Group with related Person(s) using:
{
"name" : "Group 1",
"memberships" : [
{ "person" : 1 },
{ "person" : 2 }
]
}
Use PrimaryKeyRelatedField shown here:
http://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
class GroupSerializer(serializers.ModelSerializer):
persons = serializers.PrimaryKeyRelatedField(
many=True, queryset=Person.objects.all())
class Meta:
model = Group
fields = ('name', 'persons')
Create each person first, for example. Person with ID 1, Name = "Bob". Person with ID 2, Name = "Tim". Then post them to the REST Endpoint using their primary keys So:
# Group create() REST endpoint data to POST
{'name': 'my group', 'persons': [1, 2]}
Now the people that you had created prior, are part of that Group.
I have a question about dealing with m2m / through models and their presentation in django rest framework. Let's take a classic example:
models.py:
from django.db import models
class Member(models.Model):
name = models.CharField(max_length = 20)
groups = models.ManyToManyField('Group', through = 'Membership')
class Group(models.Model):
name = models.CharField(max_length = 20)
class Membership(models.Model):
member = models.ForeignKey('Member')
group = models.ForeignKey('Group')
join_date = models.DateTimeField()
serializers.py:
imports...
class MemberSerializer(ModelSerializer):
class Meta:
model = Member
class GroupSerializer(ModelSerializer):
class Meta:
model = Group
views.py:
imports...
class MemberViewSet(ModelViewSet):
queryset = Member.objects.all()
serializer_class = MemberSerializer
class GroupViewSet(ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
When GETing an instance of Member, I successfully receive all of the member's fields and also its groups - however I only get the groups' details, without extra details that comes from the Membership model.
In other words I expect to receive:
{
'id' : 2,
'name' : 'some member',
'groups' : [
{
'id' : 55,
'name' : 'group 1'
'join_date' : 34151564
},
{
'id' : 56,
'name' : 'group 2'
'join_date' : 11200299
}
]
}
Note the join_date.
I have tried oh so many solutions, including of course Django Rest-Framework official page about it and no one seems to give a proper plain answer about it - what do I need to do to include these extra fields? I found it more straight-forward with django-tastypie but had some other problems and prefer rest-framework.
How about.....
On your MemberSerializer, define a field on it like:
groups = MembershipSerializer(source='membership_set', many=True)
and then on your membership serializer you can create this:
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field(source='group.id')
name = serializers.Field(source='group.name')
class Meta:
model = Membership
fields = ('id', 'name', 'join_date', )
That has the overall effect of creating a serialized value, groups, that has as its source the membership you want, and then it uses a custom serializer to pull out the bits you want to display.
EDIT: as commented by #bryanph, serializers.field was renamed to serializers.ReadOnlyField in DRF 3.0, so this should read:
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField(source='group.id')
name = serializers.ReadOnlyField(source='group.name')
class Meta:
model = Membership
fields = ('id', 'name', 'join_date', )
for any modern implementations
I was facing this problem and my solution (using DRF 3.6) was to use SerializerMethodField on the object and explicitly query the Membership table like so:
class MembershipSerializer(serializers.ModelSerializer):
"""Used as a nested serializer by MemberSerializer"""
class Meta:
model = Membership
fields = ('id','group','join_date')
class MemberSerializer(serializers.ModelSerializer):
groups = serializers.SerializerMethodField()
class Meta:
model = Member
fields = ('id','name','groups')
def get_groups(self, obj):
"obj is a Member instance. Returns list of dicts"""
qset = Membership.objects.filter(member=obj)
return [MembershipSerializer(m).data for m in qset]
This will return a list of dicts for the groups key where each dict is serialized from the MembershipSerializer. To make it writable, you can define your own create/update method inside the MemberSerializer where you iterate over the input data and explicitly create or update Membership model instances.
I just had the same problem and I ended it up solving it with an annotation on the group queryset.
from django.db.models import F
class MemberSerializer(ModelSerializer):
groups = serializers.SerializerMethodField()
class Meta:
model = Member
def get_groups(self, instance):
groups = instance.groups.all().annotate(join_date=F(membership__join_date))
return GroupSerializer(groups, many=True).data
class GroupSerializer(ModelSerializer):
join_date = serializers.CharField(required=False) # so the serializer still works without annotation
class Meta:
model = Group
fields = ..., 'join_date']
NOTE: As a Software Engineer, I love to use Architectures and I have deeply worked on Layered Approach for Development so I am gonna be Answering it with Respect to Tiers.
As i understood the Issue, Here's the Solution
models.py
class Member(models.Model):
member_id = models.AutoField(primary_key=True)
member_name = models.CharField(max_length =
class Group(models.Model):
group_id = models.AutoField(primary_key=True)
group_name = models.CharField(max_length = 20)
fk_member_id = models.ForeignKey('Member', models.DO_NOTHING,
db_column='fk_member_id', blank=True, null=True)
class Membership(models.Model):
membershipid = models.AutoField(primary_key=True)
fk_group_id = models.ForeignKey('Group', models.DO_NOTHING,
db_column='fk_member_id', blank=True, null=True)
join_date = models.DateTimeField()
serializers.py
import serializer
class AllSerializer(serializer.Serializer):
group_id = serializer.IntegerField()
group_name = serializer.CharField(max_length = 20)
join_date = serializer.DateTimeField()
CustomModels.py
imports...
class AllDataModel():
group_id = ""
group_name = ""
join_date = ""
BusinessLogic.py
imports ....
class getdata(memberid):
alldataDict = {}
dto = []
Member = models.Members.objects.get(member_id=memberid) #or use filter for Name
alldataDict["MemberId"] = Member.member_id
alldataDict["MemberName"] = Member.member_name
Groups = models.Group.objects.filter(fk_member_id=Member)
for item in Groups:
Custommodel = CustomModels.AllDataModel()
Custommodel.group_id = item.group_id
Custommodel.group_name = item.group_name
Membership = models.Membership.objects.get(fk_group_id=item.group_id)
Custommodel.join_date = Membership.join_date
dto.append(Custommodel)
serializer = AllSerializer(dto,many=True)
alldataDict.update(serializer.data)
return alldataDict
You would technically, have to pass the Request to DataAccessLayer which would return the Filtered Objects from Data Access Layer but as I have to Answer the Question in a Fast Manner so i adjusted the Code in Business Logic Layer!