views:

81

answers:

2

I have the following model in Django:

class Bout (models.Model):
    fighter_1 = models.ForeignKey(Fighter, related_name="bout_fighter_1")
    fighter_2 = models.ForeignKey(Fighter, related_name="bout_fighter_2")
    winner = models.ForeignKey(Fighter, related_name="bout_winner", 
        blank=True, null=True, help_text='Leave blank for draw.') 
    date = models.DateField()
    cancelled = models.BooleanField()

I would like to "idiot-proof" the administration for its records. Incidently, I want to create three rules:

  1. Fighter 1 is not the same as fighter 2 (which is only good for a monty python skit).

  2. Winner should be in the bout (i.e., either Fighter 1 or Fighter 2)

  3. The winner can't be set before the match takes place. (After all, this isn't WWE.)

All three of these rules require checking one field against another field in the same record. Is it possible to do this in django, either using native django methods or resorting to python?

A: 

Short answer: you can achieve this in Django using "native django methods". I am not sure what exactly you mean by "native Django methods"; I am assuming that you mean making calls to the Django API.

There are a couple of ways to go about this. If your users can only create Bout instances using a form that you provide then the form's validation methods can test for the conditions you mentioned. For e.g.:

class BoutForm(forms.ModelForm):
    class Meta:
        model = Bout

    def clean(self):
        fighter_1 = self.cleaned_data.get('fighter_1')
        fighter_2 = self.cleaned_data.get('fighter_2')
        winner = self.cleaned_data.get('winner')  
        date = self.cleaned_data.get('date')

        if not (fighter_1 and fighter_2 and (fighter_1.id != fighter_2)):
            raise forms.ValidationError("Both fighters cannot be the same")

        if not (winner and (winner.id == fighter_1.id or winner.id == fighter_2.id)):
            raise forms.ValidationError("Winner is not in the fight")

        if not (date and date < datetime.today()):
            raise forms.ValidationError("Winner is not in the fight")

        return self.cleaned_data

The above snippet is incomplete. You can tweak it to meet your needs. Also take a look at Django's new fangled form validators.

If on the other hand your users can create instances using the API (say, by instantiating the Bout class in their programs) then you'll have to do the validation by overriding the save() method of the Bout class.

Manoj Govindan
Cheers for the response. At the moment I'm only concerned about input from the Admin.Regarding your response, it is very similar to the answer I came up with while speaking with a friend, though slightly longer. In particular, in your method you first get the data and copy it to local variables, and then you return cleaned_data.Is there any reason you chose not to directly test self.fighter_1, self.fighter_2, self.winer, and self.date?
Brian Kessler
I was following a Django idiom of picking up data from the `cleaned_data` attribute rather than directly using `self.field_name`. The documentation recommends this for good reasons: http://docs.djangoproject.com/en/dev/topics/forms/#processing-the-data-from-a-form
Manoj Govindan
Cheers for the explanatory link. :-)
Brian Kessler
A: 

While Manoj Govindan's answer looks very good, I also came up with my own solution... I'll include it here in case anyone finds my slightly shorter solution preferable:

def clean(self):
    if self.fighter_1 == self.fighter_2:
        raise ValidationError('Fighter 1 can not be Fighter 2.')
    if (self.winner != self.fighter_1) and (self.winner != self.fighter_2):
        raise ValidationError('Winner must be in the bout.')
    if (self.date >= datetime.date.today()) and (self.winner):
        raise ValidationError('Winner can not be set before match.')
Brian Kessler
I need to correct myself. Both the second and third validation tests should start:if (self.winner) and... so that the rest of these tests don't get executed if no winner is selected.(Yes, I included (self.winner) already in the third test, but it doesn't work properly when I put it there. When it is there, it essentially turns the winner field into a required field.
Brian Kessler