DRY KISS: django-stdfields
Because Django doesn't really have a model field to track a duration and I always end up inventing something new to make working with choices
on a field easier, I set out to finally build a solution that works for me: django-stdfields.
Duration in Django
When you need to record a duration in Django, you're out of luck using
a standard Django model field. The DateField
, DateTimeField
and TimeField
do exactly what they are supposed to do: record a date, date and time or only
the time.
Instead, you could use the model field fields.MinutesField
from
django-stdfields. It's actually a simple extension of Django's
PositiveIntegerField
accompanied by a forms.MinutesField
that will allow
input in a format most users can understand. Instead of other existing
third-party solutions, the MinutesField does exactly what its name suggests:
track minutes, nothing more.
Users can input 8:30, resulting in 510 minutes. They can input
8.5 for the same result. Or even 8,5. Any integer value
is interpreted as hours spent, any decimal value is interpreted as hours and
part of an hour spent and anything of the form hh:mm
is interpreted as the
number of hours and exact number of minutes spent. It's basically the way you
enter time spent on a task in Basecamp.
The tricky part is displaying those minutes back to your users. It's
far easier to interpret 8:30 than 510.
Simple solution: use the included minutes
filter:
{% load stdfieldstags %}
...
{{ task.time_spent|minutes }}
...
When task.time_spent
equals 510, this template will print 8:30.
Choices in Django
The choices
argument for fields is great when you're
dealing with a fixed number of possible values. The thing is... I always
need to start messing with the constants further down the line. And then
I need to get the label of the constant. I hate it because it gets
gradually harder to find out what the hell you were doing two weeks
later. The solution: enumerations.
Django-stdfields contains an easy way to create enums:
from django.db import models
from stdfields.models import Enum, EnumValue, EnumCharField
class CardinalDirection(Enum):
NORTH = EnumValue('N', 'North')
EAST = EnumValue('E', 'East')
SOUTH = EnumValue('S', 'South')
WEST = EnumValue('W', 'West')
class Wind(models.Model):
direction = EnumCharField(enum=CardinalDirection,
max_length=CardinalDirection.max_length())
force = models.IntegerField()
You could still use a regular models.CharField
and the choices
argument.
It's basically the same thing the EnumCharField
and EnumIntegerField
do
behind the scenes.
But thanks to the Enumeration
base class, things get easier to manage when
you have to change stuff around (note: all fields can be used with South).
from django.db import models
from stdfields.models import Enum, EnumValue, EnumCharField
class CardinalDirection(Enum):
NORTH = EnumValue('N', 'North')
NORTHEAST = EnumValue('NE', 'Northeast')
EAST = EnumValue('E', 'East')
SOUTHEAST = EnumValue('SE', 'Southeast')
SOUTH = EnumValue('S', 'South')
SOUTHWEST = EnumValue('SW', 'Southwest')
WEST = EnumValue('W', 'West')
NORTHWEST = EnumValue('NW', 'Northwest')
class Wind(models.Model):
direction = EnumCharField(enum=CardinalDirection,
max_length=CardinalDirection.max_length())
force = models.IntegerField()
We just added northeast, southeast, southwest and northwest to the
possible values. Did you notice that this also changes the maximum
length for the direction
field of Wind
?
Well, it doesn't matter: we're using the max_length
method
provided by Enumeration
which will automatically determine
the maximum possible length of the values -- in this case 2. Just
don't forget to create and run a schema migration with South.
But Enum
might not work entirely as expected: when it's constructed, all
contained EnumValue
fields will be turned into regular fields:
>>> CardinalDirection.NORTH == 'N'
True
>>> CardinalDirection.NORTH_display == 'North'
True
>>> CardinalDirection.all()
[('N', 'North'), ('NE', 'Northeast'), ('E', 'East'), ('SE', 'Southeast'),
('S', 'South'), ('SW', 'Southwest'), ('W', 'West'), ('NW', 'Northwest')]
>>> CardinalDirection.max_length()
2
>>> CardinalDirection.as_display(CardinalDirection.NORTH)
'North'
That's the gist of django-stdfields. It will save me a lot of time and I hope it does the same for you. Get it from Pypi or Bitbucket.
Are you a Java developer getting started with Django? Then you'll surely love my upcoming Django for Java Developers ebook! Find out how to manage dependencies, start, build and deploy a Django project from the perspective of a Java web developer.