serialize multiple related models - django-models

I am just learning django and rest-framework.
I have three models User, UserHospital and Timeslots. User is having time schedule for hospitals. I am requesting for users all details with related hospitals which displays hospital details along with timeslots. Want to represent user details in below format.
Whats wrong in my code?
Using viewsets and serializers it can be possible or i have to try another way?
{
"first_name": "abc",
"last_name": "xyz",
"mobile_number":1111111111,
"related_hospitals": [{
"id": 1,
"name": "bbbb"
"timeslot": [
{
"day": "TUE",
"start_time": "09:00:00",
"end_time": "15:00:00"
},
{
"day": "WED",
"start_time": "10:00:00",
"end_time": "20:00:00"
}
]
},
{
"id": 2,
"name": "ccc"
"timeslot": []
}]
}
created Models as below :
class Users(models.Model):
mobile_number = models.BigIntegerField()
first_name = models.CharField(max_length=255, null=True)
last_name = models.CharField(max_length=255, null=True)
class TimeSlots(BaseAbstract):
DAYS = (
('SUN', 'sunday'),
('MON', 'Monday'),
('TUE', 'tuesday'),
('WED', 'wednesday'),
('THU', 'thursday'),
('FRI', 'friday'),
('SAT', 'saturday'),
)
STATUS = (
(1, 'HOLIDAY'),
(2, 'ON_LEAVE'),
(3, 'AVAILABLE'),
(4, 'NOT_AVAILABLE')
)
DEFAULT_STATUS = 3
DEFAULT_DAY = "SUN"
day = models.CharField(default=DEFAULT_DAY, choices=DAYS, max_length=20)
start_time = models.TimeField()
end_time = models.TimeField()
status = models.SmallIntegerField(default=DEFAULT_STATUS, choices=STATUS)
class UserHospital(BaseAbstract):
user = models.ForeignKey('users.Users', on_delete=models.SET_NULL, null=True)
name = models.(Hospital,CharField(max_length=255, null=True)
timeslots = models.ManyToManyField(TimeSlots)
I have tried:
class TimeslotSerializer(serializers.ModelSerializer):
class Meta:
model = TimeSlots
fields = ('day', 'start_time', 'end_time')
read_only_fields = ('id',)
class RelatedHospitalSerializer(serializers.ModelSerializer):
timeslot = TimeslotSerializer(many=True)
class Meta:
model = UserHospital
fields = ('name', 'timeslot')
read_only_fields = ('id',)
class UserDetailsSerializer(serializers.ModelSerializer):
related_hospitals = serializers.SerializerMethodField()
def get_related_hospitals(self, obj):
hospitalData = []
if UserHospital.objects.all().filter(user=obj).exists():
hospitalData = UserHospital.objects.all().filter(user=obj)
return RelatedHospitalSerializer(hospitalData).data
class Meta:
model = Users
fields = ('first_name', 'last_name','mobile_number','related_hospitals')
read_only_fields = ('id', 'related_hospitals')
class UserDetailsViewset(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = Users.objects.all()
serializer_class = UserDetailsSerializer
def get_queryset(self):
userid = self.request.query_params.get('userid')
if userid is not None:
userData = Users.objects.filter(user=userid)
return userData
else:
return Users.objects.all()
whats wrong with my code ?

I would recommend using related_name parameter of models.ForeignKey, ..ManytoMany field etc
For example,
def Hospital(models.Model):
user = models.ForeignKey(....., related_name="hospitals")
...
def HospitalSerializer(models.Model):
...
def UserSerializer(Hyperlinkedmodelserializer ...(or other):
hospitals = HospitalSerializer(many=True)
class Meta:
....
Note: The use of "hospitals" ....
This will automatically allow one to get the result of a
UserSerializer(userModel, context={'request':request}).data ...
in your desired format

Related

How can I send the Name of the Item instead of PK in Django-Rest-Framework using POSTMAN?

So I am creating a E-Commerce API using the Django Rest Framework and I have been trying to send the name of the Item instead of the PK of the Item to create an order.
These are the models I am using:
class Product(models.Model):
product_tag = models.CharField(max_length=10)
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.IntegerField()
stock = models.IntegerField()
image = models.ImageField(default="default.png")
in_stock = models.BooleanField(default=True)
date_created = models.DateField(auto_now_add=True)
class Meta:
ordering = ["-date_created"]
def __str__(self):
return self.name
class PlacedOrder(models.Model):
ordered_by = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
phone = models.CharField(max_length=15)
address = models.CharField(max_length=100)
zipcode = models.CharField(max_length=100)
items = models.ManyToManyField(Product)
total_price = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
return f"{self.ordered_by}"
This is my serializer that I am working with to create an order:
class PlacedOrderSerializer(serializers.ModelSerializer):
ordered_by = serializers.ReadOnlyField(source="ordered_by.email")
class Meta:
model = PlacedOrder
fields = (
"id",
"created_at",
"ordered_by",
"first_name",
"last_name",
"phone",
"address",
"zipcode",
"items",
"total_price",
)
Here is the view I am using to create an order:
class CreateOrder(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated]
queryset = PlacedOrder.objects.all()
serializer_class = PlacedOrderSerializer
def perform_create(self, serializer):
serializer.save(ordered_by=self.request.user)
This is my input in POSTMAN:
{
"first_name": "yes",
"last_name": "no",
"phone": "0100000000",
"address": "whatever address",
"zipcode": "254",
"items": [
1,
1,
2
],
"total_price": "69"
}
and this is the output:
{
"id": 13,
"created_at": "2022-10-26T20:56:08.789574Z",
"ordered_by": "bal#bal.com",
"first_name": "yes",
"last_name": "no",
"phone": "0100000000",
"address": "whatever address",
"zipcode": "254",
"items": [
1,
2
],
"total_price": 69
}
I basically want it to take the name and quantity of the items instead of the PK of the product in the input.
I have tried using RelatedField but that makes it so that "items" just goes null to the backend without taking any products and makes a blank order.
Try this approach:
class PlacedOrderSerializer(serializers.ModelSerializer):
# HERE
ordered_by = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model = PlacedOrder
fields = (
"id",
"created_at",
"ordered_by",
"first_name",
"last_name",
"phone",
"address",
"zipcode",
"items",
"total_price",
)
But make sure to add self.name to the models __str__.
You need to use a SlugRelatedField [DRF-doc] here, like:
class PlacedOrderSerializer(serializers.ModelSerializer):
ordered_by = serializers.ReadOnlyField(source="ordered_by.email")
items = serializers.SlugRelatedField(
many=True,
slug_field='name',
queryset=Product.objects
)
class Meta:
model = PlacedOrder
fields = (
# ...
)
This will thus make the serializer interpret the name fields, and query the Product objects with that name, and add these to the many-to-many relation.
Note that this will result in a query for each item, so if the list of items can be long, you might want to consider a different strategy, like serializing a list of product PKs, and then in the view construct a mapping name -> product, and use that to construct the many-to-many relation.
Note: It is normally better to use a slug field (a short identifier), than a name field for these kinds of relations. Slugs are meant to be database friendly (only contain ascii characters, no spaces, etc), and furthermore slugs normally do not change, whilst a name can. So if you later rename a Product, then the order will no longer point to the correct product.

How to make it by list comprehension

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
First you need to move the cleaningtype field into the ServiceType model.
And I think it's better to set the related_name from the view of the target model.
class ServiceType(models.Model):
cleaningtype = models.ForeignKey(
CleaningType, related_name='service_types', on_delete=models.CASCADE)
...
class Service(models.Model):
servicetype = models.ForeignKey(
ServiceType, related_name='services', 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)
And in CleaningTypeSerializer
class CleaningTypeSerializer(serializers.ModelSerializer):
service_objects = ServiceTypeSerializer(many = True, read_only = True)
service_types = serializers.SerializerMethodField()
class Meta:
Model = CleaningType
fields = ('id', 'cleaning_type', 'service_types',)
def get_service_types(self, obj):
return list(obj.service_types.values_list('service_type').distinct())
Finally in views.py,
class CleaningTypesViewSet(ModelViewSet):
serializer_class = CleaningTypeSerializer
queryset = CleaningType.objects.all()
You can get the list of CleaningType data from this CleaningTypesViewSet.

Count the no of movies in django rest framework

I want to count the no of movies worked by the actors in api. i have tried this but not working
models.py
class Actor(models.Model):
actors = models.CharField(max_length=100)
class Genre(models.Model):
genre = models.CharField(max_length=100)
class Movie(models.Model):
name = models.CharField(max_length=100)
actors = models.ManyToManyField(Actor, related_name="actor_movies")
genre = models.ManyToManyField(Genre,related_name="genre_movies")
serializers.py
class ActorSerializer(serializers.HyperlinkedModelSerializer):
actor_movies = serializers.RelatedField(source='actor.movies', read_only = True)
class Meta:
model = Actor
fields = ['id','actors','actor_movies']
class GenreSerializer(serializers.HyperlinkedModelSerializer):
genre_movies = serializers.RelatedField(source='genre.names', read_only = True)
class Meta:
model = Genre
fields = ['id','genre','genre_movies']
class MovieSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Movie
fields = ['url','name','actors','genre']
im getting output like this
[
{
"id": 1,
"genre": "Ajith"
},
{
"id": 2,
"genre": "Vijay"
}
]
in actual output i need the total no of movies he worked also.
You can use a SerializerMethodField:
class ActorSerializer(serializers.HyperlinkedModelSerializer):
count_actor_movies= serializers.SerializerMethodField()
def get_count_actor_movies(self, instance):
return instance.actor_movies.count()
class Meta:
model = Actor
fields = ['id','actors','count_actor_movies']
For more details, the official docs are here.

how to get foreignkey fields in serializer

In serializer, class I am trying to get the category details such as "name" but the following code gives me the foreignkey id
models.py
class Category(MP_Node, Timestamps):
name = models.CharField(_('Name'), max_length=255, db_index=True)
class VideoCategory(Category):
image = models.ImageField(upload_to='video_categories', blank=True, null=True, max_length=255)
class VideoCategoryVideo(BaseModel, Timestamps, SoftDelete):
video = models.ForeignKey(Video, on_delete=models.CASCADE)
category = models.ForeignKey(VideoCategory, on_delete=models.CASCADE, null=True)
serializers.py
class VideoCategoryVideoSerializer(serializers.ModelSerializer):
class Meta:
model = VideoCategoryVideo
fields = ('category', )
class VideosDetailsListSerializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
class Meta:
model = Video
fields = ('id', 'create_date', 'category')
def get_category(self, data):
cate = VideoCategoryVideo.objects.filter(video=data.id)
category = VideoCategoryVideoSerializer(cate, many=True)
return category.data
result is:
"category": [
{
"category": 1
}]
but the expected result is
"category": [
{
"name": "cate_name"
}]
You can add a CharField to VideoCategoryVideoSerializer and specify the source of the value like this:
class VideoCategoryVideoSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='category.name', read_only=True)
class Meta:
model = VideoCategoryVideo
fields = ('category', 'name')
This will tell the serializer to get the value for name from the related category.

Get data from two tables using foreign key in Django

I have a vendor table it has following structure:
id vendor_name
I have another table named purchases that have the following structure:
id product_id us er_id vendor_id
Then, there is auth_user table by default that has users email address, first_name, last_name etc.
I need to create an API in django class based so that I can get users email address, first_name, last_name etc. based on vendor_id column that is in purchase table when I use GET /vendor/{vendor_id}
Note: I need this API to be created in class based model and views:
My code:
model.py
class Vendor(models.Models):
vendor_name = models.CharField(max_length=25)
class Meta:
ordering = ('vendor_name',)
def __str__(self):
return self.vendor_name
class Purchases(models.Models):
user = models.OneToOneFiled(User, on_delete=models.CASECADE)
product_id = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True, blank=True)
vendor_id = models.ForeignKey('Vendor', on_delete=models.SET_NULL, null=True, blank=True)
def __str__(self):
return self.vendor_id
views.py
class VendorDetail(generics.RetrieveUpdateDestoryAPIView):
queryset= Vendor.objects.all()
serializer_class = VendorSerializer
name = 'vendor-details'
I want JSON response as below. Also, I want the fields to be in same order as below:
{
"id": 1,
"name": "group-1"
"users": [
{
"id": 1,
"email": "abc#gmail",
"first_name": "myfirstname1",
"last_name": "mylastname1" },
{
"id": 2,
"email": "xyz#gmail",
"first_name": "myfirstname2",
"last_name": "mylastname2"
}
],
}
you can get all data by using values
In your Purchases model change vendor_id line like this;
vendor_id = models.ForeignKey('Vendor', on_delete=models.SET_NULL, null=True, blank=True, related_name='vendor_purchases')
And then you can get all data like this; NOTE if you do not use prefetch_related in for loop it access database for each user but now for all data it access database only 3 times
vendor = Vendor.objects.get(pk=vendor_id).prefetch_related(vendor_purchases).prefetch_related(vendor_purchases__user)
user_list = []
for purchase in vendor.vendor_purchases.all():
purchase_user = {}
purchase_user["id"] = purchase.user.pk
purchase_user["email"] = purchase.user.email
purchase_user["first_name"] = purchase.user.first_name
purchase_user["last_name"] = purchase.user.last_name
user_list.append(purchase_user)
result={}
result["id"] = vendor.pk
result["name"] = vendor.name
result["users"] = user_list

Resources