views:

591

answers:

2

Suppose my models.py is like so:

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()

I want only one of my Character instances to have is_the_chosen_one == True and all others to have is_the_chosen_one == False . How can I best ensure this uniqueness constraint is respected?

Top marks to answers that take into account the importance of respecting the constraint at the database, model and (admin) form levels!

+4  A: 

Whenever I've needed to accomplish this task, what I've done is override the save method for the model and have it check if any other model has the flag already set (and turn it off).

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()

    def save(self):
        if self.is_the_chosen_one:
            try:
                temp = Character.objects.get(is_the_chosen_one=True)
                if self != temp:
                    temp.is_the_chosen_one = False
                    temp.save()
            except Character.DoesNotExist:
                pass
         super(Character, self).save()
Adam
A: 
class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()

    def save(self, *args, **kwargs):
        if self.is_the_chosen_one:
            qs = Character.objects.filter(is_the_chosen_one=True)
            if self.pk:
                qs = qs.exclude(pk=self.pk)
            if qs.count() != 0:
                # choose ONE of the next two lines
                self.is_the_chosen_one = False # keep the existing "chosen one"
                #qs.update(is_the_chosen_one=False) # make this obj "the chosen one"
        super(Character, self).save(*args, **kwargs)

class CharacterForm(forms.ModelForm):
    class Meta:
        model = Character

    # if you want to use the new obj as the chosen one and remove others, then
    # be sure to use the second line in the model save() above and DO NOT USE
    # the following clean method
    def clean_is_the_chosen_one(self):
        chosen = self.cleaned_data.get('is_the_chosen_one')
        if chosen:
            qs = Character.objects.filter(is_the_chosen_one=True)
            if self.instance.pk:
                qs = qs.exclude(pk=self.instance.pk)
            if qs.count() != 0:
                raise forms.ValidationError("A Chosen One already exists! You will pay for your insolence!")
        return chosen

You can use the above form for admin as well, just use

class CharacterAdmin(admin.ModelAdmin):
    form = CharacterForm
admin.site.register(Character, CharacterAdmin)
shadfc