Get data from two tables using foreign key in Django - django-models

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

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 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.

serialize multiple related 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

How to count deep relationship fields with Django?

How could I count the relationship of the models below?
class Client(models.Model):
name = models.CharField(max_length=150)
class Pet(models.Model):
client = models.ForeignKey(Client, related_name='pet')
name = models.CharField(max_length=150)
class Photo(models.Model):
pet = models.ForeignKey(Pet, related_name='photo')
photo = models.ImageField(storage=DIR)
I would like to return the total pet and total photos taken of all pets dr to a single customer client, with the following answer:
[
{
"name": "john",
"totalPet": 3, // imagine 10 photos were taken of each pet
"totalPhoto": 30,
},
{
"name": "Mary",
"totalPet": 2, // imagine that 10 photos were taken of one and 5
// of another pet
"totalPhoto": 15,
},
]
My serializer is as follows:
class ClientSerializer(ModelSerializer):
totalPet = serializers.SerializerMethodField()
def get_totalPet(self, obj):
return obj.pet.count()
class Meta:
model = Client
fields = ('name', 'totalPet')
As can be observed, I can total the amount of pets for each customer, but I can't find a way to total the photos of all pets of a customer.
What would be the best way to do this photo count?
You can perform a filter on Photo model with your client.pet queryset, using the __in lookup.
At first, I would define a custom QuerySet for our Photo model:
from django.db import models
class PhotoQuerySet(models.QuerySet):
def for_client(self, client):
# Get all related pets to the current client
client_pets = client.pet.all()
# Filter Photos that are linked with these pets
return self.filter(pet__in=client_pets)
And then inside the model:
class Photo(models.Model):
...
objects = PhotoQuerySet.as_manager()
This will allow you to do the following: Photo.objects.for_client(client=...)
After that, you can use this method in the serializer:
class ClientSerializer(ModelSerializer):
total_pets = serializers.SerializerMethodField()
total_photos = serializers.SerializerMethodField()
def get_total_pets(self, obj):
return obj.pet.count()
def get_total_photos(self, obj):
# Calling our newly created method and aggregating count
return Photo.objects.for_client(client=obj).count()
class Meta:
model = Client
fields = ('name', 'total_pets', 'total_photos')

How to add a snippet of category, used in a different snippet

How do I have a snippet of category be used in another snippet called resource? I want to have a list of Resources filtered by category and category is a snippet of its own. I'm getting this error when I try to create a Resource:
AttributeError at /admin/snippets/home/resource/add/
'ForwardManyToOneDescriptor' object has no attribute 'rel'
Here is my code:
class Resource(models.Model):
"""Snippet for Resources"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
resource_name = models.CharField(max_length=128, blank=False, null=True)
phone_number = models.CharField(max_length=12, blank=True, null=True)
website = models.URLField(blank=True, null=True)
info = RichTextField(blank=True, null=True)
category = models.ForeignKey('ResourceCategory', on_delete=models.CASCADE, null=True, blank=True)
panels = [
MultiFieldPanel(
[
FieldPanel("resource_name"),
FieldPanel("phone_number"),
],
heading="Resource information"
),
MultiFieldPanel(
[
FieldPanel('website')
],
heading="Links"
),
MultiFieldPanel(
[
FieldPanel('info')
],
heading="Info"
),
MultiFieldPanel(
[
InlinePanel("category", label="Category")
]
)
]
def __str__(self):
"""String representation of this class"""
return self.resource_name
class Meta:
verbose_name = "Resource"
verbose_name_plural = "Resources"
register_snippet(Resource)
class ResourceCategory(models.Model):
"""Snippet for Resources"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
category_name = models.CharField(max_length=128, blank=False, null=True)
panels = [
MultiFieldPanel(
[
FieldPanel('category_name')
],
heading="Category"
)
]
def __str__(self):
"""String representation of this class"""
return self.category_name
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
register_snippet(ResourceCategory)
The category field should use FieldPanel('category'), not InlinePanel. InlinePanel is for managing multiple child objects belonging to the snippet, but here a Resource only belongs to a single ResourceCategory.
(If you do intend to allow a resource to belong to multiple categories, you'll need to adjust your models so that Resource has a child model, containing a ParentalKey to Resource and a ForeignKey to ResourceCategory.)

Resources