I written this query in PostgreSQL and I'm confused of conversion of this query to django orm
SELECT count(*),
concat(date_part('month', issue_date), '/', date_part('year', issue_date) ) as date
FROM affiliates_issuelog
WHERE tenant_id = '{tenant_id}'
GROUP BY date_part('month', issue_date),
date_part('year', issue_date)
ORDER BY date_part('year', issue_date) desc,
date_part('month', issue_date) desc
I have this model that records the insertion of new affiliates by date and by institution (tenant), only I need to receive from the query the total amount of records inserted per month in the year, and I was using the listview to make my pages until then but I don't know how to filter this data using orm.
class IssueLog():
tenant = models.ForeignKey("tenants.Federation", on_delete=models.CASCADE)
issue_date = models.DateField(
default=date.today, verbose_name=_("date of issue")
)
class Meta:
verbose_name = _("Entrada de emissão")
verbose_name_plural = _("Entradas de emissão")
def __str__(self):
return f"{self.institution}, {self.tenant}"
My pages that return a list of data I did as the example below, is it possible to pass the data as I want through get_queryset()?, I already managed to solve my problem using the raw query, but the project is being done only with orm so I wanted to keep that pattern for the sake of the team. Ex:
class AffiliateExpiredListView(HasRoleMixin, AffiliateFilterMxin, ListView):
allowed_roles = "federation"
model = Affiliate
ordering = "-created_at"
template_name_suffix = "issued_list_expired"
paginate_by = 20
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["renew_form"] = AffiliateRenewForm()
tenant_t = self.request.user.tenant
context["cancel_presets"] = tenant_t.cancelationreason_set.all()
return context
def get_queryset(self):
return super().get_queryset().filter(is_expired=True).order_by('full_name')
You can query with:
from django.db.models import Count
from django.db.models.functions import ExtractMonth, ExtractYear
IssueLog.objects.values(
year=ExtractYear('issue_date'),
month=ExtractMonth('issue_date')
).annotate(
total=Count('pk')
).order_by('-year', '-month')
This will make a queryset with dictionaries that look like:
<QuerySet [
{'year': 2022, 'month': 2, 'total': 14},
{'year': 2022, 'month': 1, 'total': 25},
{'year': 2021, 'month': 12, 'total': 13}
]>
I would not do string formatting in the database query, but just do this in the template, etc.
But the model can not be abstract = True [Django-doc]: that means that there is no table, and that it is only used for inheritance purposes to implement logic and reuse it somewhere else.
Related
Target is to sum and annotate workingtimes for each employee on a given time range.
models:
class Employee(models.Model):
first_name = models.CharField(max_length=64)
class WorkTime(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="work_times")
work_start = models.DateTimeField()
work_end = models.DateTimeField()
work_delta = models.IntegerField(default=0)
def save(self, *args, **kwargs):
self.work_delta = (self.work_end - self.work_start).seconds
super().save(*args, **kwargs)
getting work times for each employee at a given date range:
queryset = Employee.objects.prefetch_related(
Prefetch(
'work_times',
queryset=WorkTime.objects.filter(work_start__date__range=("2021-03-01", "2021-03-15"]))
.order_by("work_start"),
to_attr="filtered_work_times"
)).all()
trying to annotate sum of work_delta to each employee:
queryset.annotate(work_sum=Sum("filtered_work_times__work_delta"))
This causes a FieldError:
Cannot resolve keyword 'filtered_work_times' into field. Choices are: first_name, id, work_times
How would one proceed from here? Using Django 3.1 btw.
You should use filtering on annotations.
I haven't tried, but I think the following code might help you:
from django.db.models import Sum, Q
Employee.objects.annotate(
work_sum=Sum(
'work_times__work_delta',
filter=Q(work_times__work_start__date__range=["2021-03-01", "2021-03-15"])
)
)
You cannot use the prefetch_related values in the query because simply the prefetching is done separately, Django would first fetch the current objects and then make queries to fetch the related objects so the field you try to refer is not even part of the query you want to add it to.
Instead of doing this simply add a filter [Django docs] keyword argument to your aggregation function:
from django.db.models import Q
start_date = datetime.date(2021, 3, 1)
end_date = datetime.date(2021, 3, 15)
result = queryset.annotate(work_sum=Sum("work_times__work_delta", filter=Q(work_times__work_start__date__range=(start_date, end_date))))
I would like get number of customers for each hour of the day.
This my model:
class Client(models.Model):
id_track=models.IntegerField(default=0)
temps_attente=models.CharField(max_length=50)
date_entree = models.DateTimeField(auto_now_add=True)
camera = models.ForeignKey("Camera", on_delete=models.CASCADE)
class Meta:
verbose_name = "Client"
ordering = ['camera']
def __str__(self):
return self.temps_attente
I want something like this :
[
{date:02/09/2020 , hour:"09" ,nbrCustumer:30}
{date:02/09/2020 , hour:"10" ,nbrCustumer:22 }
{date:02/09/2020 , hour:"11" ,nbrCustumer:10}
{date:02/09/2020 , hour:"12h ,nbrCustumer:12}
]
You can use GROUP BY expression as
from django.db.models import Count
from django.db.models.functions import ExtractHour
from django.utils import timezone
result = Client.objects.filter(
date_entree__date=timezone.now().date()
).annotate(
hour=ExtractHour('date_entree')
).values('hour').annotate(count=Count('hour'))
The result will be as,
[{'hour': 2, 'count': 1}, {'hour': 18, 'count': 1}]
References
date lookup
ExtractHour
Django Group By
Count()
I'm using Django 2.0 and Django REST Framework
I have a model like below.
class Contact(models.Model):
first_name = models.CharField(max_length=100)
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
#property
def total_payable(self):
return self.amount
#property
def amount_due(self):
returned_amount = 0
for returned in self.amountreturned_set.all():
returned_amount += returned.amount
return self.total_payable - returned_amount
class AmountReturned(models.Model):
amount_given = models.ForeignKey(AmountGiven, on_delete=models.CASCADE)
amount = models.FloadField()
I have to get the top 10 contacts of the amount given and due respectively.
In my view, I'm filtering data like
#api_view(http_method_names=['GET'])
def top_ten(request):
filter_type = request.query_params.get('type', None)
if filter_type == 'due':
# query for due type
elif filter_type == 'given':
qs = Contact.objects.filter(
user=request.user
).values('id').annotate(
amount_given=Sum('amountgiven__amount')
).order_by(
'-amount_given'
)[:10]
graph_data = []
for q in qs:
d = {}
contact = Contact.objects.get(pk=q['id'])
d['contact'] = contact.full_name if contact else 'Unknown'
d['value'] = q['amount_given']
graph_data.append(d)
return Response(graph_data)
else:
raise NotFound('No data found for given filter type')
the type query can be due or given.
The code for given type is working fine as all fields are in the database. But how can I filter based on the virtual field for due type?
What I have to do is to annotate Sum of amount_due property group by contact.
You cannot filter based on #property.
As far as I understand your problem correctly you can aggregate sum of related AmountGiven and sum of AmountReturned, then calculate due field which keep result of subtracting letter and former.
The query:
from django.db.models import Sum, Value
from django.db.models.functions import Coalesce
Contact.objects.filter(
amountgiven__amount__gt=0
).annotate(
due=Sum('amountgiven__amount') - Coalesce(Sum('amountgiven__amountreturned__amount'), Value(0))
).order_by('-due').values_list('due', 'id')
will return:
<QuerySet [{'id': 3, 'due': 2500.0}, {'id': 1, 'due': 2450.0}, {'id': 2, 'due': 1500.0}]>
However with this solution you cannot distinct between many AmountGiven across one Contact. You get big picture like results.
If you want split due value per AmountGiven instance the just annotate like so:
AmountGiven.objects.annotate(
due=Sum('amount') - Coalesce(Sum('amountreturned__amount'), Value(0))
).order_by('-due').values_list('due', 'contact__id', 'id')
which returns
<QuerySet [
{'contact__id': 3, 'id': 3, 'due': 2500.0},
{'contact__id': 1, 'id': 1, 'due': 1750.0},
{'contact__id': 2, 'id': 2, 'due': 1500.0},
{'contact__id': 1, 'id': 4, 'due': 350.0},
{'contact__id': 1, 'id': 5, 'due': 350.0}
]>
References
Coalesce
I have three models: Business, Offers and OfferPlan:
Business:
class Business(models.Model):
name_of_business = models.CharField(max_length=255)
Offers:
class Offers(models.Model):
business = models.ForeignKey(Business, related_name="business_offer",
on_delete=models.CASCADE)
title = models.CharField(max_length=255)
subtext = models.CharField(max_length=255)
OfferPlan:
class OfferPlan(models.Model):
WEEKDAYS = [
(1, _("Monday")),
(2, _("Tuesday")),
(3, _("Wednesday")),
(4, _("Thursday")),
(5, _("Friday")),
(6, _("Saturday")),
(7, _("Sunday")),
]
offer = models.ForeignKey(Offers, related_name="business_offer_plan",
on_delete=models.CASCADE)
weekday = models.IntegerField(
choices=WEEKDAYS,
)
from_hour = models.TimeField()
to_hour = models.TimeField()
I have a ListView which search for businesses open based on different params such as city, category etc. I also want to now search by weekday, say which business is open on Monday will be displayed and which are not wont be displayed on that day. Weekday information is stored in OfferPlan and there could be multiple timings for the offers that day in OfferPlan table, but I want to query (filter, exclude) the businesses who has even a single entry on that weekday number.
Here is my ListView:
class SearchListView(ListView):
template_name = 'search/search.html'
model = Business
def get_queryset(self):
# queryset = Business.objects.filter(business_address__city=AppLocations.objects.first().city)
if 'city' in self.request.GET:
queryset = Business.objects.filter(business_address__city=self.request.GET.get('city'))
if 'category' in self.request.GET:
queryset = queryset.filter(category__code=self.request.GET.get('category'))
# if 'date' not in self.request.GET:
# queryset = B
raise
return queryset
How could this be possible? Also looked into https://docs.djangoproject.com/en/1.8/ref/models/conditional-expressions/ but not able to figure out.
Thanks
Update 1
After researching more in the web, I figured out this is how it could be achieved, but need to know for sure from other Django enthusiasts here that it is right.
queryset.filter(business_offer__business_offer_plan__weekday=1).annotate(count_entry=Count('business_offer__business_offer_plan__weekday')).filter(count_entry__gt=1)
Solution
Jefferson's solution was tagged as right answer as it provided more insights, about which query is fast and what wrong was with my previous update, so here is the proper solution to which we both agreed:
queryset.filter(business_offer__business_offer_plan__weekday=1).annotate(count_entry=Count('business_offer__business_offer_plan__weekday')).filter(count_entry__gte=1)
def get_query(weekday):
businesses = Business.objects.filter(business_offer__in=Offers.objects.filter(
business_offer_plan__in=OfferPlan.objects.filter(weekday=weekday))).distinct()
return businesses
There's a heavy query, but it works.
There's no conditional expression here - and your annotation is much too complicated. You just need an additional filter.
queryset.filter(business_offer__business_offer_plan__weekday=self.request.GET['weekday'])
I'm using Django's generic year archive view to display event objects by year. This may or may not be the best way to do this since I found that Django restricts the object list to the year being passed; my date range spans the current year into the next.
Here's my view:
class VisitingScholarsYearView(YearArchiveView):
allow_empty = True
allow_future = True
date_field = 'event_date'
template_name = "events/dvs_test.html"
context_object_name = 'event_list'
make_object_list = True
def get_queryset(self):
return Event.school_year_events.from_year(self.get_year()).filter(event_type__title='Distinguished Visiting Scholars Series')
Here's the manager on my model (an Event object with a DateField called event_date):
class SchoolYearManager(models.Manager):
def live_events(self, start_date, end_date):
return self.filter(status=self.model.LIVE).filter(event_date__range=(start_date, end_date))
def this_year(self):
now = datetime.datetime.now()
current_year = now.year
start_date = datetime.date(current_year, 7, 1)
end_date = datetime.date((current_year + 1), 6, 30)
return self.live_events(start_date, end_date)
def from_year(self, year):
start_date = datetime.date(int(year), 7, 1)
end_date = datetime.date((int(year) + 1), 6, 30)
return self.live_events(start_date, end_date)
And finally, my url for the view:
url(r'^distinguished-visiting-scholars-series/(?P<year>\d{4})/$', VisitingScholarsYearView.as_view()),
When I hit the API, I get the events I expect. But the YearArchiveView appears to limit the returned events to the year I give it; this is also expected, but I'd like it to span the range I refer to in the manager (ie July 1 to June 30 ).
How can I change this behavior? Or should I attempt a different view (ListView)?
I don't think you should use YearArchiveView as the base here - there's too much built-in logic around getting the objects for that date.
Instead, use ListView with YearMixin:
class VisitingScholarsYearView(YearMixin, ListView):