I’m having some difficulties to add some data validation to a manyTomany field I have in my model. The code I have until now is the following:
#models.py
class Cargo(models.Model):
name = models.CharField(max_length=100)
def __str__(self) -> str:
return self.name
class Committee(models.Model):
MAX_CARGOS = 3
name = models.CharField(max_length=100)
image = models.ImageField(upload_to='committee/', blank=True)
cargos = models.ManyToManyField(Cargo, blank=True, related_name='cargos')
def __str__(self) -> str:
return self.name
#admin.py
class CommitteeCargoInline(admin.TabularInline):
model = Committee.cargos.through
max_num = 5
class CommitteeAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['name']}),
]
inlines = [CommitteeCargoInline]
admin.site.register(Committee, CommitteeAdmin)
admin.site.register(Cargo)
The aim here is to ensure that when admin user clicks on one of the “Save” buttons in admin panel Committee page, it is checked that a Committee has no more than MAX_CARGOS.
At the moment I’m kinda lost because I couldn’t find anything that says what is the correct and standard way to achieve this, I’d like feedback on this point.
I already tried multiple things:
-
Started by trying to add a field validator to my cargos attribute but then I found at Django Docs that field validators are not supported for ManyToMany fields (“ManyToManyField does not support validators.”)
-
Then I tried implement Committe save() method but it also didn’t work:
def save(self, force_insert, force_update, using, update_fields):
if self.cargos.count() > self.MAX_CARGOS:
raise ValidationError(f"A Committee can have at most {self.MAX_CARGOS} cargos.")
super().save(force_insert, force_update, using, update_fields)
Anyway this is not what I wanted since this implementation will only take into account the old model values and not the updated ones.
- Then I tried to implement it through signals, it seems many people use this solution. Here, I tried to listen m2m_changed, but no signal of this type seems to be triggered when data is modified through admin panel. I was able to get a signal pre_save, but with this signal I don’t know how to get the new values:
def validate_committee_cargos(sender, instance, **kwargs):
logging.info("We got a signal")
logging.info(sender)
logging.info(sender.objects.get(pk=instance.pk))
updated = sender.objects.get(pk=instance.pk).cargos.all()
for item in updated:
logging.info(item)
logging.info(kwargs)
my_m2m_field = instance.cargos.all()
for item in my_m2m_field:
logging.info(item)
pre_save.connect(validate_committee_cargos, sender=Committee, weak=False)
- Also saw some people implementing these validations only at forms level. IMO this is good to make some pre checks and to save some round-trips, but at backend level this validation should be done too, since it is part of business / model logic.
Are signals the standard way to do this? If yes, how can I achieve this data validation? Should I use managers, since Django Docs say “Signals can make your code harder to maintain. Consider implementing a helper method on a custom manager, to both update your models and perform additional logic, or else overriding model methods before using model signals.”?
Using Django 4.1. Thanks in advance