I have a table with a set of Orders that my customers made (purchased, to say so).
The customers can choose the delivery date. That value is stored in each Order in a field called Order.delivery_date (not too much inventive there)
class Order(BaseModel):
customer = ForeignKey(Customer, on_delete=CASCADE, related_name='orders')
delivery_date = DateField(null=True, blank=True)
I would like to annotate a queryset that fetches Orders with the previous Sunday for that delivery_date (mostly to create a weekly report, "bucketized" per week)
I thought "Oh! I know! I'll get the date index in the week and I'll subtract a datetime.timedelta with the number of days of that week index, and I'll use that to get the Sunday (like Python's .weekday() function)":
from server.models import *
import datetime
from django.db.models import F, DateField, ExpressionWrapper
from django.db.models.functions import ExtractWeekDay
Order.objects.filter(
delivery_date__isnull=False
).annotate(
sunday=ExpressionWrapper(
F('delivery_date') - datetime.timedelta(days=ExtractWeekDay(F('delivery_date')) + 1),
output_field=DateField()
)
).last().sunday
But if I do that, I get a TypeError: unsupported type for timedelta days component: CombinedExpression when trying to "construct": the timedelta expression.
Not using the F function in the Extract doesn't make a difference either: I get the same error regardless of whether I use Extract(F('delivery_date')) or Extract('delivery_date')
This is a Python 3.4, with Django 2.0.3 over MySQL 5.7.21
I know that I can always fetch the Order object and do this in Python (I even have a little helper function that would do this) but it'd be nice to fetch the objects with that annotation from the DB (and also for learning purposes)
Thank you in advance.
Oh, I had forgotten about extra
It looks like this should do (at least for MySQL)
orders_q = Order.objects.filter(
delivery_date__isnull=False
).extra(
select={
'sunday': "DATE_ADD(`delivery_date`, interval(1 - DAYOFWEEK(`delivery_date`)) DAY)"
},
).order_by('-id')
It seems to work:
for record in orders_q.values('sunday', 'delivery_date'):
print("date: {delivery_date}, sunday: {sunday} is_sunday?: {is_sunday}".format(
is_sunday=record['sunday'].weekday() == 6, **record)
)
date: 2018-06-04, sunday: 2018-06-03 is_sunday?: True
date: 2018-05-30, sunday: 2018-05-27 is_sunday?: True
date: 2018-05-21, sunday: 2018-05-20 is_sunday?: True
date: 2018-06-04, sunday: 2018-06-03 is_sunday?: True
EDIT: Apparently, extra is on its way to being deprecated/unsupported. At the very least, is not very... erm... fondly received by Django developers. Maybe it'd be better using RawSQL instead. Actually, I was having issues trying to do further filter in the sunday annotation using extra which I'm not getting with the RawSQL method..
This seems to work better:
orders_q = orders_q.annotate(
sunday=RawSQL("DATE_ADD(`delivery_date`, interval(1 - DAYOFWEEK(`delivery_date`)) DAY)", ())
)
Which allows me to further annotate...
orders_q.annotate(sunday_count=Count('sunday'))
I'm not sure why, but when I was using extra, I'd get Cannot resolve keyword 'sunday' into field
Related
I have a model in Django that represents a week. I'd like to have someone enter any date and have the model automatically save the start of the week. That's easy, just override save.
class Week(models.Model):
start_date = models.DateField()
def save(self):
self.start_date = self.start_date - datetime.timedelta(days=date.weekday())
But I'd also like someone to be able to query any day and get the week. So for example, I'd want someone to do this:
this_week = Week.objects.filter(start_date=date.today())
where today is a Wednesday, and get the week object where the date is set for the start of the week. It needs to work for any date, not just today.
I know we can override get_queryset in a manager, but is there a way to edit what was actually searched for? Every manager example I can find just changed the queryset in a static way. Or would my best bet be trying to subclass the DateField?
(Note code above is typed in, simplified, and may contain mistakes, but it works in my actual code)
We can subclass the DateField to each time clean a date time to the start of the week. This thus looks like:
from django.db.models.fields import DateField
from datetime import timedelta
class WeekField(DateField):
def to_python(self, value):
value = super().to_python(value)
if value is not None:
value -= timedelta(days=value.weekday())
return value
Then we can create, filter, etc. with a WeekField. We can thus for example specify this in a Week model:
class Week(models.Model):
week = WeekField()
and then for example use .get_or_create(…):
>>> from datetime import date
>>> Week.objects.get_or_create(week=date.today())
(<Week: Week object (1)>, True)
>>> Week.objects.get_or_create(week=date.today())
(<Week: Week object (1)>, False)
Filter with weeks:
>>> Week.objects.filter(week=date(2021, 8, 31))
<QuerySet [<Week: Week object (1)>]>
>>> Week.objects.filter(week=date(2021, 8, 2))
<QuerySet []>
see the start of the week with .values():
>>> Week.objects.values()
<QuerySet [{'id': 1, 'week': datetime.date(2021, 8, 30)}]>
I did not tested this extensively, but I guess most of the functionality is covered. Please ping me if there is still a use case that is not covered.
At the database level, you can make the ordering based on the start_date, as opposed to the default primary_key
class Week(models.Model):
start_date = models.DateField()
class Meta:
ordering = ['start_date']
From here, it's fairly straight forward to get the week you're looking for.
week = Week.objects.filter(start_date__lte=date.today()).first()
The alternative to using Meta is to simply utilize order_by in your query.
week = Week.objects.filter(start_date__lte=date.today()).order_by('start_date').first()
I am trying to use MongoEngine to apply a filter on a mongodb collection called Employees. The filter is based on country, city and join_date.
The filter condition is that the number of months obtained by subtracting join_date from today's date should be a minimum of "x" months, where x is a setting value. So, for example, if x is 18 months, I need to find all employees whose join_date was a minimum of 18 months prior to today's date.
I am trying to achieve this by calling the filter() method, but I'm unable to figure out how to do that.
matching_records = Employees.objects(
country=rule.country,
city=rule.city) \
.filter(relativedelta.relativedelta(datetime.datetime.now, join_date).months > 18)
I get an error, "name join_date is not defined". I am unable to figure out how to get the filter to work. Please help.
You need to use the lte (less than or equal) or gte (greater than or equal) operators like this:
from datetime import datetime, timedelta
import dateutil.relativedelta
from mongoengine import *
connect()
now = datetime.utcnow()
yesterday = now - dateutil.relativedelta.relativedelta(days=5)
past = now - dateutil.relativedelta.relativedelta(months=20)
class TestDate(Document):
dt = DateTimeField()
# Saving 3 objects to verify the query works
TestDate(dt=now).save()
TestDate(dt=yesterday).save()
TestDate(dt=past).save()
TestDate.objects(dt__lte=now - dateutil.relativedelta.relativedelta(months=18)) # return TestData associated with `past`
TestDate.objects(dt__gte=now - dateutil.relativedelta.relativedelta(months=18)) # return TestData associated with `now` and `yesterday`
I have a model which contains created_at DateTimeField. I want to filter the model to get all object created on this day, month till now.
For Example : Date = 21/06/2016
Now I want to get all objects created on 21/06 till now irrespective of year.
Edit:
To be precise, I have model which stores Date of Birth of Users. I want to get all the users who were born on this day.
I tried using the __range, __gte, __month & __day. This things did not work.
Thanks for your comments and answers. I have used this answer to solve the problem. I have removed the timedelta(days) from the code.
An example of filtering outside of the queryset.
Get the date u want and remove unwanted results
from datetime import datetime, timedelta
twodaysago = str(datetime.strptime(str(datetime.now().date()-timedelta(days=2)), '%Y-%m-%d')).split()[0]
now do your query and filter it like this, you may also do it in the query filter, but if u need some extra manipulations do it in your code
date_filtered = [x for x in query\
if datetime.strptime(
x.get('created_at ', ''),
'%Y-%m-%d') > twodaysago
]
Not sure if I understand the problem correctly, but try this:
before = datetime.date(2016, 06, 21)
today = datetime.date.today()
MyModel.objects.filter(
created_at__month__range=(before.month, today.month),
created_at__day__range=(before.day, today.day)
)
From what I can understand from your question, you want to get all objects with the same date as today, irrespective of the year.
I would use something like this. See if this helps.
from datetime import datetime
today = datetime.now()
OneModel.objects.filter(created_at__day = today.day.__str__(),
created_at__month = today.month.__str__())
For more see this link: How can I filter a date of a DateTimeField in Django?
im fighting with something here i'm using django and may you can help me.
I got a Account model with a date_of_birth field, and i have a method for find out the age.
class Account(AbstractBaseUser, PermissionsMixin):
date_of_birth = models.DateField()
def age(self):
"""
Returns the age from the date of birth
"""
today = datetime.date.today()
try:
birthday = self.date_of_birth.replace(year=today.year)
except ValueError: # raised when birth date is February 29 and the current year is not a leap year
birthday = self.date_of_birth.replace(year=today.year, day=self.date_of_birth.day-1)
if birthday > today:
return today.year - self.date_of_birth.year - 1
else:
return today.year - self.date_of_birth.year
i was wondering if is possible to obtain the age from a query like this:
list = Account.objects.filter('account__age__gte', today)
i tried already but i got this error:
cannot resolve keyword 'age' into field. Choices are:......
and only shows me the fields. not the methods.\
i appreciate your help.
thanks a lot.
You cannot directly query against model method, since custom methods cannot evaluate to their corresponding SQL queries.
You have a couple of options instead:
In the view, compute the earliest date of birth given the age. Example 24 years:
from dateutil.relativedelta import relativedelta
datetime.date.today() - relativedelta(years=24)
datetime.date(1989, 11, 15)
and now, the query would be on the date_of_birth field.
Note that dateutil is a 3rd party library and may not be available with your python by default. (If you want to use timedelta, you could do that too, since datetime.timedelta is python builtin)
Another option (a little less efficient) is to fetch the object queryset, and use a list comprehension to filter out the unwanted records.
qs = Account.objects.all()
qs = [account for account in qs if account.age() > 24]
24, obviously was just an example. replace that with some "sane" value.
I know you have an answer for this already and that answer is accurate, but I think you might benefit from making your age method into a property (actually, I thought this is what is what model properties were for, but I would be happy to be corrected on this point if I am wrong).
Thus, you could do something like this:
class Account(AbstractBaseUser, PermissionsMixin):
date_of_birth = models.DateField()
def _age(self):
"""
Returns the age from the date of birth
"""
today = datetime.date.today()
... {value is computed and returned} ...
age = property(_age)
This, of course, doesn't solve your filtering problem; it just makes it easier to treat the method as if it's an instance attribute and your SQL query will still need to grab everything, or filter by date_of_birth (which if you're going to do a lot, may be nice to include as a custom manager).
I'm using Django 1.5.1, Python 3.3.x, and can't use raw queries for this.
Is there a way to get a QuerySet grouped by weekday, for a QuerySet that uses a date __range filter? I'm trying to group results by weekday, for a query that ranges between any two dates (could be as much as a year apart). I know how to get rows that match a weekday, but that would require pounding the DB with 7 queries just to find out the data for each weekday.
I've been trying to figure this out for a couple hours by trying different tweaks with the __week_day filter, but nothing's working. Even Googling doesn't help, which makes me wonder if this is even possible. Any Django guru's here know how, if it is possible to do?
Since extra is deprecated, here is a new way of grouping on the day of the week using ExtractDayOfWeek.
from django.db.models.functions import ExtractWeekDay
YourObjects.objects
.annotate(weekday=ExtractWeekDay('timestamp'))
.values('weekday')
.annotate(count=Count('id'))
.values('weekday', 'count')
This will return a result like:
[{'weekday': 1, 'count': 534}, {'weekday': 2, 'count': 574},.......}
It is also important to note that 1 = Sunday and Saturday = 7
Well man I did an algorithm this one brings you all the records since the beginning of the week (Monday) until today
for example if you have a model like this in your app:
from django.db import models
class x(models.Model):
date = models.DateField()
from datetime import datetime
from myapp.models import x
start_date = datetime.date(datetime.now())
week = start_date.isocalendar()[1]
day_week =start_date.isoweekday()
days_quited = 0
less_days = day_week
while less_days != 1:
days_quited += 1
less_days -= 1
week_begin = datetime.date(datetime(start_date.year,start_date.month,start_date.day-days_quited))
records = x.objects.filter(date__range=(week_begin, datetime.date(datetime.now())))
And if you add some records in the admin with a range between June 17 (Monday) and June 22 (today) you will see all those records, and if you add more records with the date of tomorrow for example or with the date of the next Monday you will not see those records.
If you want the records of other week unntil now you only have to put this:
start_date = datetime.date(datetime(year, month, day))
records = x.objects.filter(date__range=(week_begin, datetime.date(datetime.now())))
Hope this helps! :D
You need to add an extra weekday field to the selection, then group by that in the sum or average aggregation. Note that this becomes a database specific query, because the 'extra' notation becomes passed through to the DB select statement.
Given the model:
class x(models.Model):
date = models.DateField()
value = models.FloatField()
Then, for mysql, with a mapping of the ODBC weekday to the python datetime weekday:
x.objects.extra(select={'weekday':"MOD(dayofweek(date)+5,7)"}).values('weekday').annotate(weekday_value=Avg('value'), weekday_value_std=StdDev('value'))
Note that if you do not need to convert the MySql ODBC weekday (1 = Sunday, 2 = Monday...) to python weekday (Monday is 0 and Sunday is 6), then you do not need to do the modulo.
For model like this:
class A(models.Model):
date = models.DateField()
value = models.FloatField()
You can use query:
weekday = {"w": """strftime('%%w', date)"""}
qs = A.objects.extra(select=weekday).values('w').annotate(stat = Sum("value")).order_by()