Django add aggregate operation to a query result - django

I'm trying to do an aggregate operation between two tables using Django, my models are:
class Cusinetype(models.Model):
hometype_en = models.TextField()
active = models.BooleanField()
hometype_es = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'cusinetype'
class Foodpreferences(models.Model):
id_client = models.ForeignKey(Client, models.DO_NOTHING, db_column='id_client')
id_cusinetype = models.ForeignKey(Cusinetype, models.DO_NOTHING, db_column='id_cusinetype')
created_at = models.DateTimeField()
class Meta:
managed = False
db_table = 'foodpreferences'
The query that I'm trying to build is:
SELECT
ct.id,
ct.hometype_en,
ct.hometype_es
,
((SELECT COUNT(*)
FROM foodpreferences fp
WHERE fp.id_cusinetype = ct.id AND fp.id_client = 3 ) > 0 ) selected
FROM
Cusinetype ct
I'm trying to generate a model, to store the information of those tables in a single one query, but anything works.
Someone has an idea about how to do it?

serializers.py
class PreferencesSerializer(serializers.ModelSerializer):
selected = serializers.IntegerField()
class Meta:
model = Cusinetype
fields = ('id', 'trucktype_en', 'trucktype_es', 'selected')
views.py
qs = Cusinetype.objects.filter().filter(active = True)
qs = qs.annotate(
selected=Sum(Case(
When(foodpreferences__id_client=3, then=1),
output_field=IntegerField()
))
)
serializers = PreferencesSerializer(qs, many = True)
return Response({ "result": serializers.data })

Related

How to filter results and manytomany childs?

I am making a web page that display a "Kanban".
I have columns as well as cards.
I want to filter the results according to a field (priority) in the cards (Sale).
My models:
class PipelineColumn(models.Model):
name = models.CharField(_("Name"), max_length=80)
order = models.PositiveIntegerField(_("Order of column"), unique=True)
default_probability = models.PositiveSmallIntegerField(_("probability"))
class Meta:
verbose_name = _("PipelineColumn")
verbose_name_plural = _("PipelineColumns")
ordering = ["order"]
def __str__(self):
return self.name
class Sale(models.Model):
name = models.CharField(_("name"), max_length=255)
expected_income = models.FloatField(_("expected income"))
probability = models.PositiveSmallIntegerField(_("probability"))
contact = models.ForeignKey(
"crm.Person",
verbose_name=_("person"),
related_name="sale_person",
on_delete=models.PROTECT,
)
date = models.DateField(_("date"), auto_now_add=True)
scheduled_closing_date = models.DateField(_("scheduled closing date"))
priority = models.PositiveSmallIntegerField(_("priority"), default=0)
column = models.ForeignKey(
"crm.PipelineColumn",
verbose_name=_("column"),
related_name="sale_column",
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("Sale")
verbose_name_plural = _("Sales")
ordering = ["scheduled_closing_date", "-priority"]
def __str__(self):
return self.name
For example, I would like to display only the columns and cards that have a priority of 2. If I try with PipelineColumn.objects.filter(sale_column__priority=2) it filters the columns well, only the columns with a sale with a priority equal to 2 are displayed. On the other hand, in the displayed columns all the sales are displayed even those whose priority is not equal to 2.
I would also like to be able to filter the sales returned by my query and get only the one whose priority is equal to 2.
For the moment I use the django-url-filter module to do this filtering.
My viewset:
class PipelineColumnViewSet(viewsets.ModelViewSet):
"""
A simple ViewSet for listing or retrieving PipelineColumn.
"""
permission_classes = [permissions.IsAuthenticated]
serializer_class = PipelineColumnSerializer
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter,
]
search_fields = [
"sale_column__name",
"sale_column__contact__last_name",
"sale_column__contact__first_name",
]
filter_fields = ["is_done", "sale_column"]
ordering = ["order"]
def get_queryset(self):
total = PipelineColumn.objects.aggregate(
total=Sum("sale_column__expected_income")
)["total"]
return (
PipelineColumn.objects.all()
.annotate(total_ca=Sum("sale_column__expected_income"))
.annotate(percentage_ca=(Sum("sale_column__expected_income") * 100 / total))
.prefetch_related("sale_column")
)
How to filter the sales in addition to the columns?

KeyError: 'id' in django rest framework when trying to use update_or_create() method

I am trying to update the OrderItem model using update_or_create() method. OrderItem model is related to the Order model with many to one relationship ie with a Foreignkey.
I am trying to query the orderitem object using id and update the related fields using default as you can see, but got this error.
My models:
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
ordered_date = models.DateTimeField(auto_now_add=True)
ordered = models.BooleanField(default=False)
total_price = models.CharField(max_length=50,blank=True,null=True)
#billing_details = models.OneToOneField('BillingDetails',on_delete=models.CASCADE,null=True,blank=True,related_name="order")
def __str__(self):
return self.user.email
class Meta:
verbose_name_plural = "Orders"
ordering = ('-id',)
class OrderItem(models.Model):
#user = models.ForeignKey(User,on_delete=models.CASCADE, blank=True)
order = models.ForeignKey(Order,on_delete=models.CASCADE, blank=True,null=True,related_name='order_items')
item = models.ForeignKey(Product, on_delete=models.CASCADE,blank=True, null=True)
order_variants = models.ForeignKey(Variants,on_delete=models.CASCADE,blank=True,null=True)
quantity = models.IntegerField(default=1)
ORDER_STATUS = (
('To_Ship', 'To Ship',),
('Shipped', 'Shipped',),
('Delivered', 'Delivered',),
('Cancelled', 'Cancelled',),
)
order_item_status = models.CharField(max_length=50,choices=ORDER_STATUS,default='To_Ship')
My view:
class UpdateOrderView(UpdateAPIView):
permission_classes = [AllowAny]
queryset = Order.objects.all()
serializer_class = OrderUpdateSerializer
My serializers:
class OrderUpdateSerializer(serializers.ModelSerializer):
order_items = OrderItemUpdateSerializer(many=True)
billing_details = BillingDetailsSerializer()
class Meta:
model = Order
fields = ['id','ordered','order_status','order_items','billing_details']
def update(self, instance, validated_data):
instance.order_status = validated_data.get('order_status')
instance.ordered = validated_data.get('ordered')
#billing_details_logic
billing_details_data = validated_data.pop('billing_details',None)
if billing_details_data is not None:
instance.billing_details.address = billing_details_data['address']
instance.billing_details.save()
#order_items_logic
instance.save()
order_items_data = validated_data.pop('order_items')
# print(order_items_data)
#instance.order_items.clear()
for order_items_data in order_items_data:
oi, created = OrderItem.objects.update_or_create(
id= order_items_data['id'],
defaults={
'quantity' : order_items_data['quantity'],
'order_item_status': order_items_data['order_item_status']
}
)
super().update(instance,validated_data)
return oi
Updated serializer:
for order_item_data in order_items_data:
oi, created = instance.order_items.update_or_create(
id= order_item_data['id'],
defaults={
'quantity' : order_item_data['quantity'],
'order_item_status': order_item_data['order_item_status']
}
)
The order_items data are sent like this.
order_items_data is a list.
Then you iterate over it with the same variable name.
for order_items_data in order_items_data:
Just rename it to something like
for order_data in order_items_data:
and there will be an id in your order_data.
I wasn't getting the id on the OrderedDict also, so I've added a id = serializers.IntegerField(required=False) on the serializer and put that id into the update_or_create method:
for obj in data:
Model.objects.update_or_create(
pk=obj.get('id', None), ...
)

How to access column from another table in list_display?

I have these models:
class Orders(models.Model):
order_id = models.AutoField(primary_key=True)
user_id = models.IntegerField()
status = models.CharField(max_length=100)
created_at = models.CharField(max_length=100)
class Meta:
managed = False
db_table = 'orders'
class OrderItems(models.Model):
order_id = models.ForeignKey(Orders, models.DO_NOTHING)
quantity = models.IntegerField()
class Meta:
managed = False
db_table = 'order_items'
Now suppose I want list_display of the orders model to show the columns
order_id, user_id, status, created_at, quantity
I know I could do something like this:
class OrdersAdmin(admin.ModelAdmin):
list_display = ('id', 'user_id', 'status', 'created_at', 'get_quantity')
class Meta:
model = Orders
def get_quantity(self, obj):
result = XXX
return result.get('quantity')
get_quantity.short_description = 'Quantity'
admin.site.register(Order, OrderAdmin)
How can I access the information I need at XXX?
So you need to follow the reverse foreign key set for Orders (which btw should be named in singular).
So something like this:
orderitems = obj.orderitems_set.all()
sum = 0
for orderitem in orderitems:
sum = sum + orderitem.quantity
return sum
You can set related_name to rename the reverse set:
OrderItems(models.Model):
order_id = models.ForeignKey(Orders, models.DO_NOTHING, related_name="order_items")
and call it like order.order_items.all()
To order in OrdersAdmin:
def get_queryset(self,request):
qs = super(OrdersAdmin,self).get_queryset(request)
return qs.annotate(get_quantity=Sum('order_items__quantity'))
def get_quantity(self, obj):
return obj.order_items.all.aggregate(Sum('quantity'))
get_quantity.admin_order_field = 'get_quantity'

How to do a relationship using DRF to another table without foreignKey

I can not do relatioships between two tables without relationships.
My models are :
class exampleModel(models.Model):
quantity = models.IntegerField(blank=False, null=True)
comment = models.CharField(max_length=100 , blank=True, null=True)
class Meta:
db_table = "example"
class Logger(models.Model):
id_table = models.IntegerField()
table = models.CharField(max_length=20 , blank=True, null=True)
comment = models.CharField(max_length=100 , blank=True, null=True)
action = models.CharField(max_length=100 , blank=True, null=True)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "logger"
I already have filled logger Model, but , I have not be able made the exampleSerializer.
My serializers are:
class LoggerSerializer(serializers.ModelSerializer):
class Meta:
db_table = u'logger'
model = Logger
fields = '__all__'
class exampleSerializer(serializers.ModelSerializer):
last_log = LoggerSerializer(read_only=True)
class Meta:
db_table = 'example'
model = ExampleModel
fields = ( 'id' , 'last_log' , 'quantity')
in logger saves :
id_table : 'primary key of example',
table : 'example'
comment : 'custom comment',
action : "CRUD"
You can query last logger in view, then pass it to serializer in context.
Then do something like:
class exampleSerializer(serializers.ModelSerializer):
last_log = serializers.SerializerMethodField()
class Meta:
db_table = 'example'
model = ExampleModel
fields = ('id', 'last_log', 'quantity')
def get_last_log(self, obj):
last_log = obj.state(self.context['last_log'])
last_log_serializer = LoggerSerializer(last_log)
return last_log_serializer.data
You pass to context, with something like:
exampleSerializer(queryset, context ={'last_log': last_log_object})
Also You can do the query in exampleSerializer:
class exampleSerializer(serializers.ModelSerializer):
last_log = serializers.SerializerMethodField()
class Meta:
db_table = 'example'
model = ExampleModel
fields = ('id', 'last_log', 'quantity')
def get_last_log(self, obj):
logger_queryset =Logger.objects.filter(table = self.Meta.db_table, id_table = obj.id)
return LoggerSerializer(logger_queryset).data

DRF PUT request on unique model field

I have the following model:
class Movie(models.Model):
name = models.CharField(max_length=800, unique=True)
imdb_rating = models.IntegerField(null=True)
movie_choice = (
('Act', 'Action'),
...........
)
movie_type = models.CharField(max_length=3, choices=movie_choice)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Hiren(models.Model):
movie = models.ForeignKey(Movie)
watched_full = models.BooleanField(default=True)
rating = models.IntegerField()
source = models.CharField(max_length=500, null=True)
watched_at = models.DateField()
quality_choice = (
..................
)
video_quality = models.CharField(max_length=3, choices=quality_choice)
created_at = models.DateField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
and serializer:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class HirenSerializer(serializers.ModelSerializer):
movie = MovieSerializer()
class Meta:
model = Hiren
fields = ('movie', 'id', 'watched_full', 'rating', 'source', 'video_quality', 'watched_at')
def update(self, instance, validated_data):
instance.movie.name = validated_data.get('movie', {}).get('name')
instance.movie.imdb_rating = validated_data.get('movie', {}).get('imdb_rating')
instance.movie.movie_type = validated_data.get('movie', {}).get('movie_type')
instance.watched_full = validated_data.get('watched_full', instance.watched_full)
instance.rating = validated_data.get('rating', instance.rating)
instance.source = validated_data.get('source', instance.source)
instance.video_quality = validated_data.get('video_quality', instance.video_quality)
instance.watched_at = validated_data.get('watched_at', instance.watched_at)
instance.movie.save()
instance.save()
return instance
When I try to send a put request without changing name field from Movie model it throws an error
{
"movie": {
"name": [
"movie with this name already exists."
]
}
}
However, I can perfectly update any other field if I change the name field's value each time.
The problem is in Movie model defined by you.
When you set the name field of Movie model as unique = True,then any new entry with same movie name will always throw an error.
In your model,
class Movie(models.Model):
name = models.CharField(max_length=800, unique=True)
imdb_rating = models.IntegerField(null=True)
movie_choice = (
('Act', 'Action'),
...........
)
movie_type = models.CharField(max_length=3, choices=movie_choice)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
If you want to add two entries with the same name,remove the line unique = True or make sure to save every entry with a different name.
Or,if you want to update the record/entry then you don't need to assign a value for name field,just remove that line from your code,alternatively check if the name of the movie is already same with an improvement in the code like this :
class HirenSerializer(serializers.ModelSerializer):
movie = MovieSerializer()
class Meta:
model = Hiren
fields = ('movie', 'id', 'watched_full', 'rating', 'source', 'video_quality', 'watched_at')
def update(self, instance, validated_data):
movie_name = validated_data.get('movie', {}).get('name')
if movie_name != instance.movie.name :
instance.movie.name = movie_name
instance.movie.imdb_rating = validated_data.get('movie', {}).get('imdb_rating')
instance.movie.movie_type = validated_data.get('movie', {}).get('movie_type')
instance.watched_full = validated_data.get('watched_full', instance.watched_full)
instance.rating = validated_data.get('rating', instance.rating)
instance.source = validated_data.get('source', instance.source)
instance.video_quality = validated_data.get('video_quality', instance.video_quality)
instance.watched_at = validated_data.get('watched_at', instance.watched_at)
instance.save()
return instance
Hope this helps,Thanks.