Writable nested serializer method getting an Error while posting a request - django

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

Related

Django - Too many similar queries

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")

Get a list of all the field values from a Foreign Key Table in Django

am new to Django and currently trying the Foreign Key concept. I have three models as shown below.
class Basket(models.Model):
basket_name = models.CharField(max_length=5, unique=True)
def __str__(self):
return self.basket_name
class Product(models.Model):
Grams = 'GM'
Kilograms = 'KG'
WeightBased = 'WPG'
QuantityBased = 'CPG'
PRODUCT_UNIT_WT_CHOICES=[
(Grams, 'Grams'),
(Kilograms, 'Kilograms')
]
PRODUCT_TYPE_CHOICES =[
(WeightBased, 'Weight Based Product'),
(QuantityBased, 'Quantity Based Product')
]
product_name = models.CharField(max_length=30, unique=True)
product_description = models.TextField(max_length=300)
product_price = models.DecimalField(max_digits=5, decimal_places=2)
product_unit_weight = models.DecimalField(max_digits=5, decimal_places=2)
product_unit_weight_units = models.CharField(max_length=2, choices=PRODUCT_UNIT_WT_CHOICES, default=Grams)
product_type = models.CharField(max_length=3, choices=PRODUCT_TYPE_CHOICES, default=QuantityBased)
product_image = models.ImageField(upload_to=imageUploadPath, null=True, blank=True)
def __str__(self):
return self.product_name
class BasketProductMapping(models.Model):
basket_reference = models.ForeignKey(Basket, on_delete=models.CASCADE)
product_reference = models.ForeignKey(Product, on_delete=models.CASCADE)
mapped_basket_name = models.CharField(max_length=5,null=False, blank=False)
mapped_product_name = models.CharField(max_length=30, null=False, blank=False)
Here are my serializers:
class ProductSerializer(serializers.ModelSerializer):
product = serializers.CharField(read_only=True)
class Meta:
model = Product
fields = ['id', 'product_name', 'product_description', 'product_price', 'product_unit_weight',
'product_unit_weight_units', 'product_type', 'product_image']
class BasketSerializer(serializers.ModelSerializer):
basket = serializers.CharField(read_only=True)
class Meta:
model = Basket
fields = ('id', 'basket_name')
class BasketProductMappingSerializer(serializers.ModelSerializer):
basket_reference = serializers.CharField(source ='basket.basket_name', read_only=True)
product_reference = serializers.CharField(source='product.product_name', read_only=True)
class Meta:
model = BasketProductMapping
fields = ['id', 'basket_reference', 'product_reference', 'mapped_basket_name', 'mapped_product_name']
What I am trying to achieve is get a list of all the values in 'basket_name' and 'product_name' when I call the BasketProductMappingSerializer. But the output I am getting is this:
{
"status": 1,
"message": "Basket Product Mapping List",
"data": [
{
"id": 1,
"mapped_basket_name": "A1",
"mapped_product_name": "XYZ"
}
]
}
This is my views.py code:
class BasketProductViewSet(APIView):
def get(self, request):
if request.GET.get('id'):
print('Basket Product Mapping Details')
basketProductMappingData = BasketProductMapping.get(id = request.GET.get('id'))
serializer = BasketProductMappingSerializer(basketProductMappingData)
else:
basketProductMappingData = BasketProductMapping.objects.all().values('id', 'basket_reference__basket_name', 'product_reference__product_name', 'mapped_basket_name', 'mapped_product_name')
serializer = BasketProductMappingSerializer(basketProductMappingData, many=True)
response = {'status':1, 'message':"Basket Product Mapping List", 'data':serializer.data}
Where am I going wrong? Sorry if my question is very trivial. Thanks for your help in advance.
i see that there is an error in your views BasketProductMapping.get(id = request.GET.get('id')) should be BasketProductMapping.objects.get(id = request.GET.get('id'))
i don't know if it gonna work but can you try
basket_reference = serializers.ReadOnlyField(source ='basket.basket_name')
product_reference = serializers.ReadOnlyField(source='product.product_name')
instead of
basket_reference = serializers.CharField(source ='basket.basket_name', read_only=True)
product_reference = serializers.CharField(source='product.product_name', read_only=True)

How to get a non-pk value to be the value by which a post request is made in django rest framework

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')

having the full field object instead of it's ID in django model

with these two models TblPhoto
class TblPhoto(models.Model):
#path = models.CharField(max_length=256)
type = models.ForeignKey('TblPhotoTypes', models.DO_NOTHING, blank=True, null=True)
path = models.ImageField(upload_to='images')
def __str__(self):
return str(self.id)
class Meta:
managed = False
db_table = 'tbl_photo'
verbose_name = "Photo"
verbose_name_plural = "Photos"
and TblCustomer
class TblCustomer(models.Model):
name = models.CharField(max_length=32)
email = models.CharField(max_length=32)
password = models.CharField(max_length=32)
token = models.CharField(max_length=64, blank=True, null=True)
firebase_token = models.CharField(max_length=64, blank=True, null=True)
registration_date = models.IntegerField(blank=True, null=True)
last_login_date = models.IntegerField(blank=True, null=True)
about = models.TextField(max_length=128, blank=True, null=True)
active = models.BooleanField(blank=True, null=True)
photo = models.ForeignKey(TblPhoto, models.DO_NOTHING)
def __str__(self):
return str(self.id)+' : '+self.name
class Meta:
managed = False
db_table = 'tbl_customer'
verbose_name = "Customer"
verbose_name_plural = "Customers"
the api response is...
{
"response_status": {
"code": 200,
"internalMessage": "",
"message": ""
},
"user": {
"id": 1,
"name": "deya",
"email": "gmail#gmail.com",
"password": "",
"token": null,
"firebase_token": null,
"registration_date": null,
"last_login_date": null,
"about": "hello, i'm Deya !",
"active": true,
"photo_id": 4
}
}
notice the field photo_id, how can I alter the TblCustomer model to show the photo object instead of the photo id?
Disclaimer : I can do this easily out of the model, but I want it to be in the
model class.
this is how I do it now... to get the additional photo_url key but I want to do it from the model....
class CustomerProfileView(BaseView):
def post(self, request, format=None):
requestBody = json.loads(request.body)
id = requestBody["id"]
try:
user = TblCustomer.objects.values().get(id=id)
photo = TblPhoto.objects.values().get(id=user["photo_id"])
user["photo_url"] = request.scheme+"://"+request.get_host()+"/media/"+photo["path"]
self.responseDict["user"] = user
except Exception as e:
return Helpers().statusCode(404,e)
return JsonResponse(self.responseDict)

Django Rest Framework - can save foreign key with number value

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