在django中保存实例之前,在多对多关系中验证count()约束

时间:2017-09-11 10:21:28

标签: python django validation django-models django-forms

我想在不满足某个约束时阻止django模型中的保存并给出验证错误,以便django员工用户知道出了什么问题。

约束是使用count()参数指定的中间表中的through

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()

因此UserGoalConstraint.max_goals为我提供了存储在UserProfile.goal模型中的最大可定义UserProfileGoal的数量(相同UserGoal可以更频繁地存储到{UserProfile 1}})

我已阅读并尝试了几个帖子的解决方案,这些帖子正在使用ModelForm的clean(),模型的clean()pre_save信号事件,

但我遇到的实际问题是,我怎么知道它只是一个更新还是一个新的数据库条目,因为

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')

不起作用,因为clean()也可以是更新,而+1给出了错误的结果,导致ValidationError。

我的客户端应使用django-admin界面直接通过内联向用户个人资料添加目标:

admin.py:

class UserProfileGoalInline(admin.TabularInline):
  model=UserProfileGoal

class UserProfileAdmin(admin.ModelAdmin)
  ...
  inlines = [UserProfileGoalInline, ]

因此,当他向用户个人资料添加许多目标时,他需要得到很好的信息。

也许我错过了一些关于如何解决这个问题的明显内容......? 我正在寻找一个工作和某种用户友好的解决方案(=在管理界面获得通知)。

[UPDATE]: 我尝试知道在self.pk is None

的开头使用clean()技巧检查是否创建了它
if self.pk is not None:
  return  # it is not a create
...

我认为这会解决这个问题...... 但是,在admin内联中,当staff用户同时添加多个目标时,clean()无法识别这些目标。调试输出显示添加了2个目标,目标计数器保持相同的数字,即使第二个条目应该还有一个并且应该给出验证错误

2 个答案:

答案 0 :(得分:1)

感谢@zaidfazil的首发解决方案:

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

但是,这不会处理UserProfile管理界面中的内联:如果您同时添加多个clean()并按保存,则Goal将无法正确处理。

所以我将UserProfileGoalForm应用于内联并定义了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

现在,我的客户端最多只能添加max_goals的{​​{1}}值,UserGoalConstraint的可能管理表格也会处理约束:

UserProfileGoal

答案 1 :(得分:0)

您可以使用ModelForm clean方法

来处理它
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