ensuring DateField validator above a certain age in django Models - django

I am fairly new to django and I am trying to constrain a django model field such that the age less than 25 years is shown as an error (using the datefield). So, I have the following model:
dob = models.DateField(blank=False, )
I am wondering how one can apply the above constraint in a django model.
Thanks.

I've just come across the same problem, and here is my solution for a custom field validator that checks for a minimum age value:
from django.utils.deconstruct import deconstructible
from django.utils.translation import ugettext_lazy as _
from django.core.validators import BaseValidator
from datetime import date
def calculate_age(born):
today = date.today()
return today.year - born.year - \
((today.month, today.day) < (born.month, born.day))
#deconstructible
class MinAgeValidator(BaseValidator):
message = _("Age must be at least %(limit_value)d.")
code = 'min_age'
def compare(self, a, b):
return calculate_age(a) < b
The calculate_age snippet is from this post.
Usage:
class MyModel(models.Model):
date_of_birth = models.DateField(validators=[MinAgeValidator(18)])

You need to create custom field validator.
Unfortunately you will need to hardcode the age value inside validator function, since it doesn't allow you to pass any arguments.
Then to calculate age use this snippet to correctly cover leap years.

Related

Django DateTimeRangeField: default=[timezone.now()]-[timezone.now()]+[10YEARS]

I want an "active_in" attribute as a timeframe. I assume that the DBMS is optimized for the postgresql tsrange field, and as such it is preferable to utilize the DateTimeRangeField rather than 2 separate fields for start_date and end_date.
Doing this I desire a default value for the field.
active_in = models.DateTimeRangeField(default=timezone.now+'-'+timezone.now+10YEARS)
Is my assumption about the DateTimeRangeField performance true?
Is there a smart solution be it creating a new; function,class or
simply manipulating the 2nd last digit?
My possible solutions:
Code using string manipulation:
active_in = models.DateTimeRangeField(default=timezone.now+'-'+timezone.now[:-2]+'30')
Code using custom function object: (adjusted from here: https://stackoverflow.com/a/27491426/7458018)
def today_years_ahead():
return timezone.now + '-' timezone.now() + timezone.timedelta(years=10)
class MyModel(models.Model):
...
active_in = models.DateTimeRangeField(default=today_years_ahead)
There's no need for string manipulation, as the documented Python type for this field is DateTimeTZRange.
I can't say I've ever used this field before, but something like this should work:
from psycopg2.extras import DateTimeTZRange
from django.utils import timezone
from datetime import timedelta
def next_ten_years():
now = timezone.now()
# use a more accurate version of "10 years" if you need it
return DateTimeTZRange(now, now + timedelta(days=3652))
class MyModel(models.Model):
...
active_in = models.DateTimeRangeField(default=next_ten_years)

Django model field validators: coding style

Could you have a look at the code example below:
from datetime import date
from rest_framework import serializers
def validate_age(date_of_birth):
today = date.today()
age = today.year - date_of_birth.year - ((today.month, today.day) < (date_of_birth.month, date_of_birth.day))
if (not(20 < age < 30)):
raise serializers.ValidationError("You are no eligible for the job")
return dob
class EligibilitySerializer(serializers.Serializer):
email = serializers.EmailField()
name = serializers.CharField(max_length=200)
date_of_birth = serializers.DateField(validators=[validate_age])
Suppose, this validate_age will not be used anywhere else. In this case a reasonable choice would be to incapsulate it inside the class as a private method.
Is it possible in django somehow? If it is not possible, maybe a notation should be used? Something like this: _validate_age(date_of_birth). So that programmers should know that this function definitely is not for reusing, not for importing.

Using Django ArrayField to Store Dates and Value

I'm currently having a hard time wrapping my head around Django's Array Field. What i'm hoping to do is have an array that looks something like this:
Price(close=[
[1/1/2018, 3.00],
[1/2/2018, 1.00],
])
It's basically an array that stores a date followed by a corresponding value tied to that date. However, thus far my model looks like this:
class Price(models.Model):
close = ArrayField(
models.DecimalField(max_digits=10, decimal_places=4),
size=365,
)
I am not certain how to create an array with two different types of fields, one DateTime, the other decimal. Any help would be much appreciated.
You can't mix types stored in the ArrayField. [1] I recommend you to change model schema (aka Database normalization [2]).
This is my suggestion:
from django.db import models
class Price(models.Model):
pass
class PriceItem(models.Model):
datetime = models.DateTimeField()
ammount = models.DecimalField(max_digits=10, decimal_places=4)
price = models.ForeignKey(Price, on_delete=models.CASCADE)
[1] https://stackoverflow.com/a/8168017/752142
[2] https://en.wikipedia.org/wiki/Database_normalization
It depends on how important it is to the model.
postgresql provides composite types
The generous contributor of psycopg2 (django's posgresql driver) is supporting it,
define this type in postgresql:
CREATE TYPE date_price AS (
start date,
float8 price
);
and using the methods described here to implement CompositeField
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.db import connection
from psycopg2.extras import register_composite
# register the composite
register_composite('date_price', connection.cursor().cursor)
# CompositeField implementation here . . . . . .
class DatePriceField(CompositeField):
'''
DatePriceField specifics
'''
pass
class Price(models.Model):
close = ArrayField(base_field=DatePriceField(), size=365,)
I am going to follow this route and update soon.

Correct way to store multi-select results in Django model

all:
What is the correct Model fieldtype to use with a CheckboxSelectMultiple widget for static data? I am receiving validation errors and feel I'm missing something simple.
The app is a simple Django 1.6 app in which a Campground object can have multiple eligible_days (e.g. Site #123 may be available on Monday & Tuesday, while Site #456 is available on Weds-Friday).
Because it's static data and I've ready that a ManyToManyField has unnecessary DB overhead, I'm trying to do this with choices defined inside the model, but when I try to save I get the validation error Select a valid choice. [u'5', u'6'] is not one of the available choices. every time.
Q1: Do I have to override/subclass a field to support this?
Q2: Do I need a custom validation method to support this?
Q3: Am I making things unnecessarily hard on myself by avoiding ManyToManyField?
Thank you for your help!
/m
models.py
class CampgroundQuery(models.Model):
SUN = 0
MON = 1
TUE = 2
WED = 3
THU = 4
FRI = 5
SAT = 6
DAYS_OF_WEEK_CHOICES = (
(SUN, 'Sunday'),
(MON, 'Monday'),
(TUE, 'Tuesday'),
(WED, 'Wednesday'),
(THU, 'Thursday'),
(FRI, 'Friday'),
(SAT, 'Saturday'),
)
# loads choices from defined list
eligible_days = models.CharField(max_length=14,choices=DAYS_OF_WEEK_CHOICES,
blank=False, default='Saturday')
campground_id = models.SmallIntegerField()
stay_length = models.SmallIntegerField()
start_date = models.DateField()
end_date = models.DateField()
admin.py
from django.contrib import admin
from searcher.models import CampgroundQuery
from forms import CampgroundQueryAdminForm
class CampgroundQueryAdmin(admin.ModelAdmin):
form = CampgroundQueryAdminForm
admin.site.register(CampgroundQuery, CampgroundQueryAdmin)
forms.py
from django import forms
from django.contrib import admin
from searcher.models import CampgroundQuery
class CampgroundQueryAdminForm(forms.ModelForm):
class Meta:
model = CampgroundQuery
widgets = {
'eligible_days': forms.widgets.CheckboxSelectMultiple
}
I know this is an old question, but for those who wish to avoid using a ManyToManyField, there is a package to do this, django-multiselectfield, which is quick and easy to implement.
forms.py
from multiselectfield import MultiSelectFormField
class MyForm(forms.ModelForm):
my_field = MultiSelectFormField(choices=MyModel.MY_CHOICES)
models.py
from multiselectfield import MultiSelectField
class MyModel(models.Model):
MY_CHOICES = (
('a', "A good choice"),
...
('f', "A bad choice"),
)
my_field = MultiSelectField(choices=MY_CHOICES, max_length=11)
And that's it! It stores the keys of MY_CHOICES in a comma-separated string. Easy!
A ManyToManyField is the correct choice.
In theory you could create a compact representation, like a string field containing representations like "M,W,Th" or an integer that you'll set and interpret as seven binary bits, but that's all an immense amount of trouble to work with. ManyToManyFields are fine.

How to set a Django model field's default value to a function call / callable (e.g., a date relative to the time of model object creation)

EDITED:
How can I set a Django field's default to a function that gets evaluated each time a new model object gets created?
I want to do something like the following, except that in this code, the code gets evaluated once and sets the default to the same date for each model object created, rather than evaluating the code each time a model object gets created:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
ORIGINAL:
I want to create a default value for a function parameter such that it is dynamic and gets called and set each time the function is called. How can I do that? e.g.,
from datetime import datetime
def mydate(date=datetime.now()):
print date
mydate()
mydate() # prints the same thing as the previous call; but I want it to be a newer value
Specifically, I want to do it in Django, e.g.,
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
The question is misguided. When creating a model field in Django, you are not defining a function, so function default values are irrelevant:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
This last line is not defining a function; it is invoking a function to create a field in the class.
In this case datetime.now() + timedelta(days=1) will be evaluated once, and stored as the default value.
PRE Django 1.7
Django [lets you pass a callable as the default][1], and it will invoke it each time, just as you want:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=lambda: datetime.now() + timedelta(days=1))
Django 1.7+
Please note that since Django 1.7, usage of lambda as default value is not recommended (c.f. #stvnw comment). The proper way to do this is to declare a function before the field and use it as a callable in default_value named arg:
from datetime import datetime, timedelta
# default to 1 day from now
def get_default_my_date():
return datetime.now() + timedelta(days=1)
class MyModel(models.Model):
my_date = models.DateTimeField(default=get_default_my_date)
More information in the #simanas answer below
[1]: https://docs.djangoproject.com/en/dev/ref/models/fields/#default
Doing this default=datetime.now()+timedelta(days=1) is absolutely wrong!
It gets evaluated when you start your instance of django. If you are under apache it will probably work, because on some configurations apache revokes your django application on every request, but still you can find you self some day looking through out your code and trying to figure out why this get calculated not as you expect.
The right way of doing this is to pass a callable object to default argument. It can be a datetime.today function or your custom function. Then it gets evaluated every time you request a new default value.
def get_deadline():
return datetime.today() + timedelta(days=20)
class Bill(models.Model):
name = models.CharField(max_length=50)
customer = models.ForeignKey(User, related_name='bills')
date = models.DateField(default=datetime.today)
deadline = models.DateField(default=get_deadline)
There's an important distinction between the following two DateTimeField constructors:
my_date = models.DateTimeField(auto_now=True)
my_date = models.DateTimeField(auto_now_add=True)
If you use auto_now_add=True in the constructor, the datetime referenced by my_date is "immutable" (only set once when the row is inserted to the table).
With auto_now=True, however, the datetime value will be updated every time the object is saved.
This was definitely a gotcha for me at one point. For reference, the docs are here:
https://docs.djangoproject.com/en/dev/ref/models/fields/#datetimefield
Sometimes you may need to access model data after creating a new user model.
Here is how I generate a token for each new user profile using the first 4 characters of their username:
from django.dispatch import receiver
class Profile(models.Model):
auth_token = models.CharField(max_length=13, default=None, null=True, blank=True)
#receiver(post_save, sender=User) # this is called after a User model is saved.
def create_user_profile(sender, instance, created, **kwargs):
if created: # only run the following if the profile is new
new_profile = Profile.objects.create(user=instance)
new_profile.create_auth_token()
new_profile.save()
def create_auth_token(self):
import random, string
auth = self.user.username[:4] # get first 4 characters in user name
self.auth_token = auth + ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(random.randint(3, 5)))
You can't do that directly; the default value is evaluated when the function definition is evaluated. But there are two ways around it.
First, you can create (and then call) a new function each time.
Or, more simply, just use a special value to mark the default. For example:
from datetime import datetime
def mydate(date=None):
if date is None:
date = datetime.now()
print date
If None is a perfectly reasonable parameter value, and there's no other reasonable value you could use in its place, you can just create a new value that's definitely outside the domain of your function:
from datetime import datetime
class _MyDateDummyDefault(object):
pass
def mydate(date=_MyDateDummyDefault):
if date is _MyDateDummyDefault:
date = datetime.now()
print date
del _MyDateDummyDefault
In some rare cases, you're writing meta-code that really does need to be able to take absolutely anything, even, say, mydate.func_defaults[0]. In that case, you have to do something like this:
def mydate(*args, **kw):
if 'date' in kw:
date = kw['date']
elif len(args):
date = args[0]
else:
date = datetime.now()
print date
Pass the function in as a parameter instead of passing in the result of the function call.
That is, instead of this:
def myfunc(date=datetime.now()):
print date
Try this:
def myfunc(date=datetime.now):
print date()