I have two models:
class User(models.Model):
name = models.CharField(max_length=32)
class Referral(models.Model):
referring_user = models.ForeignKey(User, related_name="referrals")
referred_user = models.ForeignKey(User, related_name="referrers")
percentage = models.PositiveIntegerField()
The idea is that every user has n
referrers, and should have at least one. Each referrer has a percentage
value which should add up to 100% when added to the other referrers.
So User "Alice" might have referrers "Bob" (50%) and "Cynthia" (50%), and User "Donald" might have one referrer: "Erin" (100%).
The problem I have is with validation. Is there a way (preferably one that plays nice with the Django admin using admin.TabularInline
) that I can have validation reject the saving of a User
if the sum of Refferrals != 100%
?
Ideally I want this to happen at the form/admin level and not by overriding User.save()
, but at this point I don't know where to start. Most of Django's validation code appears to be atomic, and validation across multiple rows is not something I've done in Django before.
After Jerry Meng suggested I look into the data
property and not cleaned_data
, I started poking around admin.ModelAdmin
to see how I might access that method. I found get_form
which appears to return a form class, so I overrode that method to capture the returning class, subclass it, and override .clean()
in there.
Once inside, I looped over self.data
, using a regex to find the relevant fields and then literally did the math.
import re
from django import forms
from django.contrib import admin
class UserAdmin(admin.ModelAdmin):
# ...
def get_form(self, request, obj=None, **kwargs):
parent = admin.ModelAdmin.get_form(self, request, obj=None, **kwargs)
class PercentageSummingForm(parent):
def clean(self):
cleaned_data = parent.clean(self)
total_percentage = 0
regex = re.compile(r"^referrers-(\d+)-percentage$")
for k, v in self.data.items():
match = re.match(regex, k)
if match:
try:
total_percentage += int(v)
except ValueError:
raise forms.ValidationError(
"Percentage values must be integers"
)
if not total_percentage == 100:
raise forms.ValidationError(
"Percentage values must add up to 100"
)
return cleaned_data
return PercentageSummingForm
As per the Django docs, clean()
is the official function to implement for your purposes. You could imagine a function that looks like this:
from django.core.exceptions import ValidationError
def clean(self):
total_percentage = 0
for referrer in self.referrers.all():
total_percentage += referrer.percentage
if total_percentage !== 100:
raise ValidationError("Referrer percentage does not equal 100")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With