python Django:如何从模型中验证 unique_together
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1923826/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Django: How do I validate unique_together from within the model
提问by orokusaki
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. I wish that in my view, I could just go:
如果我然后使用与同一帐户上的另一个相同的用户名创建一个新的 AccountAdmin,而不是它给我一个错误以显示在模板中,它会因 IntegrityError 而中断并且页面死亡。我希望在我看来,我可以去:
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. 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. It also violates DRY because if I have two forms for the same thing, I have to write the same except: again.
我不想在我看来不必捕获 IntegrityError。那是域逻辑与表示逻辑的混合。它违反了 DRY,因为如果我在 2 页上显示相同的表单,我将不得不重复相同的块。它也违反了 DRY,因为如果我有两个表单用于同一件事,我必须写相同的除了:再次。
回答by cethegeek
There are two options:
有两种选择:
a) Have a try block where you save your model and capture the IntegrityError and deal with it. Something like:
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) In the clean() method of your form, check if the a row exists and raise a forms.ValidationError
with an appropriate message. Example here.
b) 在表单的 clean() 方法中,检查 a 行是否存在并forms.ValidationError
使用适当的消息引发 a 。示例在这里。
So, b) it is... That is why I referenced the documentation; all you need is there.
所以,b)它是......这就是我引用文档的原因;你需要的一切都在那里。
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方法之后调用。
回答by trubliphone
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."
回答by teepark
Model.Meta.unique_together creates a constraint limited to the database, while ModelForm.is_valid() is primarily based on correct types. Event if it did check constraints you would have a race condition that could still cause an IntegrityError in the save() call.
Model.Meta.unique_together 创建仅限于数据库的约束,而 ModelForm.is_valid() 主要基于正确的类型。事件如果它确实检查了约束,你将有一个竞争条件,它仍然可能导致 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