简体   繁体   English

Django:如何在模型中验证unique_together

[英]Django: How do I validate unique_together from within the model

I have the following: 我有以下内容:

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

If I then create a new AccountAdmin with the same username as another on the same account, instead of it giving me an error to display in the template, it breaks with an IntegrityError and the page dies. 如果我在同一个帐户上创建一个与另一个用户名相同的新AccountAdmin,而不是让我在模板中显示错误,那么它会因IntegrityError而中断,页面就会死亡。 I wish that in my view, I could just go: 我希望在我看来,我可以去:

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

How do I conquer this problem. 我该如何克服这个问题。 Is there a second is_valid() type of method that checks the DB for violation of the unique_together = (('Account', 'is_master'), ('Account', 'username'),) part? 是否有第二种is_valid()类型的方法检查数据库是否违反了unique_together = (('Account', 'is_master'), ('Account', 'username'),)部分?

I would like not to have to catch an IntegrityError in my view. 我不想在我的视图中捕获IntegrityError。 That's domain logic mixed with presentation logic. 那个域逻辑与表示逻辑混合在一起。 It violates DRY because if I display the same form on 2 pages, I'll have to repeat the same block. 它违反DRY,因为如果我在2页上显示相同的表格,我将不得不重复相同的块。 It also violates DRY because if I have two forms for the same thing, I have to write the same except: again. 它也违反了DRY,因为如果我有两种形式用于同一件事,我必须写相同的,除了:再次。

There are two options: 有两种选择:

a) Have a try block where you save your model and capture the IntegrityError and deal with it. a)使用try块来保存模型并捕获IntegrityError并处理它。 Something like: 就像是:

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) In the clean() method of your form, check if the a row exists and raise a forms.ValidationError with an appropriate message. b)在表单的clean()方法中,检查是否存在行,并使用适当的消息引发forms.ValidationError Example here . 这里的例子。


So, b) it is... That is why I referenced the documentation; 所以,b)它是......这就是我引用文档的原因; all you need is there. 所有你需要的就是那里。

But it would be something like: 但它会是这样的:

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

For what it is worth - I just realized that your unique_together above is referencing a field called username that is not represented in the model. 对于它的价值 - 我刚刚意识到你的unique_together正在引用一个名为username的字段,该字段未在模型中表示。

The clean method above is called after all clean methods for the individual fields are called. 在调用各个字段的所有clean方法之后调用上面的clean方法。

And for a completely generic way. 而且是一种完全通用的方式。 In the model have the following two helper fns: 在模型中有以下两个辅助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

and

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

And in the form have the following fn: 并在表单中有以下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."

Model.Meta.unique_together creates a constraint limited to the database, while ModelForm.is_valid() is primarily based on correct types. Model.Meta.unique_together创建一个仅限于数据库的约束,而ModelForm.is_valid()主要基于正确的类型。 Event if it did check constraints you would have a race condition that could still cause an IntegrityError in the save() call. 事件如果确实检查了约束,那么您将遇到竞争条件仍然可能导致save()调用中出现IntegrityError。

You probably want to be catching IntegrityError: 您可能想要捕获IntegrityError:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM