How to order by values in ArrayList in Django? - django

I have a model lets say products, which has a field prices that is basically an array:
prices = [price on Monday, price on Tuesday, price on Thursday]
How do I order products by prices on a specific day, eg: price on Monday?

I assume your prices field is an ArrayField of DecimalField.
You can access items like this prices__0, prices__1, ... (See index transforms)
For instance if you want to order by prices according to today's week day:
from datetime import date
weekday = date.today().weekday()
results = Products.objects.all().order_by('prices__{}'.format(weekday))

You can have another model:
class WeekPrice(...):
#The day field can be a choice field
day = models.CharField(....)
amount = models.DecimalField(..)
class Product(...):
weekly_prices = models.ManyToManyField(..)

Related

query to django model to find best company sale in the month

I have two django model one "company" and the other is "MonthlyReport" of the company
I want to find out which company sale in current month had more than 20% of previous month sale
class Company(models.Model):
name = models.CharField(max_length=50)
class MonthlyReport(models.Model):
company = models.ForeignKey(Company,on_delete=models.CASCADE)
sale = models.IntegerField()
date = models.DateField()
How can i figure out this issue to find a company that has more than 20% sales over the previous month
You can certainly do it using the ORM. You will need to combine Max (or SUM depending on your use case) with a Q() expression filter and annotate the percentage increase to the queryset before filtering it.
You could do it in a single piece of code, but I have split it out because getting the dates and the query expressions are quite long. I have also put the increase value in a separate variable, rather than hardcoding it.
from datetime import datetime, timedelta
from django.db.models import Max, Q
SALES_INCREASE = 1.2
# Get the start dates of this month and last month
this_month = datetime.now().date().replace(day=1)
last_month = (this_month - timedelta(days=15)).replace(day=1)
# Get the maximum sale this month
amount_this_month = Max('monthlyreport__sale',
filter=Q(monthlyreport__date__gte=this_month))
# Get the maximum sale last month, but before this month
amount_last_month = Max('monthlyreport__sale',
filter=Q(monthlyreport__date__gte=last_month) & \
Q(monthlyreport__date__lt=this_month))
Company.objects.annotate(
percentage_increase=amount_this_month/amount_last_month
).filter(percentage_increase__gte=SALES_INCREASE)
Edit - removed incorrect code addition
There is probably a way to do this using ORM, but I would just go with python way:
First add related name to MonthlyReport
class Company(models.Model):
name = models.CharField(max_length=50)
class MonthlyReport(models.Model):
company = models.ForeignKey(Company, related_name="monthly_reports", on_delete=models.CASCADE)
sale = models.IntegerField()
date = models.DateField()
Then
best_companies = []
companies = Company.objects.all()
for company in companies:
two_last_monthly_reports = company.monthly_reports.order_by("date")[:2]
previous_report = two_last_monthly_reports[0]
current_report = two_last_monthly_reports[1]
if current_report.sale / previous_report.sale > 1.2:
best_companies.append(company)

Django ORM: get the monthly average of price for each category

Imagine this simple model:
class Expense(models.Model):
price = models.DecimalField(decimal_places=2, max_digits=6)
description = models.CharField(max_length=300)
category = models.CharField(choices=ExpenseCategory.choices, max_length=20)
created_at = models.DateField()
I'm trying to get the monthly average of price for each category in the current year. My general thought was to do something like:
sub = (
Expense.objects.filter(created_at__year=date.today().year)
.annotate(month=TruncMonth("created_at"))
.values("month", "category")
.annotate(total=Sum("price"))
.order_by("month")
)
qs = Expense.objects.values("category").annotate(avg=Avg(Subquery(sub.values("total"))))
I'm basically trying to:
Truncate the months of created_at
Group by category and month
Sum the prices
Aggregate the prices for each category
It works just fine if I do like:
for category in categories:
sub.filter(category=category).aggregate(avg=Avg("total"))
Your query can be more simple than you think. Your current attempt at the solution is:
Truncate created_at to get the month
Group by category and month
Sum prices
Take Avg of the sum for each category
The problem with this is taking an aggregate of an aggregate. Let's think of your problem in reverse (We will do a bit of mathematics here). You want the average of the monthly price of a category, if we consider only one category and the monthly prices to be an array M[12], then we can express this as:
(M[0] + M[1] + ... + M[11]) / 12
Each of the values in M can be considered to be a summation of prices where the month matches. If we consider P[12][] to be a 2 dimensional array containing prices for each month we can rewrite above formula as:
(Sum(P[0]) + Sum(P[1] + ... + Sum(P[12])) / 12
Thinking of this further it is simply the sum of all prices in the year divided by 12! That means your query can simply be written as:
from django.db.models import ExpressionWrapper, FloatField, Sum, Value
qs = Expense.objects.filter(
created_at__year=date.today().year
).values("category").annotate(
avg=ExpressionWrapper(
Sum("price") / Value(12), output_field=FloatField()
)
)
Note: Dividing by 12 means we are assuming that we have data for the entire year, this would probably not be true for the current year so instead we should divide by the appropriate number of months. We might also want to filter upto the previous month in case we are not significantly into the current month.
A function within a model manager could be a good solution to this. In your models.py add a models.Manager class called ExpenseManager before your Expense class:
class ExpenseManager(models.Manager):
def summarise_categories(self):
return (
super(ExpenseManager, self)
.get_queryset()
.filter(created_at__year=date.today().year)
.annotate(month=TruncMonth("created_at"))
.values("month", "category")
.annotate(total=Sum("price")/Count("created_at__month", distinct=True))
)
then in you Expense class do:
class Expense(models.Model):
...
objects = ExpenseManager()
To get the resulting queryset aggregation you can simply call the model manager method on the model objects within a view:
summarised_categories = Expense.objects.summarise_categories()
This approach pushes all the work to the database returning a dictionary of categories and their average monthly prices for the year-to-date.

Group by weeks, months, days

I would like to group querysets by a date interval according to a datetime attribute of the model. I want to show average values from the model on a day-to-day, week-by-week and month-by-month basis.
E.g.
Week commencing 01/01/2017 - average distance: 30
Week commencing 08/01/2017 - average distance: 40
...
Can this be achieved with the standard Django queryset API?
Assuming the following model which might match your description
class Activity(models.Model):
timestamp = models.DateTimeField(auto_now_add=True, blank=True)
distance = models.IntegerField()
You can achieve a week by week statistic with the following query
from django.db.models.functions import ExtractWeek, ExtractYear
from django.db.models import Sum, Count
stats = (Activity.objects
.annotate(year=ExtractYear('timestamp'))
.annotate(week=ExtractWeek('timestamp'))
.values('year', 'week')
.annotate(avg_distance=Avg('distance'))
)
Sample output
<QuerySet [{'year': 2018, 'week': 31, 'distance': 3.2}]>
To recover the first day of week, check Get date from week number
In particular:
for record in stats:
week = "{year}-W{week}-1".format(year=record['year'], week=record['week'])
timestamp = datetime.datetime.strptime(week, "%Y-W%W-%w")
There are a bunch of field lookups specifically for date/datetime fields: week, day, month (should be combined with year) etc.

Django date effective retrieval of ORM records

If I have a Django Employee model with a start_date and end_date date field, how can I use get in the ORM to date effectively select the correct record if different versions of the record exist over time based on these date fields?
So I could have the following records:
start_date, end_date, emp
01/01/2013, 31/01/2013, Emp1
01/02/2013, 28/02/2013, Employee1
01/03/2013, 31/12/4000. EmpOne
And if today's date is 10/02/2013 then I would want Employee1.
Something similar to:
from django.utils import timezone
current_year = timezone.now().year
Employee.objects.get(end_date__year=current_year)
or
res = Employee.objects.filter(end_date__gt=datetime.now()).order_by('-start_date')
Or is there a more efficient way of doing the same?
Your second example looks fine. I corrected the filter parameters to match your start_date constraints. Also, i added a LIMIT 1 ([:1]) for better performance:
now = datetime.now()
employees = Employee.objects.filter(start_date__lt=now, end_date__gt=now).order_by('-start_date')
employee = employees[:1][0] if employees else None

Django count by group

I need to create a report in which I can get the value for each month of the year.
models.py
class Ask(models.Model):
team = models.CharField(max_length=255)
date = models.DateField()
In team are only three possible values: A, B, C.
How do I calculate how many times a month was added individual team?
I would like to generate a report for each year.
I suggest you to add month field to your model and pass there month on save. This is help you to run this:
from django.db.models import Count
Ask.objects.filter(date__year='2013').values('month', 'team').annotate(total=Count('team'))
Other method is to use extra parameter and extract month from date field:
from django.db.models import Count
Ask.objects.filter(date__year='2013').extra(select={'month': "EXTRACT(month FROM date)"}).values('month', 'team').annotate(Count('team'))
but EXTRACT method is database dependent and for example dont`t work in SQLite