complex aggregate in Django - django

I have the following models:
class Item(models.model):
price = models.floatField()
...
and :
class purchase(models.model):
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
date = models.DateField()
I would like to make a query which will provide top 10 users who purchased the most in the last week .I would also like to have the Sum of the purchased they completed in that week.
So the output I would like to show on my page is similar to this :
Top buyers of the week :
1. User_1 | 150 Points
2. User_2 | 130 Points
...
10. User_10 | 10 Points
Is it possible to use annotate for that ? or it should be break ot several queries ?

Well let's give it a go (still needs to be tested for optimized SQL):
from datetime import timedelta
from django.db.models import Sum
from django.contrib.auth.models import User
from django.utils import timezone
some_day_last_week = timezone.now().date() - timedelta(days=7)
rows = User.objects.filter(purchases__date__gte=some_day_last_week)\
.annotate(item_sum=Sum('purchases__item__price'))\
.order_by('item_sum')[:10]
print [(u.username, u.item_sum) for u in rows]

Related

How to create specific django-model field, contains a date and unique number per day?

I create a internet-shop on django and have the following problem.
How to create a django model-field for order`s number, contains a date and unique number per day?
This option is described in the assignment and others will not suit me.
UPD:
order number should look like this: yyyymmdd + unique order`s num per day. The next day, this num will start from the beginning.
As a result, I made like this. Maybe someone will come in handy.
from django.db import models
from django.utils.timezone import datetime
def get_num(date=datetime.now()):
date = date.strftime('%Y%m%d')
if Order.objects.all().last():
last_order = Order.objects.all().last()
if last_order.number[0:8] == date:
num = int(last_order.number[8::]) + 1
else:
num = 1
else:
return f'{date}1'
return(f'{date}{num}')
class Order(models.Model):
number = models.CharField(max_length=12, default=get_num, verbose_name='номер заказа')
...

Django: Filter data based on expiration date coming within next 1 week and 2 weeks

I have a model class:
class Products(models.Model):
product = models.Charfield( field specs here . )
expiration_date = modelsDateTimeField ( . field specs here . )
Now I want the users to be able to see all the products which will expire in 1 week or/and 1 month or/and 3 months from today.
I have already tried:
from django_filters import rest_framework as filters
expiration_date_after = filters.DateFilter(field_name='expiration_date', lookup_expr='gte')
expiration_date_before = filters.DateFilter(field_name='expiration_date', lookup_expr='lte')
with this, I have to pass two parameters in the url:
/products/?expiration_date_after=2019-06-06&expiration_date_before=2019-06-12
But I want to pass in the url only 1 or 2 or 3 which will display data for 1 week, 2 weeks and 3 weeks .
So if i pass products/1 . it should filters expiration date for next 1 week.
and if i pass products/2 . it should filter expiration date for next 1 month.
I am new to django.... So please let me know what is best approach to this problem.
You should write query like this:
products = Products.objects.filter(expiration_date__range=[start_date, end_date])
if You want on weekley basis than you should do like this:
days = 7*n
start_date = today()
end_date = start_date + datetime.timedelta(days=days)
where n=number of weeks
Note: Do not forget to change date string to datetime object
I implemented this in this manner:
In custom_filters folder:
from Django_filters import rest_framework as filters
from date time import date time, date, timedelta
import datetime
class DaysFromTodayFilter(filters.Filter):
def filter(self,qs,value):
if value not in (None,''):
days_key = int(value):
today = datetime.date.today()
if days_key ==1:
x_days_from_today = today + date time.timedelta(days=7)
elif days_key ==2:
x_days_from_today = today + date time.timedelta(days=14)
return qs.filter(**{'%s__%s'%(self.field_name, 'range'):(today, x_days_from_today)})
return qs
In views.py file:
from Django_filters import rest_framework as filters
from views import DaysFromTodayFilter
from models import Products
import Django_filters
class ProductsFilter(Django_filters.FilterSet):
expiration_date_within = DaysFromTodayFilter(field_name='expiration_date')
It's working fine....
in the url:
localhost:8000/products/1
is filter all products which will expire in 1 week and /2 for two weeks

Django ORM: Conditional filtering

In a Django app, I keep daily scores of users in such a model:
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
date = models.DateField(auto_now_add=True)
I want to find out the days when a user's score has changed drastically compared to a consequent day. That is, if for example, the user scores 10 times higher than the previous day.
How can I include such a condition in a query filter using Django ORM? Is it possible with a single query using conditional expressions as described here: https://docs.djangoproject.com/en/1.9/ref/models/conditional-expressions/
Thanks.
If you change your Score class slightly to include the previous day's score (which is admittedly pretty wasteful), you can pack the query into one line using F expressions.
Your new class:
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
lastscore = models.PositiveIntegerField(default=0)
date = models.DateField(auto_now_add=True)
Then the filter becomes:
from django.db.models import F
daily_chg = 10
big_changes = Score.objects.filter(score__gt=daily_chg*F('lastscore'))
Instead of using timedeltas to search for and set the previous day's score field, I'd look into establishing an ordering via a Meta class and calling latest() when saving the current day's score.
Using timedelta we can test for the last week's days for a given user as such:
from my_app.models import Score
import datetime
def some_view(request):
days_with_score_boost = []
today = datetime.date.today()
for each_day in xrange(0,7):
day_to_test, day_before_to_test = today - datetime.timedelta(days=each_day), today - datetime.timedelta(days=each_day + 1)
day_before_score = Score.objects.get(user=request.user,date=today - datetime.timedelta(days=each_day)).score # will need to catch the exception raised if .get return None and process appropriately
if Score.objects.filter(user=request.user,score__gte=days_before_score * 10,date=day_before_to_test).count() > 0:
days_with_score_boost.append(day_to_test)
days_with_score_boost will be a list of datetime.date objects where the score increased by 10 or more from the day before.
In reponse to your comment, I'd make a measure that checks at save time whether the score boost has occured. However, I would get rid of auto_now_add in favor of writing in the save method.
from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
date = models.DateField(null=True,blank=True)
increased_ten_over_previous_day = models.BooleanField(null=True,blank=True)
def save(self, *args, **kwargs):
self.date = timezone.now().today()
try:
yesterday_score = Score.objects.get(date=self.date-timezone.timedelta(days=1)).score
self.increased_ten_over_previous_day = (yesterday_score * 10) <= self.score
except ObjectDoesNotExist: # called if Score.object.get returns no object; requires you only have one score per user per date
self.increased_ten_over_previous_day = False
super(self, Score).save(*args, **kwargs)
Then you could filter objects for a date_range where increased_ten_over_previous_day is True.

Filtering objects less than 30 days and excluding object's user for a particular object

I have 2 models called Car and Review . A user can write many reviews for a car.
I am able to filter all the reviews for a certain car .
The issue i'm facing is , I only want to filter all the reviews that belong to a particular car in less than 30 days and excluding the user who own the car.
This question support my question Getting all items less than a month old but I am unable
to filter all the reviews that belong to a particular car in less than 30 days and excluding the user who own the car.
class Car(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=11)
class Review(models.Model):
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User)
review = models.TextField()
car = models.ForeignKey(Car)
Filter all the review for a particular car
Car = Car.objects.get(pk=1)
Review = Review.objects.filter(car=Car)
Filter all the review for a particular car but it retrieves all the reviews for everycar in 30 days which I don't want . I want to retrieve only the reviews in less than 30 days for a particular car and excluding the user who own the car
Car = Car.objects.get(pk=1)
from datetime import datetime, timedelta
last_month = datetime.today() - timedelta(days=30)
Review = Review.objects.filter(car__in=car,review__gte=month)
My question is How can I filter all the reviews for a particular car in less than 30 days and not including the reviews of the user who own the car .
thank you for helping me
First of all you need to filter on created field rather than on review field to lookup older entries:
from django.db import Q
from datetime import datetime, timedelta
Car = Car.objects.get(pk=1)
threshold = datetime.now() - timedelta(days=30)
# filtering step by step to give better understanding
reviews = Review.objects.filter(car__id=car.id) # filter by car first
reviews = reviews.filter(created__gte=threshold) # filter by datetime
reviews = reviews.filter(~Q(user__id=car.user.id)) # exclude user reviews who owns the car
# filtering in single step
reviews = Review.objects.filter(
~Q(user__id=car.user.id), car__id=car.id, created__gte=threshold)
How about following the relationship 'backwards' (https://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects) :
from datetime import datetime, timedelta
threshold = datetime.now() - timedelta(days=30)
car = Car.objects.get(pk=1)
reviews = car.review_set.exclude(user=car.user).filter(created__gt=threshold)

Custom model method in Django

I'm developing a web app in Django that manages chores on a reoccurring weekly basis. These are the models I've come up with so far. Chores need to be able to be assigned multiple weekdays and times. So the chore of laundry could be Sunday # 8:00 am and Wednesday # 5:30 pm. I first want to confirm the models below are the best way to represent this. Secondly, I'm a little confused about model relationships and custom model methods. Since these chores are on a reoccurring basis, I need to be able to check if there has been a CompletedEvent in this week. Since this is row level functionality, that would be a model method correct? Based on the models below, how would I check for this? It has me scratching my head.
models.py:
from django.db import models
from datetime import date
class ChoreManager(models.Manager):
def by_day(self, day_name):
return self.filter(scheduledday__day_name = day_name)
def today(self):
todays_day_name = date.today().strftime('%A')
return self.filter(scheduledday__day_name = todays_day_name)
class Chore(models.Model):
objects = ChoreManager()
name = models.CharField(max_length=50)
notes = models.TextField()
class Meta:
ordering = ['scheduledday__time']
class ScheduledDay(models.Model):
day_name = models.CharField(max_length=8)
time = models.TimeField()
chore = models.ForeignKey('Chore')
class CompletedEvent(models.Model):
date_completed = DateTimeField(auto_now_add=True)
chore = models.ForeignKey('Chore')
Then all you need to do is:
monday_of_week = some_date - datetime.timedetla(days=some_date.weekday())
end_of_week = date + datetime.timedelta(days=7)
chore = Chore.objects.get(name='The chore your looking for')
ScheduledDay.objects.filter(completed_date__gte=monday_of_week,
completed_date__lt=end_of_week,
chore=chore)
A neater (and faster) option is to use Bitmasks!
Think of the days of the week you want a chore to be repeated on as a binary number—a bit for each day. For example, if you wanted a chore repeated every Tuesday, Friday and Sunday then you would get the binary number 1010010 (or 82 in decimal):
S S F T W T M
1 0 1 0 0 1 0 = 1010010
Days are reversed for sake of illustration
And to check if a chore should be done today, simply get the number of that day and do an &:
from datetime import datetime as dt
if dt.today().weekday() & 0b1010100:
print("Do chores!")
Models
Your models.py would look a bit like this:
from django.contrib.auth.models import User
from django.db import models
from django.utils.functional import cached_property
class Chore(models.Model):
name = models.CharField(max_length=128)
notes = models.TextField()
class ChoreUser(models.Model):
chore_detail = models.ForeignKey('ChoreDetail')
user = models.ForeignKey('ChoreDetail')
completed_time = models.DateTimeField(null=True, blank=True)
class ChoreDetail(models.Model):
chore = models.ForeignKey('Chore')
chore_users = models.ManyToManyField('User', through=ChoreUser)
time = models.DateTimeField()
date_begin = models.DateField()
date_end = models.DateField()
schedule = models.IntegerField(help_text="Bitmask of Weekdays")
#cached_property
def happens_today(self):
return bool(dt.today().weekday() & self.weekly_schedule)
This schema has a M2M relationship between a User and a Chore's Schedule. So you can extend your idea, like record the duration of the chore (if you want to), or even have many users participating in the same chore.
And to answer your question, if you'd like to get the list of completed events this week, you could could put this in a Model Manager for ChoreUser:
from datetime import datetime as dt, timedelta
week_start = dt.today() - timedelta(days=dt.weekday())
week_end = week_start + timedelta(days=6)
chore_users = ChoreUser.objects.filter(completed_time__range=(week_start, week_end))
Now you have all the information you need in a single DB call:
user = chore_users[0].user
time = chore_users[0].chore_detail.time
name = chore_users[0].chore_detail.chore.name
happens_today = chore_users[0].chore_detail.happens_today
You could also get all the completed chores for a user easily:
some_user.choreuser_set.filter(completed_time__range=(week_start, week_end))