Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validating number of ManyToManyField items (with intermediate model) in Django admin

I have some Django models that look something like this (this isn't my exact code but is a simpler example that has the same structure):

class Player(models.Model):
    # Some fields here.
    pass

class Team(models.Model):
    players = models.ManyToManyField(Player, through='TeamPlayer')

class TeamPlayer(models.Model):
    team = models.ForeignKey(Team)
    player = models.ForeignKey(Player)
    some_other_field = models.BooleanField()

I'm using the through mechanism because I have extra columns on my link table.

My admin classes look something like this (note that I am using an inline admin to add the players):

class TeamPlayerInline(admin.TabularInline):
    model = TeamPlayer
    max_num = 11
    extra = 11

class TeamAdmin(admin.ModelAdmin):
    inlines = [TeamPlayerInline]

admin.site.register(Team, TeamAdmin)

The Question: My problem is that in my admin I would like to validate that a team has exactly 11 players. Any fewer should result in an error. How can I do this?

These are the things that I have tried and the reasons why they didn't work:

  1. Validate the number of players in the clean method of the Team model. This doesn't work because the players haven't been saved yet, so for a new object there are always zero players.

  2. Validate the number in the clean_players method of a ModelForm used by the TeamAdmin. This method never gets called. Similar methods for other non-ManyToMany fields do get called.

  3. Validate the number in the clean method of the aforementioned ModelForm. This method gets called but the self.cleaned_data dictionary does not have an entry for 'players'.

Any ideas how I can achieve this type of validation? I'm far from being a Django expert so don't assume that I've necessarily done everything that should be obvious.

like image 886
Dan Dyer Avatar asked Nov 26 '25 06:11

Dan Dyer


1 Answers

You need to set the formset on the TeamPlayerInline. And override the clean method in that form set. For example:

from django.forms.models import BaseInlineFormSet

class TeamPlayerFormset(BaseInlineFormSet):
    def clean(self):
        """Check that exactly 11 players are entered."""
        super(TeamPlayerFormset, self).clean()

        if any(self.errors):
            return

        count = 0
        for cleaned_data in self.cleaned_data:
            if cleaned_data and not cleaned_data.get('DELETE', False):
                count += 1
        if count != 11:
            raise forms.ValidationError('You must enter 11 team players.')


class TeamPlayerInline(admin.TabularInline):
    model = TeamPlayer
    max_num = 11
    extra = 11
    formset = TeamPlayerFormset


class TeamAdmin(admin.ModelAdmin):
    inlines = [TeamPlayerInline]
like image 105
jproffitt Avatar answered Nov 28 '25 19:11

jproffitt



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!