How to get queryset in django? - django

I am trying to send an email to those orders that is created 5 minutes before the datetime.now(). I try to filter the orders but it is not working, it is not giving me any queryset. How to do this? I am sharing my code.
def my_email():
now = datetime.now() - timedelta(minutes=5) # 11:55
now = now.replace(tzinfo=pytz.utc)
print(now)
order = Order.objects.filter(createdAt__gt = now)
print(order)
for o in order:
print(o._id)

You should not replace the timezone with UTC. now() will obtain the current datetime for a given timezone. If you replace that with UTC, then the result can be several hours ahead or behind the current time.
It might also be better to make use of a Now() expression [Django-doc], which will use the clock of the database, we thus can filter with:
from django.db.models.functions import Now
from datetime import timedelta
order = Order.objects.filter(createdAt__gt=Now()-timedelta(minutes=5))
If you plan to run this to send emails for orders the last five minutes, this will however be quite error-prone: since a scheduled task can always have a few milliseconds of delay, it thus means that certain items can, by the time the query is running, be considered too old. You probably might want to use a BooleanField that indicates if an email has been sent.

Related

Django. How to correctly save time & timezones

I have this code that behaves in a rather strange way and opens the question, how should I deal with timezones? So, first I have a datetime object I build from the info a user posts:
time_zone = request.POST.get("time_zone")
date_start = request.POST.get("date_start")
time_day = request.POST.get("time_day")
time_zone_obj = pytz.timezone("Etc/" + time_zone) # GMT + 2 in this example
date_start = datetime.strptime(date_start, "%d/%m/%Y")
date_start = date_start.replace(tzinfo=time_zone_obj)
time_day = datetime.strptime(time_day, "%I:%M %p")
date_start = date_start.replace(hour=time_day.hour, minute=time_day.minute)
...
event.date_start = date_start
event.save()
print("event.date_start.hour:%s" % event.date_start.hour)
print("event.date_start.tzinfo:%s" % event.date_start.tzinfo)
print("is_aware(event.date_start:%s)" % is_aware(event.date_start))
return redirect("event_detail", event_id=event.id)
This prints event.date_start.hour:6, event.date_start.tzinfo:Etc/GMT+2 and is_aware:True. Then, inmediatlty after saving the object and printing the hour, it redirects to the event_detail view, very simple:
def event_detail(request, event_id):
event = get_object_or_404(Event, id=event_id)
print("event.date_start.hour:%s" % event.date_start.hour)
print("event.date_start.tzinfo:%s" % event.date_start.tzinfo)
...
And it prints event.date_start.hour:8 and event.date_start.tzinfo:UTC. (it has replaced the tz info with UTC) I don't understand why. I am saving the object with a clear tz_info. Plz note that I printed the hour after I saved the object and then after I retrieved it in the other view. It has a difference of two hours that must have something to do with the timezone the user selected (GMT + 2). Why is this? Which is the best way to save this data?
The user submits "6:00 AM" + "GMT+2" in the form and then later when I want to show the time in the event detail html ({{ event.date_start|date:"h:i A" }}) it displays "8:00 AM".
I assume you're using PostgreSQL to save the timezone aware timestamp.
It's important to understand that (contrary to the name and popular belief) PostgreSQL doesn't save the timezone of the timezone aware timestamp. It's just a way to tell PostgreSQL that the value is not in some local time, but is timezone aware.
PostgreSQL then converts it to UTC and stores as such. If the original timezone is important, you need to store it separately.
More info on the topic: https://www.postgresqltutorial.com/postgresql-timestamp/
The best way to store this data is a separate column (usually called timezone). I use https://pypi.org/project/django-timezone-field/
Then either activate timezone (https://docs.djangoproject.com/en/3.1/ref/utils/#django.utils.timezone.activate) or use localtime (https://docs.djangoproject.com/en/3.1/ref/utils/#django.utils.timezone.localtime) util function.
As per the Django docs,
"When support for time zones is enabled, Django stores DateTime information in UTC in the database. It’s still good practice to store data in UTC in your database. The main reason is the Daylight Saving Time (DST). "
So saving DateTime in UTC format in the database as expected.
Now, going ahead with your requirement. In order to display the time back in the timezone which was used for saving you need to add a column in the DB to store the timezone info.
While retrieving the DateTime, convert it into the required timezone back using the tzinfo stored in DB.
This is the correct way of doing. Hope this helps you understand better.

Django manager with datetime.timedelta object inside F query combined with annotate and filter

I am trying to create manager method inside my app, to filter emails object, that have been created 5/10/15 minutes or what so ever, counting exactly from now.
I though I'am gonna use annotate to create new parameter, which will be bool and his state depends on simple subtraction with division and checking if the result is bigger than 0.
from django.db.models import F
from django.utils import timezone
delta = 60 * 1 * 5
current_date = timezone.now()
qs = self.annotate(passed=((current_date - F('created_at')).seconds // delta > 0)).filter(passed=True)
Atm my error says:
AttributeError: 'CombinedExpression' object has no attribute 'seconds'
It is clearly happening duo the fact, that ((current_date - F('created_at')) does not evaluate to datetime.timedelta object but to the CombinedExpression object.
I see more problems out there, i.e. how to compare the expression to 0?
Anyway, would appreciate any tips if I am somewhere close to achieve my goal or is my entire logic behind this query incorrect
Well, I managed to find the solution, even though it might not be the elegant one, it works
qs = self.annotate(foo=Sum(current_date - F('created_at'))).filter(foo__gt=Sum(timezone.timedelta(seconds=delta)))
Why not something like this:
time_cut_off = timezone.now() - timezone.timedelta(minutes=delta)
qs = self.filter(created_at__gte=time_cut_off)
This will get you the messages created in the last delta minutes. Or where you looking for messages created exactly 5 minutes ago (how do you define that if that is the question).
The documentation provides a simple and elegant solution if your timedelta is a constant :
For date and date/time fields, you can add or subtract a timedelta object. The following would return all entries that were modified more than 3 days after they were published:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
In your case I don't think you even need the F() objects.

Django-Postgres: how to group by DATE a datetime field with timezone enabled

I am having this problem with prostgresql and django:
I have a lot of events that were created on a certain date at a certain time which is stored in a datetime field created .
I want to have aggregations based on the date part of the created field. The simplest examples is: how many event are in each day of this month?.
The created field is timezone aware. So the result should change depending on the timezone the user is in. For example if you created 2 events at 23:30 UTC time on 2017-10-02 if you view them from UTC-1 you should see them on 3rd of October at 00:30 and the totals should add for the 3rd.
I am struggling to find a solution to this problem that works with a lot of data. So doing for each day and SQL statement is not an option. I want something that translates into:
SELECT count(*) from table GROUP BY date
Now I found a solution for the first part of the problem:
from django.db import connection
truncate_date = connection.ops.date_trunc_sql('day', 'created')
queryset = queryset.extra({'day': truncate_date})
total_list = list(queryset.values('day').annotate(amount=Count('id')).order_by('day'))
Is there a way to add to this the timezone that should be used by the date_trunc_sql function to calculate the day? Or some other function before date_trunc_sql and then chain that one.
Thanks!
You're probably looking for this: timezone aware date_trunc function
However bear in mind this might conflict with how your django is configured. https://docs.djangoproject.com/en/1.11/topics/i18n/timezones/
Django 2.2+ supports the TruncDate database function with timezones
You can now do the following to :
import pytz
east_coast = pytz.timezone('America/New_York')
queryset.annotate(created_date=TruncDay("created", tzinfo=east_coast))
.values("created_date")
.order_by("created_date")
.annotate(count=Count("created_date"))
.order_by("-created_date")

Django query filter contacts on time now()

My question is:
Using the following query how can I get contacts that are due now()? ie all contacts at the time of running the view that have not been sent
Contact.objects.filter(send_email_on=<WHERE TIME NOW???? , status='not sent')
Note I don't want future contacts juts ones not send up to now()
Well, you may try with:
from datetime import datetime
# greater than or equal now(), change the __lte for whatever you need
Contact.objects.filter(send_email_on__lte=datetime.now(), status='not sent')
You can take a look at the docs which is very helpful with Field lookups strings.
HINT:
__lt lower than
__lte lower than or equal
__gt greater than
__gte greater than or equal
import datetime
from django.utils.timezone import utc
now = datetime.datetime.utcnow().replace(tzinfo=utc)
Contact.objects.filter(send_email_on__lt=now , status='not sent')
Provided that send_email_on is a DateTimeField.
For the lt(less than) see here.
Though I think you need to use celery and periodic tasks.

Django queryset aggregate by time interval

Hi I am writing a Django view which ouputs data for graphing on the client side (High Charts). The data is climate data with a given parameter recorded once per day.
My query is this:
format = '%Y-%m-%d'
sd = datetime.datetime.strptime(startdate, format)
ed = datetime.datetime.strptime(enddate, format)
data = Climate.objects.filter(recorded_on__range = (sd, ed)).order_by('recorded_on')
Now, as the range is increased the dataset obviously gets larger and this does not present well on the graph (aside from slowing things down considerably).
Is there an way to group my data as averages in time periods - specifically average for each month or average for each year?
I realize this could be done in SQL as mentioned here: django aggregation to lower resolution using grouping by a date range
But I would like to know if there is a handy way in Django itself.
Or is it perhaps better to modify the db directly and use a script to populate month and year fields from the timestamp?
Any help much appreciated.
Have you tried using django-qsstats-magic (https://github.com/kmike/django-qsstats-magic)?
It makes things very easy for charting, here is a timeseries example from their docs:
from django.contrib.auth.models import User
import datetime, qsstats
qs = User.objects.all()
qss = qsstats.QuerySetStats(qs, 'date_joined')
today = datetime.date.today()
seven_days_ago = today - datetime.timedelta(days=7)
time_series = qss.time_series(seven_days_ago, today)
print 'New users in the last 7 days: %s' % [t[1] for t in time_series]