Django: annotate(sum(case(when()))) - django
In my project, I'm trying to aggregate the data based on the status of a 'field'. The 'view' is expected to return a table like so:
SERVICE_CODE
SUCCESS
TECHNICAL_DECLINES
BUSINESS_DECLINES
Service-1
S11
S12
S13
Service-2
S21
S22
S23
where S11,S12... are the aggregates taken based on the value of the field 'STATUS' in the model given below:
models.py
from django.db import models
class Wfmain(models.Model):
ENTITY_NUM = models.IntegerField()
SRV_REF_NUM = models.CharField(max_length=30,primary_key=True)
ITER_SL = models.IntegerField()
STAGE_ID = models.CharField(max_length=30)
ACTION = models.CharField(max_length=30)
STATUS = models.CharField(max_length=30)
REQUEST_DATE = models.DateField()
SERVICE_CODE = models.CharField(max_length=30)
SERVICE_TYPE_CODE = models.CharField(max_length=30)
FUNCTION_CODE = models.CharField(max_length=30)
REQUEST_START_TIME = models.DateField()
class Meta:
db_table = "WFMAIN"
unique_together = (("ENTITY_NUM", "SRV_REF_NUM"),)
The aggregates ought to be grouped by the field 'SERVICE_CODE'
views.py
from django.db.models import When, Case, Sum, IntegerField
from django.shortcuts import render,redirect
from hr.models import *
def financial(request):
S=['0','00','000']
NT=['0','00','000','099','100','101','102','103','104','105','107','108','109','110','111','113','114','116','117','118','119','120','121','122',
'123','124','180','181','182','183','184','185','186','187','188','189','200','201','205','213','217','218','219','220','221','222','223','224',
'230','231','232','233','234','235','236','237','238','239','240','241','248','249','250','256','258','260','262','263','264','265']
B=['S','099','100','101','102','103','104','105','107','108','109','110','111','113','114','116','117','118','119','120','121','122','123','124',
'180','181','182','183','184','185','186','187','188','189','200','201','205','213','217','218','219','220','221','222','223','224','230','231',
'232','233','234','235','236','237','238','239','240','241','248','249','250','256','258','260','262','263','264','265']
wf=Wfmain.objects.using('MBDB')
fin = wf.values('SERVICE_CODE').annotate(
Success=Sum(Case(When(STATUS in S, then=1),
when(STATUS not in S, then=0),
output_field=IntegerField())),
Technical_declines=Sum(Case(When(STATUS not in NT, then=1),
When(STATUS in NT, then=0),
output_field=IntegerField())),
Business_declines=Sum(Case(When(STATUS in B, then=1),
When(STATUS not in B, then=0),
output_field=IntegerField()))).order_by('SERVICE_CODE')
I'm stuck with error: "name 'STATUS' is not defined"
Output #browser:
Please tell me where I went wrong.
You need to translate the Case(When(…)) to a query named parameters. You thus define this with:
from django.db.models import Case, Value, When
fin = wf.values('SERVICE_CODE').annotate(
Success=Sum(Case(
When(!STATUS__in=S, then=Value(1)),
default=Value(0),
output_field=IntegerField()
)),
Technical_declines=Sum(Case(
When(STATUS__in=NT, then=Value(0)),
default=Value(1),
output_field=IntegerField()
)),
Business_declines=Sum(Case(
When(STATUS__in=B, then=Value(1)),
default=Value(0)
output_field=IntegerField()
))
).order_by('SERVICE_CODE')
Related
Make order by function inside model class in Django
models: class Product(models.Model): product_model = models.CharField(max_length=255) price = models.DecimalField(default=0 , decimal_places=0 , max_digits=8) discount_price = models.DecimalField(blank=True, null=True, decimal_places=0, max_digits=8) def product_all_price(self): if self.price > 0: a = sum([stock.quantity for stock in self.product.all()]) if a == 0: return "0" if a >= 1: if self.discount_price: discount_price = str(self.discount_price) price = str(self.price) return 0 ,discount_price, price else: return str(self.price) else: return "0" my views: def products_list(request): products_list = Product.objects.filter(product_draft=False) products_list = products_list.order_by('-created_on') ... How can i order this list by product_all_price in models. show returned "price", above and all returned "0", below
You can order with: from django.db.models import BooleanField, ExpressionWrapper, Q Product.objects.filter( product_draft=False ).alias( price_non_zero=ExpressionWrapper( Q(price__gt=0), output_field=BooleanField() ) ).order_by('-price_non_zero', '-created_on') Or in before django-3.2: from django.db.models import BooleanField, ExpressionWrapper, Q Product.objects.filter( product_draft=False ).annotate( price_non_zero=ExpressionWrapper( Q(price__gt=0), output_field=BooleanField() ) ).order_by('-price_non_zero', '-created_on') This will thus list the Products with a price greater than 0 first (sorted by created_on in decending order); and then the items with price 0 (again sorted by created_on in descending order).
How to auto populate data from other model and how to add calculated fields?
I am learning django and I have not been able to properly do two things within model clearance: Within modelRetrieve the name fields that correspond to the imo number selected. Autopopulate a date field with the current day plus 7 days. Any ideas what I am doing wrong? Here is my code: from django.db import models from django.core.exceptions import ValidationError from django.utils import timezone from datetime import timedelta, datetime def imo_validator(value): if value < 0 or value > 9999999: raise ValidationError( 'This is not a valid IMO number', params={'value':value}, ) class ship(models.Model): imo = models.IntegerField(unique=True,validators=[imo_validator]) name = models.CharField(max_length=20) rpm = models.FloatField() power = models.FloatField() main_engine = models.IntegerField() class Meta: ordering = ['imo'] def __str__(self): return "{}, (IMO:{})".format(self.name, self.imo) class clearance(models.Model): STATUSES = [ ('PENDING','PENDING'), ('REJECTED','REJECTED'), ('APPROVED','APPROVED'), ] PORTS = [ ('PACAN','PACAN'), ('PABLB','PABLB'), ('PACCT','PACCT'), ('PAANP','PAANP'), ('PAANA','PAANA'), ] date_of_request = models.DateField(default=timezone.now,blank=False,editable=True) imo = models.ForeignKey(ship, on_delete=models.PROTECT) port = models.CharField(max_length=20,null=True,choices=PORTS) eta = models.DateField(null=False) name = ship.name.get(imo=imo) calculated_eta = models.DateField(datetime.today + timedelta(days=1)) aduanas = models.FileField(blank=True) aduanas_ok = models.CharField(max_length=15,default='PENDING',choices=STATUSES,editable=False) minsa = models.FileField(blank=True) minsa_ok = models.CharField(max_length=15,default='PENDING',choices=STATUSES,editable=False) def __str__(self): return "{}, ETA:{}".format(self.imo, self.eta) class Meta: ordering = ['eta']
To add a default to a DateField that is 7 days in the future you need to create a function that returns the date 7 days in the future and then pass that to the "default" parameter of the field def seven_days_from_now(): return datetime.date.today() + datetime.timedelta(days=7) class clearance(models.Model): ... calculated_eta = models.DateField(default=seven_days_from_now) ... Your "name" field should be a property that returns the name of the associated "imo" class clearance(models.Model): ... #property def name(self): return self.imo.name ...
Django Query how to sum returned objects method
I have a model: class TimeStamp(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) t_in = models.DateTimeField(_("In"), auto_now=False, auto_now_add=False) t_out = models.DateTimeField( _("Out"), auto_now=False, auto_now_add=False, blank=True, null=True) class Meta: verbose_name = _("TimeStamp") verbose_name_plural = _("TimeStamps") def __str__(self): return str(f'{self.t_in.date()} {self.user.get_full_name()}') def get_absolute_url(self): return reverse("TimeStamp_detail", kwargs={"pk": self.pk}) def get_total_hrs(self): if self.t_out: t_hrs = abs(self.t_out-self.t_in).total_seconds()/3600 else: t_hrs = '0' return str(t_hrs) then in my views.py def timeclock_view(request): week_start = timezone.now().date() week_start -= timedelta(days=(week_start.weekday()+1) % 7) week_end = week_start + timedelta(days=7) obj = request.user.timestamp_set.filter( t_in__gte=week_start, t_in__lt=week_end) obj.aggregate(total_hrs=Sum('get_total_hrs')) if obj: last = obj.last() context = {'obj': obj, 'last': last, 'week_start': week_start, 'week_end': week_end, 'week_total': total_hrs, } else: context = {} return render(request, 'timeclock/timeclock_view.html', context) How do i write this to get a sum of the hrs for the queryset? obj.aggregate(total_hrs=Sum('get_total_hrs)) is giving me an error: django.core.exceptions.FieldError: Cannot resolve keyword 'get_total_hrs' into field. Choices are: description, id, note, note_set, pw, t_in, t_out, user, user_id
Aggregation is done on the database. That means you can use only fields in the aggregation, and not model functions or properties. I would first implement the logic of get_total_hrs with an annotate() queryset, then use the this queryset to aggregate over the calculated field. from django.db.models.functions import Abs from django.db.models import F, ExpressionWrapper, DurationField, Sum queryset.annotate( total_hrs=ExpressionWrapper( Abs(F("t_out") - F("t_in")), output_field=DurationField() ), ).aggregate(overall_hrs=Sum("total_hrs"))
Django add aggregate operation to a query result
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 })
django-Cannot assign "u'Joan Manel'": "despesa.nomTreballador" must be a "treballador" instance
I have been working on a project in which I have to point out the expenses that the workers of a company have. For this I have created two models, workers and expenses, in which expenses has a foreign key to workers, in the field: "nomTreballador". When I try to save it in the db I get the error: "Cannot assign "u'Joan Manel'": "despesa.nomTreballador" must be a "treballador" instance." My models.py: from __future__ import unicode_literals from django.db import models from django.core.validators import RegexValidator KILOMETRATGE = 'KM' DINAR = 'DIN' AUTOPISTA = 'AP' MANTENIMENTPC = 'PC' GASTOS = ( (KILOMETRATGE, 'Kilometres'), (DINAR, 'Dinar'), (AUTOPISTA, 'Autopista peatge'), (MANTENIMENTPC, 'Manteniment de pc') ) NIF = 'NIF' NIE = 'NIE' DNI = 'DNI' TIPUSDOC = ( (DNI, 'DNI'), (NIF, 'NIF'), (NIE, 'NIE') ) class treballador(models.Model): nom = models.CharField(max_length=150, null=False, unique=True) cognom = models.CharField(max_length=150, null=False) tipusDocID = models.CharField(max_length=3, choices=TIPUSDOC, null=False) docId = models.CharField(max_length=9, null=False) tlf_regex = RegexValidator(regex=r'^\d{9,9}$',message="Phone number must be entered in the format: '+999999999'. Up to 9 digits allowed.") tlf = models.CharField(validators=[tlf_regex], blank=True, max_length=9) # validators should be a list correu = models.EmailField(max_length=254) ciutat = models.CharField(max_length=150) dataDAlta = models.DateTimeField(auto_now_add=True) def __unicode__(self): return unicode(self.nom) or 'u' class despesa(models.Model): nomTreballador = models.ForeignKey(treballador, to_field='nom') tipusDeGast = models.CharField(max_length=3, choices=GASTOS) quantia = models.DecimalField(max_digits=5, decimal_places=2) data = models.DateTimeField() def __unicode__(self): return unicode(self.nomTreballador) or 'u' My forms.py: from django import forms from functools import partial from .models import despesa, treballador DateInput = partial(forms.DateInput, {'class':'datepicker'}) class desModelForm(forms.ModelForm): data = forms.DateField(widget=DateInput(format='%d/%m/%Y'), label="Data de la despesa", input_formats=['%d/%m/%Y']) iquery = treballador.objects.values_list('nom', flat=True).distinct() iquery_choices = [('','None')] + [(treballador,treballador) for treballador in iquery] nomTreballador = forms.ChoiceField(choices=iquery_choices) class Meta: model= despesa fields= ["nomTreballador","tipusDeGast","quantia","data"] def clean_despesa(self): despeses = self.cleaned_data.get("tipusDeGast") return despeses def clean_date(self): date = self.cleaned_data.get("data") return date def clean_quantia(self): quantia = self.cleaned_data.get("quantia") return quantia def clean_nom(self): nomTreballador = self.cleaned_data.get("nomTreballador") return nomTreballador My views.py: from django.shortcuts import render from .forms import desModelForm, treballadorForm from .models import treballador, despesa def home(request): form = desModelForm(request.POST or None) context = { "gast_form": form } if form.is_valid(): desp = form.save(commit=False) desp.save() return render(request, "imputacioDespeses.html", context) I've tried solutions of similar questions but I have not managed to solve it Thank you!!
You are getting this error because you are passing a text string to be used as the nomTreballador foreign key, while you should be passing a treballador instance. It looks like you're trying to restrict the available choices to a set of distinct trebelladors by using a forms.ChoiceField, but a better way to do this with a ModelForm is to change the queryset attribute of the nomTreballador field. You do this in the form's init method: self.fields['nomTreballador'].queryset = treballador.objects.all().distinct() Also you should check the clean methods you've implemented because not all of them map to an existing field.