views:

1014

answers:

4

Django has various numeric fields available for use in models, e.g. DecimalField and PositiveIntegerField. Although the former can be restricted to the number of decimal places stored and the overall number of characters stored, is there any way to restrict it to storing only numbers within a certain range, e.g. 0.0-5.0 ?

Failing that, is there any way to restrict a PositiveIntegerField to only store, for instance, numbers up to 50?

+7  A: 

There are two ways to do this. One is to use form validation to never let any number over 50 be entered by a user. Form validation docs.

If there is no user involved in the process, or you're not using a form to enter data, then you'll have to override the model's save method to throw an exception or limit the data going into the field.

tghw
Beat me to the punch on both recommendations!
David Berger
You can use a form for validating non-human input, too. It works great to populate the Form as a all-around validation technique.
S.Lott
After thinking on this, I'm quite sure I don't want to put the validation in a form. The question of which range of numbers is acceptable is as much a part of the model as is the question of which kind of number is acceptable.I don't want to have to tell every form through which the model is editable, just which range of numbers to accept. This would violate DRY, and besides, it's just plain inappropriate.So I'm going to look into overriding the model's save method, or perhaps creating a custom model field type - unless I can find an *even* better way :)
sampablokuper
tghw, you said I could "override the model's save method to throw an exception or limit the data going into the field." How would I - from within the model definition's overriden save() method - make it so that if the number entered is outside a given range, the user receives a validation error much as if she had entered character data into a numeric field? I.e. is there some way I can do this that will work regardless of whether the user is editing via the admin or via some other form? I don't want to just limit the data going into the field without telling the user what's going on :) Thanks!
sampablokuper
+9  A: 

You could also create a custom model field type - see http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

In this case, you could 'inherit' from the built-in IntegerField and override its validation logic.

The more I think about this, I realize how useful this would be for many Django apps. Perhaps a IntegerRangeField type could be submitted as a patch for the Django devs to consider adding to trunk.

This is working for me:

from django.db import models

class IntegerRangeField(models.IntegerField):
    def __init__(self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs):
        self.min_value, self.max_value = min_value, max_value
        models.IntegerField.__init__(self, verbose_name, name, **kwargs)
    def formfield(self, **kwargs):
        defaults = {'min_value': self.min_value, 'max_value':self.max_value}
        defaults.update(kwargs)
        return super(IntegerRangeField, self).formfield(**defaults)

Then in your model class, you would use it like this (field being the module where you put the above code):

size = fields.IntegerRangeField(min_value=1, max_value=50)

OR for a range of negative and positive (like an oscillator range):

size = fields.IntegerRangeField(min_value=-100, max_value=100)

What would be really cool is if it could be called with the range operator like this:

size = fields.IntegerRangeField(range(1:50))

But, that would require a lot more code since since you can specify a 'skip' parameter - range(1:50:2) - Interesting idea though...

NathanD
+1 (but I'm out of votes for today!)
a paid nerd
I think that as long as ticket #6845 remains open, this is the best solution.http://code.djangoproject.com/ticket/6845
sampablokuper
A: 

You could create a pre-save signal:

http://docs.djangoproject.com/en/dev/ref/signals/#django.db.models.signals.pre_save

igorgue
A: 

I had this very same problem; here was my solution:

SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
chrishaum