I'm creating a music rating app and I'm making a serializer for albums which has many relations and one aggregation method serializer which I think is causing all the trouble. The method gets average and count of reviews for every album. Is there any way I can decrease the number of queries for more performance?
All my models
class Artist(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
image = models.FileField(null=True, blank=True,
upload_to=rename_artist_image)
background_image = models.FileField(
null=True, blank=True, upload_to=rename_artist_bg_image)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.name
class Album(models.Model):
RELEASE_TYPE_ALBUM_CHOICES = [
("LP", "LP"),
("EP", "EP"),
("Single", "Single"),
("Live", "Live"),
]
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
release_date = models.DateField(null=True)
artist_id = models.ForeignKey(
Artist, on_delete=models.PROTECT, related_name="albums"
)
art_cover = models.FileField(
null=True, blank=True, upload_to=rename_album_art_cover)
release_type = models.CharField(max_length=10,
choices=RELEASE_TYPE_ALBUM_CHOICES, default="LP")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.title
class Genre(models.Model):
name = models.CharField(max_length=255)
def __str__(self) -> str:
return self.name
class AlbumGenre(models.Model):
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_genres")
genre_id = models.ForeignKey(
Genre, on_delete=models.PROTECT, related_name="album_genres")
def __str__(self) -> str:
return self.genre_id.name
class AlbumLink(models.Model):
SERVICE_NAME_CHOICES = [
("spotify", "Spotify"),
("tidal", "Tidal"),
("amazonMusic", "Amazon Music"),
("appleMusic", "Apple Music"),
]
service_name = models.CharField(
max_length=15, choices=SERVICE_NAME_CHOICES)
url = models.CharField(max_length=255)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_links")
def __str__(self) -> str:
return f"{self.service_name} - {self.url}"
class Track(models.Model):
title = models.CharField(max_length=255)
position = models.PositiveIntegerField()
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="tracks")
duration = models.DurationField(null=True)
def __str__(self) -> str:
return f"{self.position}. {self.title} - {self.duration}"
class AlbumOfTheYear(models.Model):
album_id = models.OneToOneField(
Album, on_delete=models.PROTECT, related_name="aoty")
position = models.IntegerField()
def __str__(self) -> str:
return str(self.position)
class Review(models.Model):
reviewer_id = models.ForeignKey(Reviewer, on_delete=models.PROTECT)
rating = models.IntegerField(
validators=[MaxValueValidator(100), MinValueValidator(0)]
)
review_text = models.TextField(null=True)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="reviews")
created_at = models.DateTimeField(auto_now_add=True)
That's how album serializer looks.
"id": 2,
"title": "OK Computer",
"slug": "ok-computer",
"created_at": "2022-02-22T21:51:52.528148Z",
"artist": {
"id": 13,
"name": "Radiohead",
"slug": "radiohead",
"image": "http://127.0.0.1:8000/media/artist/images/radiohead.jpg",
"background_image": "http://127.0.0.1:8000/media/artist/bg_images/radiohead.jpg",
"created_at": "2022-02-22T00:00:00Z"
},
"art_cover": "http://127.0.0.1:8000/media/album/art_covers/ok-computer_cd5Vv6U.jpg",
"genres": [
"Alternative Rock",
"Art Rock"
],
"reviews": {
"overall_score": null,
"number_of_ratings": 0
},
"release_date": "1997-05-28",
"release_type": "LP",
"tracks": [
{
"position": 1,
"title": "Airbag",
"duration": "00:04:47"
},
{
"position": 2,
"title": "Paranoid Android",
"duration": "00:06:27"
}
],
"links": [
{
"service_name": "spotify",
"url": "https://open.spotify.com/album/6dVIqQ8qmQ5GBnJ9shOYGE?si=L_VNH3HeSMmGBqfiqKiGWA"
}
],
"aoty": null
Album serializer with method that gets average and count of reviews of album
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
genres = StringRelatedField(
source="album_genres", many=True, read_only=True)
aoty = StringRelatedField(read_only=True)
links = AlbumLinkSerializer(
source="album_links", many=True, read_only=True)
artist = SimpleArtistSerializer(source="artist_id")
def get_avg_and_count_of_reviews(self, album: Album):
reviews = Review.objects.only("rating").filter(album_id=album.id).aggregate(
overall_score=Avg(F("rating"), output_field=IntegerField()), number_of_ratings=Count(F("rating"), output_field=IntegerField()))
return reviews
reviews = serializers.SerializerMethodField(
method_name="get_avg_and_count_of_reviews")
class Meta:
model = Album
fields = ["id",
"title",
"slug",
"created_at",
"artist",
"art_cover",
"genres",
"reviews",
"release_date",
"release_type",
"tracks",
"links",
"aoty"]
# Save slug
def create(self, validated_data):
slug = slugify(validated_data["title"])
return Album.objects.create(slug=slug, **validated_data)
Here is a queryset in album Viewset
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related("tracks").prefetch_related("album_genres").prefetch_related(
"album_links").prefetch_related("reviews").select_related("aoty").select_related("artist_id").all()
First you need to change your aggregate that you call once for every Album to an annotation, this will remove all of those extra aggregation queries
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related(
"tracks",
"album_genres",
"album_links",
"reviews"
).select_related(
"aoty",
"artist_id"
).annotate(
overall_score=Avg(F("reviews__rating"), output_field=IntegerField()),
number_of_ratings=Count(F("reviews__rating"), output_field=IntegerField())
)
Now you can replace your reviews field with two regular IntegerFields
class AlbumSerializer(serializers.ModelSerializer):
...
overall_score = serializers.IntegerField(source="overall_score")
number_of_ratings = serializers.IntegerField(source="number_of_ratings")
Related
I am trying to modify the way I want to accept data from endpoints without modifying the model defined for it. Hence I want to modify the serializer. I am not sure how should I redefine it.
I have a serializer defined as
class AssetWorkorderSerializer(MaxenBaseSerializer):
id = ReadOnlyField()
assets = PrimaryKeyRelatedField(
many=True,
read_only=False,
allow_null=False,
queryset=models.Asset.objects.all(),
)
assignee = PrimaryKeyRelatedField(
many=False, read_only=False, allow_null=True, queryset=User.objects.all()
)
checklists = PrimaryKeyRelatedField(
many=True, read_only=False, queryset=Checklist.objects.all()
)
assigned_by = PrimaryKeyRelatedField(
many=False, queryset=User.objects.all())
deadline = ReadOnlyField()
overdue = ReadOnlyField()
class Meta:
model = models.AssetWorkorder
fields = "__all__"
So objects accepted are:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "https://<some-url>/assetworkorders/29/",
"id": 29,
"assets": [
292,
293
],
"assignee": 33,
"checklists": [
7,
8,
9
],
"assigned_by": 32,
"deadline": "2022-03-31",
"overdue": true,
"created": "2022-03-31T03:52:59.123745Z",
"updated": "2022-03-31T17:18:49.599963Z",
"name": "Service HVACs",
"description": "Service HVACs",
"status": "OP",
"priority": "LO",
"duedate": "2022-03-31",
"scheduleddate": "2022-03-30",
"notifications_enabled": false
},
]
}
Instead I want to accept like this:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "https://<some-url>/assetworkorders/29/",
"id": 29,
"assetChecklist": [
{"asset": 292, "checklists": [7,8]},
{"asset": 293, "checklists": [8,9]},
],
"assignee": 33,
"assigned_by": 32,
"deadline": "2022-03-31",
"overdue": true,
"created": "2022-03-31T03:52:59.123745Z",
"updated": "2022-03-31T17:18:49.599963Z",
"name": "Service HVACs",
"description": "Service HVACs",
"status": "OP",
"priority": "LO",
"duedate": "2022-03-31",
"scheduleddate": "2022-03-30",
"notifications_enabled": false
},
]
}
Without modifying model.
I am already modifying create method, I will overwrite it again to save data as it is.
Other models are defined as:
class AssetWorkorder(MaxenBaseModel):
#property
def organizationsite_name(self):
return self.assets[0].organizationsite.organization.name
assets = models.ManyToManyField("Asset", verbose_name="Assets")
name = models.CharField("Name", blank=False, max_length=255)
description = models.CharField(
"Description", null=True, blank=True, max_length=2048
)
class Status(models.TextChoices):
OPEN = "OP", "Open"
INPROGRESS = "IP", "In progress"
BLOCKED = "BL", "Blocked"
COMPLETED = "CO", "Completed"
status = models.CharField(
max_length=2, choices=Status.choices, default=Status.OPEN)
class Priority(models.TextChoices):
LOW = "LO", "Low"
NORMAL = "NO", "Normal"
HIGH = "HI", "High"
URGENT = "UR", "Urgent"
priority = models.CharField(
max_length=2, choices=Priority.choices, default=Priority.NORMAL
)
duedate = models.DateField("Due date", editable=True, null=True)
scheduleddate = models.DateField(
"Scheduled date", editable=True, null=True)
assigned_by = models.ForeignKey(
"auth.User", help_text="", on_delete=models.CASCADE, related_name="assigned_by"
)
assignee = models.ForeignKey(
"auth.User",
help_text="",
on_delete=models.CASCADE,
related_name="assignee",
null=True,
)
#property
def deadline(self):
"""
Deadline is the scheduled date if this one is upcoming, otherwise,
the deadline is the due date.
"""
if self.duedate and self.scheduleddate:
return (
self.scheduleddate
if self.scheduleddate >= timezone.now().date()
else self.duedate
)
else:
return self.duedate or self.scheduleddate
#property
def overdue(self) -> bool:
return self.deadline == self.duedate
notifications_enabled = models.BooleanField(
"Enable notifications", default=False)
checklists = ManyToManyField("Checklist", through="WorkorderChecklist")
class Meta:
ordering = ["id"]
verbose_name = "Workorder (class: AssetWorkorder)"
verbose_name_plural = "Workorders (class: AssetWorkorder)"
def __str__(self):
return self.name
class AssetChecklist(MaxenBaseModel):
asset = models.ForeignKey("Asset", null=False,
on_delete=models.CASCADE)
checklist = models.ForeignKey("Checklist", null=False,
on_delete=models.CASCADE)
verbose_name = "AssetChecklist"
verbose_name_plural = "AssetChecklists"
def __str__(self):
return self.asset.name + " <-> " + self.checklist.name
class Asset(MaxenBaseModel):
"""
Assets can be just about anything, from buildings to machines or individual
components of a system such as sensors or controllers. Assets are an abstract
concept enabling hierarchical organization pf elements by relating them through
parent/child relationships or semantic relationships.
NOTE:
Assets having both an AssetCategory and an AssetType fk AND an AssetType having
its own AssetCategory fk is a bit funky but the OG model was built that way as
well.
"""
name = models.CharField("Asset name", max_length=255)
description = models.CharField(
"Description", max_length=512, blank=True, null=True)
organizationsite = models.ForeignKey(
"OrganizationSite",
verbose_name="Organization site",
on_delete=models.CASCADE,
)
thumbnail2 = models.ImageField(
"thumbnail2", storage=ThumbnailStorage, blank=True, null=True
)
# FIXME: Remove once the migration to thumbnail2 is done
thumbnail = models.ImageField(
"thumbnail",
upload_to="s3temp",
max_length=1024,
blank=True,
null=True,
help_text="Thumbnail image for the asset",
)
assetcategory = models.ForeignKey(
"AssetCategory",
verbose_name="Asset category",
blank=True,
null=True,
on_delete=models.CASCADE,
)
assettype = ChainedForeignKey(
AssetType,
chained_field="assetcategory",
chained_model_field="assetcategory",
show_all=False,
auto_choose=True,
sort=True,
blank=True,
null=True,
verbose_name="Asset type",
help_text="Linked type",
on_delete=models.CASCADE,
)
organizationcontacts = models.ManyToManyField(
"OrganizationContact",
verbose_name="Organization category",
blank=True,
)
graph = models.JSONField(
"Graph",
blank=True,
null=True,
help_text="Graph representation cache, update on save of an assetrelationship "
"or an asset",
)
class Meta:
ordering = ["id"]
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
#classmethod
def get_visible_for_user(cls, user: User) -> QuerySet:
exposed_organizationsites = OrganizationSite.get_visible_for_user(user)
return cls.objects.filter(organizationsite__in=exposed_organizationsites)
def update_graph(self):
G = nx.Graph()
nodes = Asset.objects.filter(organizationsite=self.organizationsite)
for n in nodes:
G.add_node(n.id)
edges = AssetRelationship.objects.filter(
Q(assetA__organizationsite=self.organizationsite)
| Q(assetB__organizationsite=self.organizationsite)
)
for e in edges:
G.add_edge(e.assetA.id, e.assetB.id)
assets_connected_to = node_connected_component(G, self.id)
# build vizjs json graph
nodes = []
edges = []
for a in assets_connected_to:
asset = Asset.objects.filter(id=a).get()
node = {
"id": asset.id,
"label": asset.name,
"group": "",
"meta": {
"asset_displayname": asset.name,
"asset_type": asset.assettype.name
if asset.assettype is not None
else "",
"asset_category": asset.assetcategory.name
if asset.assetcategory is not None
else "",
},
}
nodes.append(node)
relationships = AssetRelationship.objects.filter(
Q(assetA__in=list(assets_connected_to))
| Q(assetB__in=list(assets_connected_to))
).distinct()
for r in relationships:
edge = {
"from": r.assetA.id,
"to": r.assetB.id,
"label": r.relationshiptype.name,
"meta": {
"link_id": r.id,
"link_name": r.assetA.name
+ " "
+ r.relationshiptype.name
+ " "
+ r.assetB.name,
"link_type": r.relationshiptype.name,
},
}
edges.append(edge)
graph = {"nodes": nodes, "edges": edges}
self.graph = graph
class Checklist(MaxenBaseModel):
organization = models.ForeignKey(
"Organization", verbose_name="Organization", null=True, on_delete=models.CASCADE
)
name = models.CharField("Checklist Name", max_length=255)
description = models.CharField(
"Description", max_length=255, blank=True, null=True)
class Meta:
ordering = ["id"]
verbose_name = "Checklist"
verbose_name_plural = "Checklists"
constraints = [
models.UniqueConstraint(
fields=['organization', 'name'], name='unique_organization_name_combination'
)
]
def __str__(self):
return self.name
class MaxenBaseModel(models.Model):
"""
Base model with timestamps
"""
created = models.DateTimeField(
"Created on",
auto_now_add=True,
editable=False,
help_text="This field is autopopulated by the platform and represents the"
" creation timestamp of the object",
)
updated = models.DateTimeField(
"Updated on",
auto_now=True,
editable=False,
help_text="This field is autopopulated by the platform and represents the"
" object's last update's timestamp",
)
class Meta:
abstract = True # add this to base models so it doesn't get migrated
models.py
class Client(models.Model):
client_id = models.AutoField(unique=True, primary_key=True)
org = models.ForeignKey(Organisation, on_delete=models.CASCADE, related_name='org',null=True)
product = models.ManyToManyField(Product,related_name='product')
client_name = models.CharField(max_length=100)
client_code = models.CharField(max_length=20)
client_logo = models.ImageField(upload_to=upload_to,storage=DownloadableS3Boto3Storage, null=True, blank=True)
currency = MoneyField(max_digits=10, decimal_places=2, default_currency='INR', null=True)
billing_method = models.CharField(max_length=40)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
email_id = models.EmailField(max_length=100)
contact_no = models.CharField(max_length=20)
mobile_no = models.CharField(max_length=20)
description = models.TextField(max_length=500)
street_address = models.CharField(max_length=250)
city = models.CharField(max_length=50)
state = models.CharField(max_length=50)
country = models.CharField(max_length=50)
pincode = models.CharField(max_length=10)
industry = models.CharField(max_length=100)
company_size = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.IntegerField(default=0, choices=STATUS_CHOICES)
class Meta:
db_table = "client_master"
def __str__(self):
return self.client_name
serializers.py
class Client_Serializers(serializers.ModelSerializer):
#product_name = Product_Serializers(many=True)
product = Product_Serializers(many=True)
class Meta:
model = Client
fields = ('client_id','currency','billing_method','first_name','last_name','description','street_address','city','state','country','pincode','industry','company_size','client_name', 'contact_no','mobile_no', 'email_id','client_logo','client_code','product',)
def create(self, validated_data):
products_data = validated_data.pop('product')
product = Product.objects.create(**validated_data)
for product_data in products_data:
Product.objects.create(product=product, **product_data)
return product
Data receiving on GET method
{
"client_id": 3,
"currency": "0.05",
"billing_method": "credit card",
"first_name": "career",
"last_name": "lab",
"description": "NA",
"street_address": "tiliconveli",
"city": "tirunelveli",
"state": "tamilnadu",
"country": "India",
"pincode": "600200",
"industry": "software",
"company_size": 100,
"client_name": "techfetch",
"contact_no": "1234567890",
"mobile_no": "1234567890",
"email_id": "icanio#gamil.com",
"client_logo": "https://icanio-project-management.s3.amazonaws.com/client_logo/sup_bat_fDauRxK.jpg",
"client_code": "TFH",
"product": [
{
"product_id": 5,
"product_name": "time"
}
]
}
But while posting it in the same format it is not getting posted, showing like
{
"status": "error",
"code": 400,
"data": {
"product": [
"This field is required."
]
},
"message": "success"
}
Views.py for reference
class Client_Viewset(DestroyWithPayloadMixin,viewsets.ModelViewSet):
renderer_classes = (CustomRenderer, )
queryset=models.Client.objects.all()
serializer_class=serializers.Client_Serializers
parser_classes = [MultiPartParser, FormParser]
filter_fields = (
'client_id',
'client_name',
'client_code',
'org_id',
)
How can I post the same data which I get in the GET request of Product field. Please help me resolve this as I was stuck in there for two days. I tried so many ways and end up not getting posted.
Product model
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
product serializer
class Product_Serializers(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('product_id','product_name',)
From your code, the Client_Serializers is for Client model but the create method is not creating any Client object.
Client_Serializers should be something on these lines -
class Client_Serializers(serializers.ModelSerializer):
product = Product_Serializers(many=True)
class Meta:
model = Client
fields = ('client_id','currency','billing_method','first_name','last_name','description','street_address','city','state','country','pincode','industry','company_size','client_name', 'contact_no','mobile_no', 'email_id','client_logo','client_code','product',)
def create(self, validated_data):
products_data = validated_data.pop('product')
client = Client.objects.create(**validated_data) # Create a client object
for product_data in products_data:
Product.objects.create(client=client, **product_data)
return client
I'm using Django and Django Rest Framework to represent a 'BaseRecipe' model. This model has a M2M field to represent the ingredients of that recipe. That M2M field relates a 'Product' object with the 'BaseRecipe' object and extends that two models with another field to represent the 'quantity'. I'm trying to retrieve a list of those ingredients in the BaseRecipeSerializer, but only the id's are returned.
Any ideas why?
Thank you in advance!
My models.py:
class Product(models.Model):
name = models.CharField(_('Name'), max_length=255, help_text=_('Product name'))
supplier = models.ForeignKey(Supplier, blank=True, related_name='supplier_products', on_delete=models.CASCADE)
serial_no = models.CharField(_('Serial number'), max_length=50, blank=True,
help_text=_('Product serial number. Max 50 characters.'))
allergens = models.ManyToManyField(Allergen, blank=True, related_name='product_allergens')
description = models.TextField(_('Description'), blank=True, help_text=_('Additional product information.'))
is_vegan = models.BooleanField(_('Vegan'), default=False)
is_halal = models.BooleanField(_('Halal'), default=False)
format_container = models.CharField(_('Format container'), max_length=6, choices=CONTAINERS, blank=True,
help_text=_('Package format'))
format_quantity = models.DecimalField(_('Format quantity'), blank=True, null=True, max_digits=9, decimal_places=3,
help_text=_('Format quantity sell'))
format_unit = models.CharField(_('Format unit'), max_length=6, choices=UNITS, blank=True)
quantity = models.DecimalField(_('Quantity'), blank=True, null=True, max_digits=9, decimal_places=3,
help_text=_('Quantity per unit provided'))
type = models.CharField(_('Type'), max_length=255, help_text=_('Type'), blank=True)
unit = models.CharField(_('Unit'), max_length=6, choices=UNITS)
unit_price = models.DecimalField(_('Unit price'), blank=True, null=True, max_digits=9, decimal_places=3)
class Meta:
ordering = ['name', ]
#property
def price(self) -> Decimal:
return self.quantity * self.unit_price
def __str__(self):
return self.name
class BaseRecipe(models.Model):
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name='user_base_recipes')
restaurant = models.ForeignKey(Restaurant, null=True, on_delete=models.SET_NULL,
related_name='restaurant_base_recipes')
title = models.CharField(_('Base recipe title'), max_length=255, help_text=_('Base recipe. Example: Chicken broth'),
blank=True)
elaboration = models.TextField(_('Elaboration'), blank=True, help_text=_('Base recipe making instructions.'))
quantity = models.DecimalField(_('Quantity'), max_digits=9, decimal_places=3,
help_text=_('Quantity produced with this recipe (after cooking). '))
unit = models.CharField(_('Unit'), max_length=6, choices=UNITS)
expiry_date = models.DateField(_('Expiry date'), help_text=_('Last day product is safe to consume.'))
ingredients = models.ManyToManyField(Product, through='IngredientBaseRecipe', related_name='base_recipe_ingredients')
image = models.ImageField(_('Picture'), upload_to=get_picture_path, blank=True, null=True)
exhausted = models.BooleanField(_('Exhausted'), default=False, help_text=_('If it\'s needed to produce more.'))
next_batch_date = models.DateField(_('Next batch date'), blank=True,
help_text=_('When it\'s necessary to prepare more of this base recipe.'))
storage = models.CharField(_('Storage'), max_length=12, choices=STORAGE_METHODS, blank=True)
class Meta:
ordering = ['-id']
#property
def cost(self) -> Decimal:
return sum([ingredient.cost for ingredient in self.ingredients.all()])
#property
def allergens(self):
allergens_ids = IngredientBaseRecipe.objects.filter(base_recipe=self).values_list('product__allergens',
flat=True)
allergens = Allergen.objects.filter(id__in=allergens_ids)
return allergens
def __str__(self):
return self.title
class IngredientBaseRecipe(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
base_recipe = models.ForeignKey(BaseRecipe, on_delete=models.CASCADE)
product_quantity = models.DecimalField(_('Product quantity'), max_digits=9, decimal_places=3, default=0.0)
class Meta:
ordering = ['-id']
#property
def allergens(self):
return self.product.allergens.all()
#property
def cost(self) -> Decimal:
return self.quantity * self.product.unit_price
def __str__(self):
return self.product.name
serializer.py:
class SimpleProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'name', ]
class IngredientBaseRecipeSerializer(serializers.ModelSerializer):
# product_name = serializers.ReadOnlyField(source='product.name')
product = SimpleProductSerializer(read_only=True)
class Meta:
model = IngredientBaseRecipe
exclude = ['base_recipe', ]
class BaseRecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientBaseRecipeSerializer(many=True, read_only=True)
allergens = AllergenSerializer(many=True, read_only=True)
cost = serializers.FloatField(read_only=True)
class Meta:
model = BaseRecipe
fields = '__all__'
Result:
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": 5,
"ingredients": [
{
"id": 1
},
{
"id": 3
},
{
"id": 3
}
],
"allergens": [
{
"id": 9,
"name": "Celery",
"icon": null
},
{
"id": 12,
"name": "Sulphites",
"icon": null
}
],
"title": "Tipical Spanish",
"elaboration": "No se tio.",
"quantity": "23232.000",
"unit": "l",
"expiry_date": "2021-02-28",
"image": null,
"exhausted": false,
"next_batch_date": "2021-03-01",
"storage": "freezer",
"user": 1,
"restaurant": 1
},
{
"id": 3,
"ingredients": [],
"allergens": [],
"cost": 0.0,
"title": "Chicken broth",
"elaboration": "One, two, three.",
"quantity": "45.000",
"unit": "l",
"expiry_date": "2021-02-28",
"image": null,
"exhausted": false,
"next_batch_date": "2021-03-01",
"storage": "freezer",
"user": 1,
"restaurant": 1
}
]
}
BaseRecipe.ingredients will return a queryset of Product instances but you are passing them to the IngredientBaseRecipeSerializer. You need to change the source for this field so that you pass the related IngredientBaseRecipe instances to this field
class BaseRecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientBaseRecipeSerializer(
many=True,
read_only=True,
source='ingredientbaserecipe_set'
)
...
As the question states, I'd like to be able to instead of having to pass a PK inside my JSON request in the post request I could pass a different value, like a username "bob" instead of 1.
so the idea is that instead of my request containing:
{
"client": 1,
"urgent": true,
"article": 1,
"quantity": 1,
"order_to": 1
}
it should contain:
{
"client": "bob",
"urgent": true,
"article": 234,
"quantity": 1,
"order_to": 1
}
here are my relevant models:
class UserModel(models.Model):
MEMBERSHIP_TYPE = [
('NM', 'NORMAL'),
('PT', 'PLATA'),
('OR', 'ORO'),
('PL', 'PLATINO'),
]
id = models.AutoField(primary_key=True)
username = models.CharField(max_length=100, unique=True)
photo = models.ImageField(upload_to='images/', blank=True)
address = models.TextField()
client_type = models.CharField(max_length=2,
choices=MEMBERSHIP_TYPE,
default= 'NM')
def __unicode__(self):
return self.name
class ArticleModel(models.Model):
id = models.AutoField(primary_key=True)
code = models.IntegerField(unique=True)
description = models.TextField()
def __str__(self):
return str(self.code)
class SupplierModel(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
address = models.TextField()
articles = models.ManyToManyField(ArticleModel)
def __str__(self):
return self.name
class OrderModel(models.Model):
id = models.AutoField(primary_key=True)
client = models.ForeignKey('UserModel', on_delete=models.CASCADE)
gen_time = models.DateTimeField(auto_now_add=True)
gen_supplied = models.DateTimeField(null=True, blank=True)
urgent = models.BooleanField()
order_to = models.ForeignKey('OrderToModel', on_delete=models.CASCADE)
article = models.ForeignKey('ArticleModel', on_delete=models.CASCADE)
quantity= models.IntegerField()
and Serializer:
class OrderCreateSerializer(serializers.ModelSerializer):
# order_to = OrderToPolymorphicSerializer()
class Meta:
model = OrderModel
fields = ('client', 'urgent', 'article', 'quantity', 'order_to')
Help is much appreciated.
Thank you in advance.
you can do this
class OrderCreateSerializer(serializers.ModelSerializer):
client = serializers.SlugRelatedField(
slug_field='username',
queryset=UserModel.objects.all()
)
article = serializers.SlugRelatedField(
slug_field='code',
queryset=ArticleModel.objects.all()
)
class Meta:
model = OrderModel
fields = ('client', 'urgent', 'article', 'quantity', 'order_to')
I try to serialize and save following json
{
"start": "2017-12-12",
"end": "2017-12-12",
"has_refund": false,
"room": {
"key": 0
},
"reserved_days": [ {
"date": "2017-12-12",
"price": "2",
"paymentMethod": "3"
},
{
"date": "2017-12-13",
"price": "2",
"paymentMethod": "3"
}
],
"check_in_time": "14:00",
"check_out_time": "12:00",
"guest_name": "Ivan",
"payed": false
}
SERIALIZERS
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = [
'date',
'price',
'paymentMethod',
]
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = [
'key',
]
class ReservationSerializer(serializers.ModelSerializer):
room = RoomSerializer()
reserved_days = DaySerializer(many=True)
class Meta:
model = Reservation
fields = [
'start',
'end',
'check_in_time',
'check_out_time',
'reserved_days',
'room',
'has_refund',
'payed',
'guest_name',
'reservation_number',
]
def create(self, validated_data):
day_data = validated_data.pop('reserved_days')
room = validated_data.pop('room')
reservation = Reservation.objects.create(**validated_data)
existing_room = Room.objects.get(key=room['key'])
reservation.room = existing_room
reservation.save()
for day in day_data:
day, created = Day.objects.get_or_create(date=day['date'], price=day['price'], paymentMethod=day['paymentMethod'])
reservation.reserved_days.add(day)
return reservation
MODEL
class Day(models.Model):
date = models.DateField(auto_now=False, auto_now_add=False)
price = models.FloatField()
paymentMethod = models.CharField(max_length = 200)
def __unicode__(self):
return str(self.date)
class Room(models.Model):
name = models.CharField(max_length = 200, null=True)
key = models.AutoField(primary_key=True)
def __unicode__(self):
return self.name
class Reservation(models.Model):
start = models.DateField(verbose_name='Заезд', auto_now=False, auto_now_add=False, blank=False)
end = models.DateField(verbose_name='Выезд', auto_now=False, auto_now_add=False, blank=False)
check_in_time = models.TimeField(verbose_name='Время заезда', blank=False)
check_out_time = models.TimeField(verbose_name='Время выезда', blank=False)
has_refund = models.BooleanField(verbose_name='Возвратная бронь', default=True)
payed = models.BooleanField(verbose_name='Оплачено', default=False)
room = models.ForeignKey(Room, null=True, blank=True, verbose_name='Номер', on_delete=models.CASCADE)
reserved_days = models.ManyToManyField(Day, blank=False)
guest_name = models.CharField(verbose_name='Имя гостя', max_length=200, blank=True)
reservation_number = models.CharField(verbose_name='Номер брони', max_length=200, blank=True)
In response I get 500 error and this
KeyError at /core/create/
'key'
However, if I change field of room to name (inside room serializer and create method of reservation serializer), everything works perfectly
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = [
'name',
]
class ReservationSerializer(serializers.ModelSerializer):
room = RoomSerializer()
reserved_days = DaySerializer(many=True)
class Meta:
model = Reservation
fields = [
'start',
'end',
'check_in_time',
'check_out_time',
'reserved_days',
'room',
'has_refund',
'payed',
'guest_name',
'reservation_number',
]
def create(self, validated_data):
day_data = validated_data.pop('reserved_days')
room = validated_data.pop('room')
reservation = Reservation.objects.create(**validated_data)
existing_room = Room.objects.get(name=room['name'])
reservation.room = existing_room
reservation.save()
for day in day_data:
day, created = Day.objects.get_or_create(date=day['date'], price=day['price'], paymentMethod=day['paymentMethod'])
reservation.reserved_days.add(day)
return reservation
Seems that it works only with strings.
How to make it work with number ? The same thing happened with me when I tried saving with pk instead of separated int fieled