how to have a TimeField with auto increment of 5 minutes (django) - django

I have a model in django that consists of these fields:
device = models.CharField(max_length=150, null=True)
time_received = models.TimeField(auto_now_add=True, null=True)
display_time = [ i want it to have same hour as time_received , minute to be started at 00 and then have interval of 5 and finally second to be 00 ]
the reason i want to do this , is to display time in an orderly manner.
for example:
device A is received at 8:01:20 ---> device A is shown as 8:00:00
device A is received at 8:07:22 ---> device A is shown as 8:05:00
one final note: I need to store both display time and received time in postgresql
thanks for helping

You can accomplish this by creating a custom method on your model that calculates the display_time based on the time_received field.
Create a method on your model that calculates the display_time by taking the hour from the time_received, setting the minutes and seconds to 0, and then adding or subtracting minutes so that the interval is 5.
from datetime import datetime, timedelta
class YourModel(models.Model):
device = models.CharField(max_length=150, null=True)
time_received = models.TimeField(auto_now_add=True)
display_time = models.TimeField(null=True)
def get_display_time(self):
hour = self.time_received.hour
# Convert minute to nearest round of 5
minute = self.time_received.minute // 5 * 5
# Create a new datetime object with the calculated hour, minute and second
display_time = datetime.strptime(f"{hour}:{minute}:00", "%H:%M:%S").time()
return display_time
def save(self, *args, **kwargs):
self.display_time = self.get_display_time()
super().save(*args, **kwargs)
This way, the display_time will always be rounded to the nearest 5 minute interval, and the seconds will be set to 0.
Personally, I would suggest to create property for this instead of saving in database.

Rather than changing the model field, you can add a property method to floor the value, like this:
import datetime as dt
class YourModel(models.Model):
...
#property
def display_time(self):
resolution = dt.timedelta(minutes=5)
delta = dt.timedelta(seconds=self.recieve_time.seconds%resolution.seconds)
return self.recieve_time - delta

Related

query to django model to compare daily sale over previous day (compare two row of database model)

I have a django model that is "DailyReport" of the companies sale I want to find out company sale change over the previous day.
the model that i define is like that:
class DailyReport(models.Model):
company = models.CharField(max_length=50)
sale = models.IntegerField()
date = models.DateField()
How can i figure out this issue to add new column for every report that represent change rate over the previous day
Use the Lag window function to annotate each row with the previous sale amount for that company.
Then use another annotation to calculate the difference between the current and previous sale
from django.db.models import Window, F
from django.db.models.functions import Lag
DailyReport.objects.annotate(
prev_val=Window(
expression=Lag('sale', default=0),
partition_by=['company'],
order_by=F('date').asc(),
)
).annotate(
diff=F('sale') - F('prev_val')
)
Assuming you can have only one record of a company for each day, you can create a model property:
#property
def previous_day_sale(self):
date = self.date
dr = DailyReport.objects.filter(company=self.company, date=date-timedelta(days=1)
if dr:
return dr.first().sale - self.sale
You may need to override the save method, but you will have to cover all edge cases.
class DailyReport(models.Model):
company = models.CharField(max_length=50)
sale = models.IntegerField()
date = models.DateField()
sale_over_previous_day = models.IntegerField()
def save(self, *args, **kwargs):
previous_day_sale_object = DailyReport.objects.filter(company=self.company, date=date-timedelta(days=1))
if previous_day_sale_object:
previous_day_sale = previous_day_sale_object[0].sale
else:
previous_day_sale = 0
self.sale_over_previous_day = self.sale - previous_day_sale
super(DailyReport, self).save(*args, **kwargs)

Django check if time is in an object's timefield range

How do I check if a time is in the range of two timefields?
#appointmentApp.models Appointment
#Appointments
class Appointment(models.Model):
...
date_selected = models.DateField(blank=True, default='2001-12-1')
time_start = models.TimeField(blank=True)
total_time = models.IntegerField(blank=False, null=False, default=0)
end_time = models.TimeField(blank=True, null=True)
appointment_accepted = models.BooleanField(blank=False, default=False)
Total_time and end_time are calculated after the object is created because it requires an attribute from a many to many field
In my views where I want to check if there is an object that exists
#appointmentApp.views def appointment_view
def appointment_view(request):
...
#form is submitted
else:
form = AppointmentForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
date = cd.get('date_selected') #this is a datetime field
start = cd.get('time_start')
#appointments = Appointment.objects.filter(date_selected=date, start_time__range=())
#form needs to save first because total time and end time don't have values until after the object is saved
form.save()
new_appointment = Appointment.objects.filter(date_selected=date, time_start=start, appointment_accepted=False)
for apps in new_appointment:
new_app_starttime = apps.time_start
new_app_endtime = apps.end_time
appointments = Appointment.objects.filter(date_selected=date, appointment_accepted=True)
#if the start time
#my question is here
for app in appointments:
if app.time_start__range(new_app_starttime, new_app_endtime):
print('another appointment begins/ends during this time slot!')
return render(request, 'home.html')
How can it check if its time falls in between two timefields?
Edit:
Been looking around these question have been a bit helpful
Django filter when current time falls between two TimeField values
Django After Midnight Business Hours TimeField Comparison Error
the current method I am trying is
#appointmentApp.views appointment_view
if Appointment.objects.filter(date_selected=date, time_start__lte=F('end_time')).exists():
print('Y E S')
Appointment.objects.filter(date_selected=date, time_start=start, appointment_accepted=False).delete()
This line hasn't returned what I am looking for and Im not sure where Im going wrong with it
Its not a pretty solution but at the end of the day it works
this answer solved my question
https://stackoverflow.com/a/65572546/10485812
#gets the appointment that is being checked
n_a = Appointment.objects.get(date_selected=date, time_start=start, appointment_accepted=False)
#filters for all appointments that have been accepted and share the same date as n_a
appointments = Appointment.objects.filter(date_selected=date, appointment_accepted=True)
#checks each appointment on that day and sees if the times overlap
for apps in appointments:
if (apps.time_start < n_a.time_start < apps.end_time) or (apps.time_start < n_a.end_time < apps.end_time) or (n_a.time_start <= apps.time_start and n_a.end_time >= apps.end_time):

Displaying / Testing outputs are correct. Sanity Check

I am still learning Django and slowly improving but I have a few questions, I have my whole model below:
from django.db import models
from datetime import datetime, timedelta
# Create your models here.
year_choice = [
('year1','1-Year'),
('year3','3-Year')
]
weeksinyear = 52
hours = 6.5
current_year = datetime.year
class AdminData(models.Model):
year1 = models.IntegerField()
year3 = models.IntegerField()
#property
def day_rate_year1(self):
return self.year1 / weeksinyear / hours
class Price(models.Model):
name = models.CharField(max_length=100)
contract = models.CharField(max_length=5, choices=year_choice)
start_date = models.DateField(default=datetime.now)
end_date = models.DateField(default=datetime(2021,3,31))
def __str__(self):
return self.name
The main concern for me at the moment is trying to understand if my function def day_rate_year1(self): is working correctly, if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input.
I am trying to work out the day rate so I can then use the start and end dates and work out the number of days between the two to calculate a price which is then displayed to the user which can again change depending on the number of days and the contract type which is a 3 year option or 1 year option.
Let me know if you need the views or templates as well.
Thanks for the help!
if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input
if you launch a shell session as below, you should see the output of your property.
python manage.py shell
Expected output:
>>> from app_name.models import AdminData
>>> test = AdminData.objects.create(year1=2010, year3=2016)
>>> print(test.day_rate_year1)
5.946745562
>>>

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.

Is it bad practice to return a tuple from a Django Manager method rather than a queryset?

I have some complex business logic that I have placed in a custom ModelManager. The manager method returns a tuple of values rather than a queryset. Is this considered bad practice? if so, what is the recommended approach. I do not want the logic in the View, and Django has no Service tier. Plus, my logic needs to potentially perform multiple queries.
The logic needs to select an Event closest to the current time, plus 3 events either side. When placed in the template, it is helpful to know the closest event as this is the event initially displayed in a full-screen slider.
The current call is as follows:
closest_event, previous_events, next_events = Event.objects.closest()
The logic does currently work fine. I am about to convert my app. to render the Event data as JSON in the template so that I can bootstrap a backbone.js View on page load. I plan to use TastyPie to render a Resource server-side into the template. Before I refactor my code, it would be good to know my current approach is not considered bad practice.
This is how my app. currently works:
views.py
class ClosestEventsListView(TemplateView):
template_name = 'events/event_list.html'
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
closest_event, previous_events, next_events = Event.objects.closest()
context['closest_event'] = closest_event
context['previous_events'] = previous_events
context['next_events'] = next_events
return self.render_to_response(context)
models.py
from datetime import timedelta
from django.db import models
from django.utils import timezone
from model_utils.models import TimeStampedModel
class ClosestEventsManager(models.Manager):
def closest(self, **kwargs):
"""
We are looking for the closest event to now plus the 3 events either side.
First select by date range until we have a count of 7 or greater
Initial range is 1 day eithee side, then widening by another day, if required
Then compare delta for each event data and determine the closest
Return closest event plus events either side
"""
now = timezone.now()
range_in_days = 1
size = 0
while size < 7:
start_time = now + timedelta(days=-range_in_days)
end_time = now + timedelta(days=range_in_days)
events = self.filter(date__gte=start_time, date__lte=end_time, **kwargs).select_related()
size = events.count()
range_in_days += 1
previous_delta = None
closest_event = None
previous_events = None
next_events = None
position = 0
for event in events:
delta = (event.date - now).total_seconds()
delta = delta * -1 if delta < 0 else delta
if previous_delta and previous_delta <= delta:
# we have found the closest event. Now, based on
# position get events either size
next_events = events[:position-1]
previous_events = events[position:]
break
previous_delta = delta
closest_event = event
position += 1
return closest_event, previous_events, next_events
class Event(TimeStampedModel):
class Meta:
ordering = ['-date']
topic = models.ForeignKey(Topic)
event_type = models.ForeignKey(EventType)
title = models.CharField(max_length=100)
slug = models.SlugField()
date = models.DateTimeField(db_index=True)
end_time = models.TimeField()
location = models.ForeignKey(Location)
twitter_hashtag = models.CharField(null=True, blank=True, max_length=100)
web_link = models.URLField(null=True, blank=True)
objects = ClosestEventsManager()
def __unicode__(self):
return self.title
I don't think it's bad practice to return a tuple. The first example in the ModelManager docs returns a list.
Saying that, if you want to build a queryset instead then you could do something like this -
def closest(self, **kwargs):
# get the events you want
return self.filter(pk__in=([event.id for event in events]))
It's fine, even Django's own get_or_create does it. Just make sure it's clear to whoever's using the function that it's not chainable (ie doesn't return a queryset).