【问题标题】:Django: How do I validate unique_together from within the modelDjango:如何从模型中验证 unique_together
【发布时间】:2010-12-27 18:52:00
【问题描述】:

我有以下几点:

class AccountAdmin(models.Model):

    account = models.ForeignKey(Account)
    is_master = models.BooleanField()
    name = models.CharField(max_length=255)
    email = models.EmailField()

    class Meta:
        unique_together = (('Account', 'is_master'), ('Account', 'username'),)

如果我随后在同一个帐户上创建一个与另一个用户名相同的新 AccountAdmin,而不是在模板中显示一个错误,它会因 IntegrityError 而中断并且页面消失。我希望在我看来,我可以去:

if new_accountadmin_form.is_valid():
    new_accountadmin_form.save()

我该如何解决这个问题。是否有第二种 is_valid() 类型的方法来检查 DB 是否违反了 unique_together = (('Account', 'is_master'), ('Account', 'username'),) 部分?

我不想在我的视图中捕获 IntegrityError。那是域逻辑与表示逻辑的混合。它违反了 DRY,因为如果我在 2 页上显示相同的表格,我将不得不重复相同的块。它也违反了 DRY,因为如果我对同一事物有两种形式,我必须写相同的 except: 再次。

【问题讨论】:

    标签: python django django-models django-views unique-constraint


    【解决方案1】:

    有两种选择:

    a) 使用 try 块保存模型并捕获 IntegrityError 并进行处理。比如:

    try:
        new_accountadmin_form.save()
    except IntegrityError:
        new_accountadmin_form._errors["account"] = ["some message"]
        new_accountadmin_form._errors["is_master"] = ["some message"]
    
        del new_accountadmin_form.cleaned_data["account"]
        del new_accountadmin_form.cleaned_data["is_master"]
    

    b) 在表单的 clean() 方法中,检查 a 行是否存在并使用适当的消息引发 forms.ValidationError。示例here


    所以,b) 它是...这就是为什么我referenced the documentation; all you need is there.

    但它会是这样的:

    class YouForm(forms.Form):
        # Everything as before.
        ...
    
        def clean(self):
           """ This is the form's clean method, not a particular field's clean method """
           cleaned_data = self.cleaned_data
    
           account = cleaned_data.get("account")
           is_master = cleaned_data.get("is_master")
           username = cleaned_data.get("username")
    
           if AccountAdmin.objects.filter(account=account, is_master=is_master).count() > 0:
               del cleaned_data["account"]
               del cleaned_data["is_master"]
               raise forms.ValidationError("Account and is_master combination already exists.")
    
           if AccountAdmin.objects.filter(account=account, username=username).count() > 0:
               del cleaned_data["account"]
               del cleaned_data["username"]
               raise forms.ValidationError("Account and username combination already exists.")
    
        # Always return the full collection of cleaned data.
        return cleaned_data
    

    对于它的价值 - 我刚刚意识到您上面的 unique_together 引用了一个名为 username 的字段,该字段未在模型中表示。

    在调用各个字段的所有 clean 方法之后调用上面的 clean 方法。

    【讨论】:

    • 如何在 clean 方法中做到这一点并将其附加到正常的 clean 方法中。我知道 super(MyForm, self).clean() 但我怎样才能将它们附加在一起,以便所有验证同时发生,以便我可以在页面上显示:-“对不起,您的电子邮件无效”- “对不起,您的用户名已存在此帐户”等
    • 很遗憾,您的第二种方法受种族限制,您偶尔仍会收到IntegrityError
    【解决方案2】:

    对于一个完全通用的方式。在模型中有如下两个helper fns:

    def getField(self,fieldName):
      # return the actual field (not the db representation of the field)
      try:
        return self._meta.get_field_by_name(fieldName)[0]
      except models.fields.FieldDoesNotExist:
        return None
    

    def getUniqueTogether(self):
      # returns the set of fields (their names) that must be unique_together
      # otherwise returns None
      unique_together = self._meta.unique_together
      for field_set in unique_together:
        return field_set
      return None
    

    并且在表格中有以下fn:

    def clean(self):
      cleaned_data = self.cleaned_data
      instance = self.instance
    
      # work out which fields are unique_together
      unique_filter = {}
      unique_fields = instance.getUniqueTogether()
      if unique_fields:
        for unique_field in unique_fields:
          field = instance.getField(unique_field)
          if field.editable: 
            # this field shows up in the form,
            # so get the value from the form
            unique_filter[unique_field] = cleaned_data[unique_field]
          else: 
            # this field is excluded from the form,
            # so get the value from the model
            unique_filter[unique_field] = getattr(instance,unique_field)
    
        # try to find if any models already exist in the db;
        # I find all models and then exlude those matching the current model.
        existing_instances = type(instance).objects.filter(**unique_filter).exclude(pk=instance.pk)
    
        if existing_instances:
          # if we've gotten to this point, 
          # then there is a pre-existing model matching the unique filter
          # so record the relevant errors
          for unique_field in unique_fields:
            self.errors[unique_field] = "This value must be unique."
    

    【讨论】:

      【解决方案3】:

      Model.Meta.unique_together 创建一个仅限于数据库的约束,而 ModelForm.is_valid() 主要基于正确的类型。如果它确实检查了约束,那么您将有一个竞争条件,仍然可能在 save() 调用中导致 IntegrityError。

      您可能想要捕获 IntegrityError:

      if new_accountadmin_form.is_valid():
          try:
              newaccountadmin_form.save()
          except IntegrityError, error:
              # here's your error handling code
      

      【讨论】:

      • 1) 一个问题是我必须从正确的数据库中导入 IntegrityError,这需要更多的配置。 2)另一个问题是我不想在我的视图中使用验证逻辑。 3)(修辞,针对 Django 本身)如果在验证过程中没有合乎逻辑的处理方式,那么对某事物施加 unique_together 约束有什么意义。
      • 文档回答了您的第三点:它可以确保创建数据库约束和管理界面。
      • 1) 您可以从 django.db 导入 IntegrityError,它会根据您的设置获得正确的后端 IntegrityError。 2)我也没有。您可能应该将它写在您的 ModelForm 子类的方法中。 3) 因为唯一的选择是不提供指定唯一性约束的方法。
      猜你喜欢
      • 2012-08-13
      • 2011-01-09
      • 1970-01-01
      • 2016-01-12
      • 2023-01-07
      • 2016-12-20
      • 2017-08-16
      • 2013-05-06
      相关资源
      最近更新 更多