Django model field validators: coding style - django

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.

Related

How do I use Signals i Django to Calculate Age based on Date entered, if entered at all?

We do not use Date of Birth as a mandatory field during signup. But, in the Model we have an auto-calculate function to arrive at the 'Age' of the user.
So while this works fine when using Django built-in registration mechanism, it fails with custom.
How can Signals be leveraged to check -
if DoB was entered?
if DoB entered, then calculate Age and Populate Age Field?
Thanks in Advance for shring your knowledge.
Regds.
Use this.
if instance.dateBirth:
today = date.today()
rdelta = relativedelta(today,instance.dateBirth)
instance.Age = rdelta.years
if instance.Age > 18: #timedelta(days=18 * 365):
instance.is_adult = True
return(instance.is_adult, instance.Age)
else:
instance.is_adult = False
return (instance.is_adult, instance.Age)
dont forget to update __init__.py and apps.py
Storing the age in a field looks like a bad idea: if the record is not updated somehow, or the signals do not run, then the age can get outdated. For example a person might be stored in the database, but if you never save that object again, after the person's birthday, it will still show the old age.
Therefore it is better not to store this in a field, and use for example a property to determine this when that is necessary, you can define such model with:
from django.utils.timezone import now
class User(models.Model):
# …
date_of_birth = models.DateField()
# no age or is_adult field
#property
def age(self):
today = now().date()
dob = self.date_of_birth
return today.year - dob.year - (today.timetuple()[1:3] < dob.timetuple()[1:3])
#property
def is_adult(self):
return self.age >= 18
This will thus make it possible to access .age and .is_adult for a User object.

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.

ensuring DateField validator above a certain age in django Models

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.

Custom model method in Django

I'm developing a web app in Django that manages chores on a reoccurring weekly basis. These are the models I've come up with so far. Chores need to be able to be assigned multiple weekdays and times. So the chore of laundry could be Sunday # 8:00 am and Wednesday # 5:30 pm. I first want to confirm the models below are the best way to represent this. Secondly, I'm a little confused about model relationships and custom model methods. Since these chores are on a reoccurring basis, I need to be able to check if there has been a CompletedEvent in this week. Since this is row level functionality, that would be a model method correct? Based on the models below, how would I check for this? It has me scratching my head.
models.py:
from django.db import models
from datetime import date
class ChoreManager(models.Manager):
def by_day(self, day_name):
return self.filter(scheduledday__day_name = day_name)
def today(self):
todays_day_name = date.today().strftime('%A')
return self.filter(scheduledday__day_name = todays_day_name)
class Chore(models.Model):
objects = ChoreManager()
name = models.CharField(max_length=50)
notes = models.TextField()
class Meta:
ordering = ['scheduledday__time']
class ScheduledDay(models.Model):
day_name = models.CharField(max_length=8)
time = models.TimeField()
chore = models.ForeignKey('Chore')
class CompletedEvent(models.Model):
date_completed = DateTimeField(auto_now_add=True)
chore = models.ForeignKey('Chore')
Then all you need to do is:
monday_of_week = some_date - datetime.timedetla(days=some_date.weekday())
end_of_week = date + datetime.timedelta(days=7)
chore = Chore.objects.get(name='The chore your looking for')
ScheduledDay.objects.filter(completed_date__gte=monday_of_week,
completed_date__lt=end_of_week,
chore=chore)
A neater (and faster) option is to use Bitmasks!
Think of the days of the week you want a chore to be repeated on as a binary number—a bit for each day. For example, if you wanted a chore repeated every Tuesday, Friday and Sunday then you would get the binary number 1010010 (or 82 in decimal):
S S F T W T M
1 0 1 0 0 1 0 = 1010010
Days are reversed for sake of illustration
And to check if a chore should be done today, simply get the number of that day and do an &:
from datetime import datetime as dt
if dt.today().weekday() & 0b1010100:
print("Do chores!")
Models
Your models.py would look a bit like this:
from django.contrib.auth.models import User
from django.db import models
from django.utils.functional import cached_property
class Chore(models.Model):
name = models.CharField(max_length=128)
notes = models.TextField()
class ChoreUser(models.Model):
chore_detail = models.ForeignKey('ChoreDetail')
user = models.ForeignKey('ChoreDetail')
completed_time = models.DateTimeField(null=True, blank=True)
class ChoreDetail(models.Model):
chore = models.ForeignKey('Chore')
chore_users = models.ManyToManyField('User', through=ChoreUser)
time = models.DateTimeField()
date_begin = models.DateField()
date_end = models.DateField()
schedule = models.IntegerField(help_text="Bitmask of Weekdays")
#cached_property
def happens_today(self):
return bool(dt.today().weekday() & self.weekly_schedule)
This schema has a M2M relationship between a User and a Chore's Schedule. So you can extend your idea, like record the duration of the chore (if you want to), or even have many users participating in the same chore.
And to answer your question, if you'd like to get the list of completed events this week, you could could put this in a Model Manager for ChoreUser:
from datetime import datetime as dt, timedelta
week_start = dt.today() - timedelta(days=dt.weekday())
week_end = week_start + timedelta(days=6)
chore_users = ChoreUser.objects.filter(completed_time__range=(week_start, week_end))
Now you have all the information you need in a single DB call:
user = chore_users[0].user
time = chore_users[0].chore_detail.time
name = chore_users[0].chore_detail.chore.name
happens_today = chore_users[0].chore_detail.happens_today
You could also get all the completed chores for a user easily:
some_user.choreuser_set.filter(completed_time__range=(week_start, week_end))

'datetime.date' object has no attribute 'date'

This code:
import datetime
d_tomorrow = datetime.date.today() + datetime.timedelta(days=1)
class Model(models.Model):
...
timeout = models.DateTimeField(null=True, blank=True, default=d_tomorrow)
...
resuls in this error:
'datetime.date' object has no attribute 'date'
What am I doing wrong?
d_tomorrow is expected, by the Django ORM, to have a date attribute (apparently), but doesn't.
At any rate, you probably want to use a callable for the default date; otherwise, every model's default date will be "tomorrow" relative to the time the model class was initialized, not the time that the model is created. You might try this:
import datetime
def tomorrow():
return datetime.date.today() + datetime.timedelta(days=1)
class Model(models.Model):
timeout = models.DateTimeField(null=True, blank=True, default=tomorrow)
Problem solved:
from datetime import datetime, time, date, timedelta
def tomorrow():
d = date.today() + timedelta(days=1)
t = time(0, 0)
return datetime.combine(d, t)
models.DateTimeField expects the value to be datetime.datetime, not datetime.date
2015 Update:
Arrow makes this all much more straight forward.
Arrow is a Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps. It implements and updates the datetime type, plugging gaps in functionality, and provides an intelligent module API that supports many common creation scenarios. Simply put, it helps you work with dates and times with fewer imports and a lot less code.
Arrow is heavily inspired by moment.js and requests.
I had this problem when using the model from django.contrib.admin. I had two similar models, both with a date field (and both using auto_now_date=True - complete red herring); one worked, one had this error.
Turned out to be
def __unicode__(self):
return self.date
goes BANG, while this
def __unicode__(self):
return u'%s' % self.date
works just fine. Which is kind of obvious after the event, as usual.
This works for me:
import datetime
from datetime import timedelta
tomorrow = datetime.date.today() + timedelta(days=1)
class Test(models.Model):
timeout = models.DateTimeField(db_index=True, default=tomorrow)
Alternatively you could use tomorrow = datetime.datetime.now() + timedelta(days=1)
I tried out your code and it worked just fine. Can you verify that you are not modifying/redefining the import in some way?
Also try this:
import datetime as DT
d_tomorrow = DT.date.today() + DT.timedelta(days=1)
class Model(models.Model):
timeout = models.DateTimeField(null=True, blank=True, default=d_tomorrow)