Reputation: 11
I would like to prevent a save in a django model when a certain constraint is not met and give a validation error so that a django staff user knows what went wrong.
The constraint is the count()
from an intermediate table specified using the through
parameter.
models.py:
class Goal(models.Model):
name = models.CharField(max_length=128)
class UserProfile(models.Model):
goals = models.ManyToManyField(Goal, through=UserProfileGoals, blank=True)
class UserProfileGoal(models.Model):
goal = models.ForeignKey(Goals)
user_profile = models.ForeignKey(UserProfile)
class UserGoalConstraint(models.Model):
user_profile = models.OneToOneField(UserProfile)
max_goals = models.PositiveIntegerField()
So the UserGoalConstraint.max_goals
gives me the number of the maximum definable UserProfile.goal
which are stored in the UserProfileGoal
model (same UserGoal
can be stored more often to the UserProfile
)
I have read and tried solutions from several posts, which are using ModelForm's clean()
, Model's clean()
and pre_save
signal events,
but the actual problem I have is, how do I know if it is just an update or a new database entry, because
class UserProfileGoal(models.Model):
goal = models.ForeignKey(Goals)
user_profile = models.ForeignKey(UserProfile)
def clean(self):
goal_counter = self.user_profile.goals.count() + 1
try:
qs = UserGoalConstraint.objects.get(user_profile=self.user_profile)
except UserGoalConstraint.DoesNotExist:
raise ObjectDoesNotExist('Goal Constraint does not exist')
if goal_counter > qs.max_goals:
raise ValidationError('There are more goals than allowed goals')
does not really work, as clean()
can also be an update and the +1 gives me a wrong result which leads to the ValidationError.
My client should use the django-admin interface to add goals to the user profile directly via an Inline:
admin.py:
class UserProfileGoalInline(admin.TabularInline):
model=UserProfileGoal
class UserProfileAdmin(admin.ModelAdmin)
...
inlines = [UserProfileGoalInline, ]
So he needs to be nicely informed when he adds to many goals to a user profile.
Maybe I am missing something obvious on how to solve this problem...? I am looking for a working and somehow user friendly solution (= get informed in admin interface).
[UPDATE]:
I tried know to check wether it is created or not with the self.pk is None
trick at the beginning of the clean()
if self.pk is not None:
return # it is not a create
...
I thought that would deal with the issue... However, in the admin inline, when the staff user adds more than one goal at the same time, the clean() does not recognize these. Debug output shows for 2 goals added, that the goal counter holds the same number even the second entry should have one more and should give an validation error
Upvotes: 0
Views: 1624
Reputation: 11
Thanks to @zaidfazil for a starting solution:
class UserProfileGoalForm(forms.ModelForm):
class Meta:
model = UserProfileGoal
...
def clean(self):
cleaned_data = super(UserProfileGoalForm, self).clean()
if self.instance.pk is not None:
return cleaned_data
user_profile = self.cleaned_data.get('user_profile')
goal_count = user_profile.goals.count()
goal_limit = UserGoalConstraint.objects.get(user_profile=user_profile).max_goals # removed try catch for get for easier reading
if goal_count >= goal_limit:
raise ValidationError('Maximum limit reached for goals')
return cleaned_data
However, this does not handle the inline in the UserProfile admin interface: clean()
won't handle correctly if you add more than one Goal
at the same time and press save.
So I applied the UserProfileGoalForm
to the inline and defined max_num
:
class UserProfileGoalInline(admin.TabularInline):
model=UserProfileGoal
form = UserProfileGoalForm
def get_max_num(self, request, obj=None, **kwargs):
if obj is None:
return
goal_limit = UserGoalConstraint.objects.get(training_profile=obj).max_goals
return goal_limit # which will overwrite the inline's max_num attribute
Now my client can only add at maximum the max_goals
value from the UserGoalConstraint
, and also a possible admin form for UserProfileGoal
will handle the constraint:
class UserProfileGoalAdmin(admin.ModelAdmin):
form = UserProfileGoalForm
Upvotes: 1
Reputation: 9235
You could handle it in ModelForm
clean
method,
class GoalForm(forms.ModelForm):
class Meta:
model = Goal
.....
def clean(self):
cleaned_data = super(GoalForm, self).clean()
if self.instance.pk is not None:
return cleaned_data
goal_limit = self.user_profile.usergoalconstraint.max_goals
goal_count = self.user_profile.goals.count()
if goal_count >= goal_limit:
raise ValidationError("Maximum limit reached for goals")
return cleaned_data
Upvotes: 0