How to make a Timer exactly 30 seconds from created date Django - python

I need to make an operation only available in 30 seconds, and then it expires. I Have this model:
(operation)
status = CharField(max_length=10, default="INI")
created_at = DateTimeField(auto_now_add=True)
account = ForeignKey(Account, null=False, on_delete=CASCADE)
metadata = models.JSONField(default=dict)
external_ref = CharField(max_length=128)
expires_at = DateTimeField(default=timezone.now()+timezone.timedelta(seconds=30))
I wanna be able to create in the expires_at field, a timestamp with exactly 30 seconds from the created_at Date, it is like a timeout function, but when I run the test:
def test_timeout_is_30_seconds(self):
print(self.operation.created_at)
timer = self.operation.created_at + timezone.timedelta(seconds=30)
print(timer)
self.assertEqual(self.operation.expires_at, timer)
it fails with this message:
AssertionError: datetime.datetime(2021, 6, 22, 19, 0, 42, 537490, tzinfo=<UTC>) != datetime.datetime(2021, 6, 22, 19, 0, 45, 844588, tzinfo=<UTC>)
I dont know if I need to make an external function or method inside the class or directly in the View, but I would prefer this default behavior in the models so I dont need to worry about setting expiry dates
I would be very grateful if you could help me solve it! :D any tips and information is appreciated

This is a common error. Your expression timezone.now()+timezone.timedelta(seconds=30) is being evaluated once, when the class is defined, and that value is being used as the default for every instance.
What you actually want is for the expiration time to be freshly calculated each time a new instance is created. Which means that you want to set default to a function:
def half_minute_hence():
return timezone.now() + timezone.timedelta(seconds=30)
expires_at = DateTimeField(default=half_minute_hence)

Related

Django: Annotate two values of the most recent foreign key

I'm actually using python 3.7 and Django 3.0.4.
I using in my app models.py like this for a simple system of messages.
from torii.models import User
class Room(models.Model):
users = models.ManyToManyField(User,
related_name='rooms',
blank=True)
name = models.CharField(max_length=100)
class Message(models.Model):
room = models.ForeignKey(Room,
on_delete=models.CASCADE,
related_name='messages')
user = models.ForeignKey(User,
on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True)
So when my user send a message I create a new Message object attached to my Room.
I want to query all Room for a given User and annotate the date of the most recent message and the last message in my query.
No problem to get the most recent date in my related messages using Max like this:
for room in Room.objects.filter(users=user).annotate(last_message_date=Max('messages__date')).order_by('-last_message_date'):
print(room.__dict__)
{'id': 7, 'name': 'room-7', 'last_message_date': datetime.datetime(2020, 3, 20, 14, 0, 2, 118190, tzinfo=<UTC>)}
{'id': 9, 'name': 'room-9', 'last_message_date': datetime.datetime(2020, 3, 8, 15, 19, 52, 343780, tzinfo=<UTC>)}
{'id': 8, 'name': 'room-8', 'last_message_date': datetime.datetime(2020, 3, 7, 17, 18, 32, 648093, tzinfo=<UTC>)}
But I don't find any way to simply annotate the content of the last message. I have tried Max('messages__content') but in fact, it's order message by alphabetic order and it's always the same who is returned... I tried several subqueries with F and Q but that it didn’t work very well.
How can I annotate the result of room.messages.last().content directly with my query?
Finally, I solve this problem using OuterRef and Subquery documented there.
from django.db.models import Max, OuterRef, Subquery
newest = Message.objects.filter(room_id=OuterRef('pk')).order_by('-date')
for room in Room.objects.filter(users=user)
.annotate(last_message_date=Max('messages__date'),
last_message=Subquery(newest.values('content')[:1]))
.order_by('-last_message_date'):
print(r.__dict__)
As I understand the behavior, we prepare beforehand Subquery by saying to use id of the element who call the subquery to annotate the filtred elements and order then by -date. So it returns the most recent element and we take the value of field content to annotate it.
This will create a complex but unique request using all the performance of the database.

How to check if a Marshmallow schema's Datetime field is less than today's date?

I have an endpoint that accepts POST method. The POST body contains a DateTime field of format - "%Y-%m-%d %H:%MZ". I need to validate if that datetime is less than current Datetime in UTC. I'm using Marshmallow to validate the request body.
run_datetime = fields.DateTime(format="%Y-%m-%d %H:%MZ")
Are there any inbuilt validators for this case to validate DateTime field. Or should I be writing a custom function for this to compare the run_datetime with today's UTC's datetime.
There is no built-in validator that can solve your particular issue at hand, take a look at the available validators here.
Although, defining your own validator is trivially easy, for your particular case: fields.Datetime will accept an argument named validate which can take a function that returns a bool. For example, I quickly defined a lambda here to validate if the datetime is more recent than "now":
from datetime import datetime
from marshmallow import Schema, fields
NOW = datetime(2020, 11, 23, 14, 23, 0, 579974)
class User(Schema):
id = fields.Integer(required=True)
name = fields.String(required=True)
# Define an arbitrary datetime here like NOW or just use datetime.now()
date_created = fields.DateTime(required=True, validate=lambda x: x > NOW)
# This will succeed
User().load(dict(
id=10,
name="Test",
# Note that this date is more recent than previously defined NOW
date_created="2020-11-23T14:24:40.224965",
))
#{'date_created': datetime.datetime(2020, 11, 23, 14, 24, 40, 224965),
# 'id': 10,
# 'name': 'Test'}
# While this will fail
User().load(dict(
id=10,
name="Test",
# Here the date is one month behind than NOW
date_created="2020-10-23T14:24:40.224965",
))
# ValidationError: {'date_created': ['Invalid value.']}

Django mock timezone aware datetime on creating model

I have a test where I create a few objects:
def test_get_courier_task_returns_couriers_tasks(self):
with patch('django.utils.timezone.now', return_value=make_aware(datetime(2018, 1, 24, 11, 57))):
task1 = TaskFactory()
response = json.loads(MyAPI.get_tasks_list(self.user.username))
print('[*] Response timestamp: {}'.format(response['content'][0]['timestamp']))
The Task has created_timestamp field with auto_add_now set to True and to_json() method which is used in get_tasks_list() above:
class Task(models.Model):
created_timestamp = models.DateTimeField(auto_now_add=True)
def to_json(self):
to_return = {
'timestamp': self.created_timestamp.strftime('%d-%m-%Y %H:%M')
}
return to_return
Unfortunately tests give this output:
[*] Response timestamp: 24-01-2018 10:57
I have checked that this is timezone aware, but instead of giving me UTC+1 it gives UTC+0 on saving. What do I have to do? I have USE_TZ = True in my settings and I have applied migrations. This question did not help with my problem.
It turned out that giving timezone explicityl helped:
with patch('django.utils.timezone.now', return_value=datetime(2018, 1, 24, 11, tzinfo=pytz.timezone('utc'))):
Try providing make_aware with the timezone you want.
Also checking for a specific time in the test is a bit circular, probably don't need to check for it and just make sure it runs and produces a timestamp.

Storing Time Difference Django

On my Django site, I am saving a user's reactions so when a user clicks a button, I store it as a created time and when the user clicks it second time, I stored the time as a finish time and so forth. Here it is my model;
class UserStatus(models.Model):
STATUS_TYPES = (
('online', 'online'),
('offline', 'offline')
)
user = models.ForeignKey(User)
status_type = models.CharField(max_length=30, choices=STATUS_TYPES, default='online')
created_time = models.DateTimeField(auto_now_add=True)
finish_time = models.DateTimeField(blank=True, null=True)
time_diff = models.DateTimeField(blank=True, null=True)
I added time_diff to show the time difference between created_time and finish time. When I try an example in the shell, I use;
user_status.created_time
datetime.datetime(2016, 3, 31, 12, 50, 21, tzinfo=<UTC>)
user_status.finish_time
datetime.datetime(2016, 3, 31, 12, 51, 37, 998593, tzinfo=<UTC>)
user_status.finish_time - user_status.created_time
datetime.timedelta(0, 76, 998593)
Everything seems to be fine until now, however when I wrote user_status.save() it gave an error;
line 93, in parse_datetime
match = datetime_re.match(value)
TypeError: expected string or buffer
I did not understand why it gave such an error.
Thank you in advance.
Now you try to use DateTimeField, but this field can only be used for storing date and time (but not time difference). You should use DurationField for storing timedelta.

How to access model attributes in custom manager?

I have a Django model for an object with a DateField. I also have two managers that filter these objects for a specific date range.
class SchoolYearManager(models.Manager):
def get_query_set(self):
now = datetime.datetime.now()
current_year = now.year
start_date = datetime.date(current_year, 7, 1)
end_date = datetime.date((current_year + 1), 6, 30)
return super(SchoolYearManager, self).get_query_set().filter(status=self.model.LIVE).filter(event_date__range=(start_date, end_date))
class PastSchoolYearManager(models.Manager):
def get_query_set(self):
current_year = self.model.event_date.year
start_date = datetime.date(current_year, 7, 1)
end_date = datetime.date((current_year + 1), 6, 30)
return super(PastSchoolYearManager, self).get_query_set().filter(status=self.model.LIVE).filter(event_date__range=(start_date, end_date))
class Event(models.Model):
LIVE = 3
DRAFT = 4
STATUS_CHOICES = (
(LIVE, 'Live'),
(DRAFT, 'Draft'),
)
status = models.IntegerField(choices=STATUS_CHOICES, default=4, help_text="Only entries with a status of 'live' will be displayed publically.")
event_date = models.DateField()
objects = Models.Manager()
school_year_events = SchoolYearManager()
past_school_year_events = PastSchoolYearManager()
My first manager (SchoolYearManager) works as expected to return events within that date range. But when I try to do Event.past_school_year_events.all(), I get an Attribute error: "type object 'Event' has no attribute 'event_date'".
My goal with the second manager (PastSchoolYearEvents) is to wrap a generic year archive view to return events within a date range for a specific year.
Why can't I call self.model.event_date within the manager?
Am I going about this the right way? If not, what's the better way to do this?
How could this work? What event date could it be referring to? When you call Event.past_school_year_events.all(), you don't have a model instance. self.model, as the name implies, refers to the model class. So how could it know what date you mean?
I can't actually work out what it is you're trying to do - where you are going to get the date from. Do you want to start from an event, then get all the other events in the same year as that one? In which case, I suspect you just want to make past_school_year_events into a model method, not a manager.
Edit If you want to start from a specific year, a manager certainly is appropriate, but you'll need to pass the year into your manager method as a parameter. For this, I'd use a separate method on the manager, rather than overriding get_query_set - in fact, add another method to SchoolYearManager:
class SchoolYearManager(models.Manager):
def live_events(self, start_date, end_date):
return self.filter(status=self.model.LIVE).filter(event_date__range=(start_date, end_date))
def this_year(self):
now = datetime.datetime.now()
current_year = now.year
start_date = datetime.date(current_year, 7, 1)
end_date = datetime.date((current_year + 1), 6, 30)
return self.live_events(start_date, end_date)
def from_year(self, year):
start_date = datetime.date(year, 7, 1)
end_date = datetime.date(year+1, 6, 30)
return self.live_events(start_date, end_date)
Now you've got just one manager, that doesn't override get_query_set, so you don't even need to preserve the default one - you can keep this one as objects. So you can do:
Event.objects.this_year() # all this year's events
Event.objects.from_year(2010) # all events from 2010

Categories

Resources