Django:如何在模型内验证unique_together
我有以下内容:
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()
的方法,可以检查数据库中是否违反了unique_together = (('Account', 'is_master'), ('Account', 'username'),)
这一部分的规则?
我希望在我的视图中不需要处理IntegrityError。这是业务逻辑和展示逻辑混在一起了。这违反了DRY原则,因为如果我在两个页面上显示同一个表单,我就得重复同样的代码块。如果我有两个表单用于同样的事情,我还得再次写相同的异常处理代码。
3 个回答
Model.Meta.unique_together 是用来在数据库中设置一个限制的,而 ModelForm.is_valid() 主要是检查数据类型是否正确。即使它检查了这些限制,仍然可能会出现竞争条件,这样在调用 save() 时可能会导致一个完整性错误(IntegrityError)。
你可能需要捕捉这个完整性错误:
if new_accountadmin_form.is_valid():
try:
newaccountadmin_form.save()
except IntegrityError, error:
# here's your error handling code
为了提供一种完全通用的方法。在模型中有以下两个辅助函数:
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
在表单中有以下函数:
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."
这里有两个选择:
a) 使用一个尝试块(try block),在这里你可以保存你的模型,并捕捉到完整性错误(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() 方法中,检查一下是否已经存在某一行数据,如果存在,就抛出一个 forms.ValidationError
,并给出一个合适的提示信息。示例可以参考 这里。
所以,选择 b) 吧……这就是我为什么 提到文档的原因;你需要的内容都在里面。
不过大概是这样的:
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 方法都调用完之后才被调用的。