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.