Hi, I have a model with a number of fields that use Django's 'choices' attribute, to limit the allowed values of those fields to a pre-defined list. I assumed that the Model system would validate my fields when I create or update them, to ensure that invalid choices weren't being assigned to fields. However, it seems that this doesn't happen. I'm aware that such validation occurs at the form layer, but I'd really like it to occur at the model layer as well.
I'm very new to Django (and Python) - I only really started using it about two weeks ago. I haven't even started working with templates yet, as I want to understand the model system and to get my models stable before moving into the front-end. I found http://code.djangoproject.com/ticket/6845 (Model validation and its propagation to ModelForms), so I know that people are working on this for future versions of Django. It will be great when model validation is built-in for future versions of Django. However, I really wanted to see if it was possible to add such validation manually for Django 1.0.x. I wrote a custom __setattr__ handler and exception class, to implement choice validation for my model. I'm putting my code here to get feedback about whether I've taken the right approach here, and whether I've followed the Django conventions (as this is my first piece of Django code), and also for the benefit of anyone else who would like to implement this in their models. Feel free to copy and use this in your own apps: file 'bar/foo/models.py': ----- from django.db import models FOO_CHOICES = ( ('this', 'This'), ('that', 'That'), ('the other', 'The other'), ) class Foo(models.Model): """ Foo is a fuuber model. """ name = models.CharField(max_length=255) foo_type = models.CharField(max_length=255, choices=FOO_CHOICES) def __unicode__(self): return self.name def __setattr__(self, name, value): """ Whenever foo_type is set, it is validated to ensure that it is one of the allowed pre-defined choices. This method fires when a foo object is instantiated (hence no need to duplicate it in __init__), and also whenever foo_type is modified. """ choice_validator = ChoiceValidator() if name == 'foo_type': choice_validator.validate_choice(value, FOO_CHOICES, name) models.Model.__setattr__(self, name, value) class ChoiceValidator: """ Validation that a model's fields match that field's allowed choices. """ def validate_choice(self, choice_provided, choices_available, choice_type): """ Checks that the provided choice matches one of the choices available. If not, an InvalidChoiceError exception is raised. """ is_valid_choice = False valid_choices = [] # choices_available is a tuple of tuples. We convert it into a list, for easy printing in the exception message. for choice in choices_available: valid_choices.append(choice[0]) # And while we're in the loop, we perform the actual matching check. Don't break inside this condition, because we always want all the choices appended to valid_choices. if choice[0] == choice_provided: is_valid_choice = True if not is_valid_choice: raise InvalidChoiceError(choice_provided, valid_choices, choice_type) class InvalidChoiceError(Exception): """ Raised when an attempt is made to set an invalid choice, when setting a model field that has a list of allowed choices. """ def __init__(self, choice_provided, valid_choices, choice_type): self.choice_provided = choice_provided self.valid_choices = valid_choices self.choice_type = choice_type def __str__(self): return 'invalid %s \'%s\', must be one of: \'%s\'' % (self.choice_type, self.choice_provided, '\', \''.join(self.valid_choices)) ----- How to test it: $ python manage.py shell >>> from bar.foo.models import Foo >>> foo1 = Foo(name='Foo Hoo', foo_type='that') (works fine, 'that' is a valid choice) >>> foo2 = Foo(name='Foo Too', foo_type='not this') (prints a traceback, and this message:) InvalidChoiceError: invalid foo_type 'not this', must be one of: 'this', 'that', 'the other' Cheers, Jeremy Epstein http://www.greenash.net.au/ --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django users" group. To post to this group, send email to django-users@googlegroups.com To unsubscribe from this group, send email to django-users+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/django-users?hl=en -~----------~----~----~----~------~----~------~--~---