I am following this to add a custom validation on my modelform and it is working...mostly.
My code:
from django import forms
from django.utils.translation import ugettext_lazy as _
from datetime import datetime, timedelta
from datetimewidget.widgets import DateWidget
from .models import User
class UserForm(forms.ModelForm):
class Meta:
# Set this form to use the User model.
model = User
# Constrain the UserForm to just these fields.
fields = ("birthdate")
widgets = {
'birthdate': DateWidget(attrs={'id':"id_birthdate"}, bootstrap_version=3)
}
def clean_birthdate(self):
birthdate = self.cleaned_data["birthdate"]
min_time = datetime.strptime('1920-01-01', '%Y-%m-%d').date()
delta = birthdate - min_time
if delta <= timedelta(days=0):
raise forms.ValidationError(_("We don't accept people born before 1920"))
return birthdate
It raises the error like intended until 1900-01-01, but once i enter to 1899 it doesn't.
I am not sure what may be causing it. I am using DateTimeWidget.
The error i am getting is:
year=1899 is before 1900; the datetime strftime() methods require year >= 1900
I checked the result of the comparison and it is working as intended (False for years below 1920).
In short model is being updated and error is not being raised when it should.
This is a limitation of python's built-in strftime function. It does not support dates prior to 1900. Try this instead
if birthdate.year < 1920:
raise forms.ValidationError(_("We don't accept people born before 1920"))
Related
I have the following (simplified) model and factory:
models.py
class Event():
duration = FloatField()
start_time = TimeField()
finish_time = DateTimeField()
def save(self, *args, **kwargs):
self.finish_time = self.start_time + timedelta(hours=self.duration)
event_factory.py
from factory import Faker
class EventFactory:
date = Faker(
"date_time_this_month",
before_now=False,
after_now=True,
tzinfo=timezone.get_current_timezone(),
)
start_time = Faker("time_object")
duration = Faker("random_int")
However, my save method raises Warning: DateTimeField Event.finish_time received a naive datetime (2022-03-28 12:43:38) while time zone support is active.
date is aware due to tzinfo argument, but start_time is naive (I checked with django's timezone.is_aware()), because time providers in Faker do not seem to allow any timezone parameters.
Any suggestion in getting a fake aware time object in factory-boy/Faker?
Try to use FuzzyDateTime objects, they return a timezone aware object: https://factoryboy.readthedocs.io/en/stable/fuzzy.html?highlight=timezone
I try to make a query to select all the object that where modified since a specified time
This time is now - max_schedule_delay where max_schedule_delay is a data from the object (see code sample below).
I try multiple thing .. but here I am. Maybe you will be able to help me find a way.
Environment
python 2.7
django 1.11
database : Posgresql
Sample code
from django.db import models
class MyObject(models.Model):
# last modification date
mtime = models.DateTimeField(auto_now=True, db_index=True)
# maximum delay before rescheduling in seconds
max_schedule_delay = models.IntegerField()
What I want to achieve
select * from MyObject where (mtime + max_schedule_delay > now)
My tests
from django.db.models import F, ExpressionWrapper,, TimeField, DateTimeField
from django.db.models.functions import Cast
import datetime
now = datetime.datetime.now()
MyObject.objects.filter(max_schedule_delay__lte=now - F("mtime")) # here max_schedule_delay is a integer, so this query is not possible
# I try to split this query in two, but still not wotking
MyObject.objects.annotate(threshold=ExpressionWrapper(now - F("max_schedule_delay"), output_field=DateTimeField())).filter(mtime__gte=F("threshold"))
MyObject.objects.annotate(threshold=ExpressionWrapper(F("mtime") + F("max_schedule_delay"), output_field=DateTimeField())).filter(threshold__gte=now)
MyObject.objects.annotate(as_date=Cast("max_schedule_delay", TimeField()))
Any help is welcome,
Thanks !
After some more research, and based on the post Using dateadd in django filter I was able to do something, but only for posgresql
from django.db.models import Func, DateTimeField
import datetime
class DateAdd(Func):
"""
Custom Func expression to add date and int fields as day addition
Usage:
Model.objects.annotate(annotation=DateAdd('field_date','field_seconds'))
"""
arg_joiner = " + CAST("
template = "%(expressions)s || ' seconds' as INTERVAL)"
output_field = DateTimeField()
MyModel.objects.annotate(
threshold=DateAdd("mtime", "max_schedule_delay")
).filter(
max_schedule_delay__isnull=False,
state=botSession.STATE.DONE,
threshold__lte=datetime.datetime.now()
)
Take the example model as follows:
import datetime
class Calendar(models.Model)
def now():
return datetime.date.today()
def 24hrslater():
return datetime.date.today() + datetime.timedelta(days=1)
now = models.DateTimeField(default=now)
24hrslater = models.DateTimeField(default=24hrslater)
Is there a way to extract just the date from these date time fields? I have tried the following (as suggested on another thread):
todaysdate = now.date()
But had no success with it. The error that comes up on the command line when running "python manage.py check" is:
AttributeError: 'DateTimeField' object has no attribute 'date'
Is there a way to do this?
def now():
return datetime.today().date()
this will return the current date
Calendar.objects.filter(datetimefield__date='your_date')
I'm not sure that I understand your question correctly, but you can get the date from DateTimeFields like this.
from django.db import models
from django.utils import timezone
class Calendar(models.Model):
now = models.DateTimeField(default=timezone.now)
twenty_four_hours_later = models.DateTimeField(
default=lambda: timezone.now() + timezone.timedelta(hours=24))
c = Calendar()
print(c.now.date())
print(c.twenty_four_hours_later.date())
I'm making a website using django.
class Member(models.Model):
...
end_date = models.DateField(blank=True, default=(datetime.now() + timedelta(days=30)))
Membership_status = models.IntegerField(blank=True, null=True, default=1) # 1 = active, 0=deactivate, 2=refund
What I want to do is comparing the end_date field to today.date every 1.a.m. and if today's day < end_date, Membership_status field is changed to 0 automatically.
I heard I should use django-kronos(https://github.com/jgorset/django-kronos).
But I can't understand the using method.
Is there anyone can tell me details how I implement what I want?
Any help will be very helpful to me, thanks!
First of all, this is not an answer to your original query, but merely a suggestion for your future,
Never pass a function call into your field defaults. If you did, the function would be evaluated at the time of your migrations. If you look into the migration files you can see for sure. Instead wrap it in a function and pass that as a callable.
Eg:
from django.utils import timezone
def TODAY():
return timezone.now().date()
def NEXT_MONTH_DAY():
return TODAY() + timedelta(days=30)
Now, in your models,
class Member(models.Model):
...
end_date = models.DateField(blank=True, default=NEXT_MONTH_DAY)
This way the function NEXT_MONTH_DAY is called whenever an instance of Member is created.
EDIT:
For your original query, I haven't tested the code, but I suppose you are looking for maybe something like this,
import kronos
#kronos.register('0 1 * * *')
def the_task():
for member in Member.objects.all():
if TODAY() == member.end_date:
member.Membership_status = 0
member.save()
Given some code like this:
# coding: utf-8
import datetime
from django.db import models
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
class Premium(models.Model):
"""Access to Premium Features™®."""
end = models.DateField()
user = models.ForeignKey(User)
site = models.ForeignKey(Site)
def get_ending_premiums():
"""Get a queryset of all Premiums for which a user has none following."""
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
future_premiums = Premium.objects.filter(end__gt=tomorrow).values('user', 'site')
return Premium.objects.filter(end=tomorrow).exclude(
# Would love if something like this actually worked...
user_and_site__in=future_premiums,
)
How can I complete get_ending_premiums()? One of the key things is I want Premiums only when there isn't another one that ends later, but on a per-site basis. So if a user has another Premium on groceries.com, the one about to end tomorrow doesn't get returned, but if they don't have another Premium on officesupplies.com, that one does get returned.
(Note the line with with the comments before it doesn’t actually work... that’s the part I need to complete.)
I can work out how to do this outside the ORM but I’d really prefer an ORM solution, as we’re planning on switching database vendors in a few months, so I’m trying to avoid raw SQL as much as possible.
Here’s a test for the behavior I’d like to get:
class PremiumTest(TestCase):
def test_gets_ending_premiums(self):
today = date(2020, 6, 5)
tomorrow = today + timedelta(days=1)
next_year = today + timedelta(days=366)
groceries = Site.objects.create(domain='groceries.com')
catvids = Site.objects.create(domain='catvids.com')
dave = User.objects.create_user('dave')
sally = User.objects.create_user('sally')
Premium.objects.create(user=dave, site=groceries, end=tomorrow)
Premium.objects.create(user=dave, site=groceries, end=next_year)
Premium.objects.create(user=dave, site=catvids, end=tomorrow)
Premium.objects.create(user=sally, site=groceries, end=tomorrow)
Premium.objects.create(user=sally, site=catvids, end=tomorrow)
Premium.objects.create(user=sally, site=catvids, end=next_year)
ending_premiums = get_ending_premiums(today)
ending = set((p.user, p.site) for p in ending_premiums)
self.assertNotIn((dave, groceries), ending)
self.assertIn((dave, catvids), ending)
self.assertIn((sally, groceries), ending)
self.assertNotIn((sally, catvids), ending)
self.assertEqual(2, len(ending_premiums))
I've come up with this... It's got some raw SQL but it still returns a QuerySet with normal QuerySet methods (although it uses the apparently deprecated QuerySet.extra() method)
def get_ending_premiums(day=None):
"""Get a queryset of Premiums for which a user has none following."""
if day is None:
day = date.today()
tomorrow = day + timedelta(days=1)
ending_premiums = Premium.objects.filter(
end=tomorrow,
).extra(
where=['NOT EXISTS (SELECT NULL FROM premium_premium child where premium_premium.site_id = site_id AND premium_premium.user_id = user_id AND end > %s )'],
params=[tomorrow],
)
return ending_premiums
Still wondering if there isn’t a better way...