2

I've got a simple Django model for a Group that has a list of Contacts. Each group must have either a Primary contact ForeignKey or a All contacts BooleanField selected but not both and not none.

class Group(models.Model):
    contacts = models.ManyToManyField(Contact)
    contact_primary = models.ForeignKey(Contact, related_name="contact_primary", null=True)
    all_contacts = models.BooleanField(null=True)

How can I ensure that:

  1. The model can't be saved unless either contact_primary or all_contacts (but not both) is set. I guess that would be by implementing the Group.save() method? Or should it be the Group.clean() method??

  2. In Django Admin either disable the other field when one is selected or at least provide a meaningful error message if both or none are set and the admin tries to save it?

Thanks!

KeepLearning
  • 349
  • 2
  • 3
  • 10

1 Answers1

2

The easiest way would be to override the save() method of your model:

class Group(models.Model):
    contacts = models.ManyToManyField(Contact)
    contact_primary = models.ForeignKey(Contact, related_name="contact_primary", blank=True, null=True)
    all_contacts = models.BooleanField(blank=True, null=True)

    def save(self, *args, **kwargs):
        if self.contact_primary is not None and self.all_contacts is not None:
            raise Exception("some error message here")  
        if self.contact_primary is None and self.all_contacts is None:
            raise Exception("some other error message here")  

        return super().save()

Notice that I added blank=True to your model fields. This is necessary if you want to insert null columns through the admin or through a form.

Update

If you want to raise a ValidationError, you must raise it from the model's clean() method. Otherwise, you will give a 500 error to the client, rather than an error message.

KeepLearning
  • 349
  • 2
  • 3
  • 10
Lord Elrond
  • 13,430
  • 7
  • 40
  • 80
  • 1
    Actually I now did the same but in `clean()` method and raising `ValidationError()` which seems to works well in the admin interface as well. Feel free to update your answer and I'll accept it. – KeepLearning Oct 17 '19 at 00:24
  • @KeepLearning The only downside to that is if you somehow end up saving a model *outside* of your admin/form, then the `clean` wouldn't apply. – Lord Elrond Oct 17 '19 at 00:25
  • Ah, good point. Maybe do it in both clean() and save() then? Or call `clean()` from `safe()` again just in case? – KeepLearning Oct 17 '19 at 00:34
  • @KeepLearning Why do you want to call it from `clean`? You can raise a `ValidationError` from your `save` method. The only scenario that would be overlooked when using `save` is if you ran a `bulk_insert`, in that case you would need to catch it in the `clean`. – Lord Elrond Oct 17 '19 at 00:46
  • When I raise ValidationError from `save()` in Admin I get a debug page with Backtrace. When I raise it from `clean()` I get a nice red message box. – KeepLearning Oct 17 '19 at 01:21
  • @KeepLearning It looks like you were right in going with `clean`. [this post](https://stackoverflow.com/questions/8771029/raise-a-validation-error-in-a-models-save-method-in-django) says you have to raise the error from your form, instead of from the model. – Lord Elrond Oct 17 '19 at 01:30