Enforce Django Admin to correctly call .update() instead of .save() to avoid triggering checks meant for object creation

How do we enforce Django Admin to correctly call .update() instead of .save() to avoid triggering checks meant for object creation?

This is the models.py:

class BinaryChoice():
    # field definitions
    ...

    def save(self, *args, **kwargs):
        # check if binary
        if self.question.qtype == 2:
            if self.question.choices.count() < 2:
                super(BinaryChoice, self).save(*args, **kwargs)
            else:
                raise Exception("Binary question type can contain at most two choices.")
        else:
            super(BinaryChoice, self).save(*args, **kwargs)

This passes the test, no surprises:

class SurveyTest(TestCase):

    def test_binary_choice_create(self):
        q1 = Question.objects.create(survey=survey, title='Have you got any internship experience?', qtype=Question.BINARY)
        BinaryChoice.objects.create(question=q1, choice="Yes")
        BinaryChoice.objects.create(question=q1, choice="No")
        with self.assertRaises(Exception):
            BinaryChoice.objects.create(question=q1, choice="Unsure / Rather not say")

The .save() correctly checks that there isn’t already 2 binary choices related to the same Question. However, in Django Admin, when using the interface to update the value (anything arbitrary, for example changing the value from “Yes” to “Sure”) and saving it, one would expect the .update() method to be called.

It turns out, according to Django docs and also a relevant thread here, the .save() method is called instead. So now our update operation would fail when there’s already 2 BinaryChoice, even if you intend to update a value in-place using the Django Admin’s default interface.

For completeness sake, this is admin.py:

@admin.register(BinaryChoice)
class BinaryChoiceAdmin(admin.ModelAdmin):
    pass

Answer

Instead of trying to patch the ModelAdmin why don’t you simply fix your save method? Simply check if the object already has a pk or not before saving:

class BinaryChoice():
    # field definitions
    ...

    def save(self, *args, **kwargs):
        # check if binary
        # Here ↓
        if not self.pk and self.question.qtype == 2:
            if self.question.choices.count() < 2:
                super(BinaryChoice, self).save(*args, **kwargs)
            else:
                raise Exception("Binary question type can contain at most two choices.")
        else:
            super(BinaryChoice, self).save(*args, **kwargs)