I'm using DRF. I've got the following code where I annotate a queryset of Company objects with an additional joined field. It seems annotate is adding an extra object to my queryset.
views.py
def get(self, request, **kwargs):
user = request.user
companies = Company.objects.all()
print(companies)
user_in_queue = When(queue__users=user, then = True)
companies = Company.objects.annotate(joined=Case(
user_in_queue, default = False
))
print(companies)
models.py
class Company(models.Model):
name = models.CharField(max_length=15, unique=True)
def __str__(self) -> str:
return self.name
class User(AbstractUser):
def __str__(self) -> str:
return f"{self.username}"
class Queue(models.Model):
company = models.OneToOneField(
Company, on_delete=models.CASCADE, related_name="queue"
)
users = models.ManyToManyField(User, through="QueueDetails", related_name="queues")
class QueueDetails(models.Model):
queue = models.ForeignKey(
Queue,
related_name="queue_details",
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
related_name="queue_details",
on_delete=models.CASCADE,
)
the first print gives me
<QuerySet [<Company: kfc>, <Company: kfc>]>
With a json of
[
{
"name": "kfc",
"id": 1,
},
{
"name": "kfc",
"id": 2,
}
]
The second gives me
<QuerySet [<Company: kfc>, <Company: kfc>, <Company: kfc>]>
With a json of
[
{
"name": "kfc",
"id": 1,
"joined": null
},
{
"name": "kfc",
"id": 1,
"joined": true
},
{
"name": "kfc",
"id": 2,
"joined": null
}
]
I want
a json of
[
{
"name": "kfc",
"id": 1,
"joined": true
},
{
"name": "kfc",
"id": 2,
"joined": null
}
]
You want to work with an Exists subquery [Django-doc] to prevent joining:
from django.db.models import Exists, OuterRef
def get(self, request, **kwargs):
user = request.user
companies = Company.objects.annotate(
joined=Exists(
Queue.objects.filter(company_id=OuterRef('pk'), users=request.user)
)
)
# …
For the serializer, you add a joined field:
class CompanySerializer(serializers.ModelSerializer):
joined = serializers.BooleanField()
# …
Related
I need some help for the creation of 2 queryset .
Calculates the total of amounts of the objects considering the user as a common attribute.
Given a specific user, calculate considering the categories, ordering the result in "in" and "out".
This is the information that already exists in my db (we can suppose that that's the serialized information of all the objects of my only Model:
[
{
"money": "-51.13",
"type": "out",
"category": "savings",
"user": "jane-doe"
},
{
"money": "2500",
"type": "in",
"category": "salary",
"user": "foo-bar"
},
{
"money": "-150.72",
"type": "out",
"category": "cellphone",
"user": "foo-bar"
},
{
"money": "-560.00",
"type": "out",
"category": "rent",
"user": "jane-doe"
},
This should be the output, queryset 1:
This consider all the data in the database.
[
{
"user": "jane-doe",
"total_in": "0",
"total_out": "-611.13"
},
{
"user": "foo-bar",
"total_in": "2500",
"total_out": "-150.72"
}
]
This should be the output, queryset 2:
This must consider de information of a specific user. The next information is just an example of the expected output. Suppose that the endpoint might be /operations/{user_email}/summary
{
"in": {
"salary": "1600.51",
"savings": "550.50"
},
"out": {
"groceries": "-41.23",
"rent": "-1286.68",
"transfer": "-605.15"
}
}
This is my models.py
from django.db import models
from rest_framework.exceptions import ValidationError
class Operation(models.Model):
TYPE_CHOICES = (
("out", "Out"),
("in", "In"),
)
type = models.CharField(
max_length=10, choices=TYPE_CHOICES, default="outflow"
)
money= models.DecimalField(max_digits=12, decimal_places=2,
validators=[])
category = models.CharField(max_length=15)
user = models.EmailField(max_length=254)
class Meta:
ordering = ("-user",)
def __str__(self):
return str(self.user)
def clean(self):
if self.type == 'out' and self.money > 0:
raise ValidationError('Out entries doesn\'t must be positive.')
if self.type == 'in' and self.money < 0:
raise ValidationError('In entries doesn\'t must be negative.')
This is my serializers.py in case you need.
from rest_framework import serializers
from core.base.models import Operation
class OperationsSerializer(serializers.ModelSerializer):
class Meta:
model = Operations
fields = ['money', 'type', 'category', 'user']
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
I'd create a view for the parent category and then get child categories for each parent category. First, you should create a serializer for CleaningType model:
class CleaningTypeSerializer(serializers.ModelSerializer):
service_types = serializers.SerializerMethodField('get_service_types')
def get_service_types(self, cleaning_type_name):
return Service.objects.filter(cleaning_type=cleaning_type_name).values_list("service_name", flat=True)
class Meta:
model = CleaningType
fields = "__all__"
Then, create a view using the new serializer:
class CleaningTypesViewSet(ModelViewSet):
serializer_class = CleaningTypeSerializer
http_method_names = ["get"]
queryset = CleaningType.objects.all()
The response looks something like this:
[
{
"id": 1,
"service_types": [
"Moving Service 1",
"Moving Service 2",
"Moving Service 3"
],
"cleaning_type_name": "Moving Cleaning",
"price": "200.00"
},
{
"id": 2,
"service_types": [
"Another Service 1",
"Another Service 2"
],
"cleaning_type_name": "Another Cleaning",
"price": "300.00"
}
]
im filtering that user whoes order_status is completed and who have listing_id 5001. But im getting output data repeated
Here is my Code:
models.py
class Users(models.Model):
name = models.CharField(max_length=100)
phone = models.CharField(max_length=20, blank=True, null=True)
'''
class Meta:
managed = False
db_table = 'users'
class UserOrder2(models.Model):
order_id = models.AutoField(primary_key=True)
order_status = models.CharField(max_length=30,default='None')
listing_id = models.CharField(max_length=250,default='None')
user_id = models.ForeignKey(Users, on_delete=models.CASCADE, db_column="user_id")
'''
class Meta:
managed = False
db_table = 'user_order'
class UserOrderProduct2(models.Model):
order_id = models.ForeignKey(UserOrder2, on_delete=models.CASCADE, db_column="order_id")
product_name = models.CharField(max_length=100)
...
class Meta:
managed = False
db_table = 'user_order_product'
Views.py
class UserPurchaseQuantityView(generics.GenericAPIView):
def post(self, request):
listing_id= request.data.get('listing_id')
kwargs = {}
kwargs['userorder2__listing_id'] = listing_id
kwargs['userorder2__order_status'] = 'Order Delivered'
queryset = Users.objects.filter(**kwargs)
data = UsersSerializer(queryset, many=True).data
return Response(data)
serializers.py
class UserOrderProductSerializer2(serializers.ModelSerializer):
class Meta:
fields = ["product_id", "product_quantity", "product_price", "sub_total",
"product_name"]
model = UserOrderProduct2
class UserOrderSerializer(serializers.ModelSerializer):
product_detail = UserOrderProductSerializer2(source="userorderproduct2_set", many=True)
class Meta:
fields = ["user_id", "order_date", "product_detail"]
model = UserOrder2
class UsersSerializer(serializers.ModelSerializer):
user_detail = UserOrderSerializer(source="userorder2_set", many=True)
class Meta:
fields = "__all__"
model = Users
I'm getting repeated output like this:
[
{
"id": 51238,
"name": "aaa",
"phone": "123456789",
"email": "aaa#gmail.com",
"user_detail": [
{
"user_id": 51238,
"order_date": "2021-07-27 15:55:56"
"product_detail": [
{
"product_id": 20767,
"product_quantity": 1,
"product_price": 150.0,
"sub_total": 150.0,
"product_name": "EMINAZ 2mg Tablet 10's"
]
},
{
"id": 51238,
"name": "aaa",
"phone": "123456789",
"email": "aaa#gmail.com",
"user_detail": [
{
"user_id": 51238,
"order_date": "2021-07-27 15:55:56"
"product_detail": [
{
"product_id": 20767,
"product_quantity": 1,
"product_price": 150.0,
"sub_total": 150.0,
"product_name": "EMINAZ 2mg Tablet 10's"
] },
{
"id": 51238,
"name": "aaa",
"phone": "123456789",
"email": "aaa#gmail.com",
"user_detail": [
{
"user_id": 51238,
"order_date": "2021-07-27 15:55:56"
"product_detail": [
{
"product_id": 20767,
"product_quantity": 1,
"product_price": 150.0,
"sub_total": 150.0,
"product_name": "EMINAZ 2mg Tablet 10's"
] } ]
I think the issue is in **kwargs. Try this
class UserPurchaseQuantityView(generics.GenericAPIView):
def post(self, request):
listing_id= request.data.get('listing_id')
queryset = Users.objects.filter(userorder2__listing_id=listing_id,
userorder2__order_status='Order Delivered')
data = UsersSerializer(queryset, many=True).data
return Response(data)
Add distinct() to make unique.
queryset = Users.objects.filter(**kwargs).distinct()
I can successfully add a new object(s) into notification_days but what is the most idiomatic way to handle removing any object(s)?
models.py
DAYS_OF_WEEK = (
(0, 'Mon'),
(1, 'Tue'),
(2, 'Wed'),
(3, 'Thu'),
(4, 'Fri'),
(5, 'Sat'),
(6, 'Sun'),
)
class WeekDay(models.Model):
day = models.IntegerField(choices=DAYS_OF_WEEK)
def __str__(self):
return self.get_day_display()
class Company(models.Model):
name = models.CharField(...)
notification_days = models.ManyToManyField(WeekDay)
serializers.py
class WeekDaySerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
day_display = serializers.SerializerMethodField()
class Meta:
model = WeekDay
fields = ['id', 'day', 'day_display']
def get_day_display(self, obj):
return obj.get_day_display()
class CompanySettingsSerializer(serializers.ModelSerializer):
notification_days = WeekDaySerializer(many=True)
class Meta:
model = Company
fields = [
'name',
'notification_days'
]
def update(self, instance, validated_data):
notification_days = validated_data.get('notification_days')
for day in notification_days:
day_id = day.get('id', None)
if item_id:
if not instance.notification_days.filter(pk=day_id).exists():
week_day = WeekDay.objects.get(pk=day_id)
instance.notification_days.add(week_day)
return instance
api.py
class CompanySettingsAPIView(RetrieveUpdateAPIView):
authentication_classes = (SessionAuthentication, )
permission_classes = (IsCompanyAdmin, )
serializer_class = CompanySettingsSerializer
def get_object(self):
return Company.objects.get(pk=self.kwargs.get('pk'))
Sample GET response:
{
"name": "Django",
"notification_days": [
{
"id": 1,
"day": 0,
"day_display": "Mon"
},
{
"id": 2,
"day": 1,
"day_display": "Tue"
}
]
}
When I send PUT request with the following body, new week day is successfuly added:
{
"name": "Django",
"notification_days": [
{
"id": 1,
"day": 0,
"day_display": "Mon"
},
{
"id": 2,
"day": 1,
"day_display": "Tue"
},
{
"id": 4,
"day": 3
}
]
}
You need only ids on the request.
And use .set() method to update the relations.
PUT Request:
{
"name": "Django",
"notification_days_ids": [
1,
2,
4
]
}
Serializer:
class CompanySettingsSerializer(serializers.ModelSerializer):
notification_days = WeekDaySerializer(many=True, read_only=True)
notification_days_ids = serializers.PrimaryKeyRelatedField(
many=True,
write_only=True,
source='notification_days', # just to make it looks a little bit better
queryset=WeekDay.objects.all()
)
class Meta:
model = Company
fields = [
'name',
'notification_days',
'notification_days_ids',
]
def update(self, instance, validated_data):
if 'notification_days' in validated_data: # to handle PATCH request
instance.notification_days.set(validated_data['notification_days'])
return instance
First clear the old notification days
instance.notification_days.clear()
and add the new ones from put request
instance.notification_days.add()
It helped me
I'm trying to format data when querying my API. I can retrieve my data like that :
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
But I want something like that:
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
},
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
I use a serializer like this:
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]
cat1 and cat2 are dynamics, they are from another table. I don't understand how to create my arrays properly using those serializers. The category is a #Property field in my model who's a foreign key of job.
My models:
class MyModel(models.Model):
CHOICES = [(i, i) for i in range(4)]
partner = models.ForeignKey(Partner, on_delete=models.CASCADE)
job = models.ForeignKey(
Job, on_delete=models.CASCADE)
position = models.IntegerField(choices=CHOICES)
#property
def category(self):
return self.job.category.domain
def __str__(self):
return '%s | %s | %s | position: %s' % (self.partner.name, self.domain.name, self.job.name, self.position)
class Job(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
code = models.CharField(
max_length=255, unique=True)
name = models.CharField(
max_length=255)
class Category(models.Model):
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
code = models.CharField(
max_length=5)
name = models.CharField(max_length=255)
hourly_rate = models.FloatField(
null=True, blank=True)
How should I deal with serializers to format my data properly ?
EDIT:
I ended with something like that, except for the ListSerializer.
I used 2 ModelSerilizers
class MyModelCustomSerializer(serializers.ModelSerializer):
position = serializers.IntegerField(read_only=True)
job = serializers.CharField(source='job.name', read_only=True)
class Meta:
model = MyModel
fields = ['job', 'position']
def to_representation(self, value):
return {"position": value.position,
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
}
And
class CategoryCustomSerializer(serializers.ModelSerializer):
models = MyModelustomerSerializer(many=True)
class Meta:
model = Category
fields = ['category', 'MyModel']
def to_representation(self, value):
filters = {'job__category__domain__name': value.name}
myModels = MyModel.objects.filter(**filters)
serializer = MyModelCustomSerializer(instance=myModels, many=True,)
return {value.name: serializer.data}
But if I try to use a jobSerializer who already exist instead of
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
},
I got this error: Object of type 'Job' is not JSON serializable, but it's working anyway because i don't need all fields
I would go the direction of implementing a custom ListSerializer for the ModelSerializer and overriding its to_representation method.
from rest_framework import serializers
from collections import OrderedDict
class CustomListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
list_rep = OrderedDict()
for item in iterable:
child_rep = self.child.to_representation(item)
k, v = list(child_rep.items()).pop()
list_rep.setdefault(k, []).append(v)
return [
{k: v}
for k, v in list_rep.items()
]
Then set the model Meta to use it
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
list_serializer_class = CustomListSerializer
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]