为啥 Django 不将我的 unique_together 约束强制为 form.ValidationError 而不是抛出异常?
Posted
技术标签:
【中文标题】为啥 Django 不将我的 unique_together 约束强制为 form.ValidationError 而不是抛出异常?【英文标题】:Why doesn't Django enforce my unique_together constraint as a form.ValidationError instead of throwing an exception?为什么 Django 不将我的 unique_together 约束强制为 form.ValidationError 而不是抛出异常? 【发布时间】:2011-05-01 09:34:14 【问题描述】:编辑:虽然这篇文章是 Django's ModelForm unique_together validation 的副本,但这里接受的从 ModelForm 中删除“排除”的答案比其他问题中接受的答案要干净得多。
这是this question的后续行动。
如果我没有明确检查 clean_title() 函数中的 unique_together 约束,django 会抛出异常:
IntegrityError at /journal/journal/4
重复键值违反唯一约束“journal_journal_owner_id_key”
请求方法:POST
请求网址:http://localhost:8000/journal/journal/4
异常类型:IntegrityError
异常值:重复键值违反唯一约束“journal_journal_owner_id_key”
异常位置:/Library/Python/2.6/site-packages/django/db/backends/util.py 在执行中,第 19 行
但是我的印象是 Django 会通过引发 ValidationError 很好地强制执行此约束,而不是我需要捕获的异常。
下面是我的代码,其中包含我用作解决方法的附加 clean_title() 方法。但我想知道我做错了什么,以至于 django 没有以预期的方式执行约束。
谢谢。
型号代码:
class Journal (models.Model):
owner = models.ForeignKey(User, related_name='journals')
title = models.CharField(null=False, max_length=256)
published = models.BooleanField(default=False)
class Meta:
unique_together = ("owner", "title")
def __unicode__(self):
return self.title
表格代码:
class JournalForm (ModelForm):
class Meta:
model = models.Journal
exclude = ('owner',)
html_input = forms.CharField(label=u'Journal Content:', widget=TinyMCE(attrs='cols':'85', 'rows':'40', ), )
def clean_title(self):
title = self.cleaned_data['title']
if self.instance.id:
if models.Journal.objects.filter(owner=self.instance.owner, title=title).exclude(id=self.instance.id).count() > 0:
raise forms.ValidationError(u'You already have a Journal with that title. Please change your title so it is unique.')
else:
if models.Journal.objects.filter(owner=self.instance.owner, title=title).count() > 0:
raise forms.ValidationError(u'You already have a Journal with that title. Please change your title so it is unique.')
return title
查看代码:
def journal (request, id=''):
if not request.user.is_active:
return _handle_login(request)
owner = request.user
try:
if request.method == 'GET':
if '' == id:
form = forms.JournalForm(instance=owner)
return shortcuts.render_to_response('journal/Journal.html', 'form':form, )
journal = models.Journal.objects.get(id=id)
if request.user.id != journal.owner.id:
return http.HttpResponseForbidden('<h1>Access denied</h1>')
data =
'title' : journal.title,
'html_input' : _journal_fields_to_HTML(journal.id),
'published' : journal.published
form = forms.JournalForm(data, instance=journal)
return shortcuts.render_to_response('journal/Journal.html', 'form':form, )
elif request.method == 'POST':
if LOGIN_FORM_KEY in request.POST:
return _handle_login(request)
else:
if '' == id:
journal = models.Journal()
journal.owner = owner
else:
journal = models.Journal.objects.get(id=id)
form = forms.JournalForm(data=request.POST, instance=journal)
if form.is_valid():
journal.owner = owner
journal.title = form.cleaned_data['title']
journal.published = form.cleaned_data['published']
journal.save()
if _HTML_to_journal_fields(journal, form.cleaned_data['html_input']):
html_memo = "Save successful."
else:
html_memo = "Unable to save Journal."
return shortcuts.render_to_response('journal/Journal.html', 'form':form, 'saved':html_memo)
else:
return shortcuts.render_to_response('journal/Journal.html', 'form':form )
return http.HttpResponseNotAllowed(['GET', 'POST'])
except models.Journal.DoesNotExist:
return http.HttpResponseNotFound('<h1>Requested journal not found</h1>')
更新工作代码: 感谢丹尼尔·罗斯曼。
型号代码与上面相同。
表单代码 - 删除排除语句和 clean_title 函数:
class JournalForm (ModelForm):
class Meta:
model = models.Journal
html_input = forms.CharField(label=u'Journal Content:', widget=TinyMCE(attrs='cols':'85', 'rows':'40',),)
查看代码 - 添加自定义唯一性错误消息:
def journal (request, id=''):
if not request.user.is_active:
return _handle_login(request)
try:
if '' != id:
journal = models.Journal.objects.get(id=id)
if request.user.id != journal.owner.id:
return http.HttpResponseForbidden('<h1>Access denied</h1>')
if request.method == 'GET':
if '' == id:
form = forms.JournalForm()
else:
form = forms.JournalForm(initial='html_input':_journal_fields_to_HTML(journal.id),instance=journal)
return shortcuts.render_to_response('journal/Journal.html', 'form':form, )
elif request.method == 'POST':
if LOGIN_FORM_KEY in request.POST:
return _handle_login(request)
data = request.POST.copy()
data['owner'] = request.user.id
if '' == id:
form = forms.JournalForm(data)
else:
form = forms.JournalForm(data, instance=journal)
if form.is_valid():
journal = form.save()
if _HTML_to_journal_fields(journal, form.cleaned_data['html_input']):
html_memo = "Save successful."
else:
html_memo = "Unable to save Journal."
return shortcuts.render_to_response('journal/Journal.html', 'form':form, 'saved':html_memo)
else:
if form.unique_error_message:
err_message = u'You already have a Lab Journal with that title. Please change your title so it is unique.'
else:
err_message = form.errors
return shortcuts.render_to_response('journal/Journal.html', 'form':form, 'error_message':err_message)
return http.HttpResponseNotAllowed(['GET', 'POST'])
except models.Journal.DoesNotExist:
return http.HttpResponseNotFound('<h1>Requested journal not found</h1>')
【问题讨论】:
Django's ModelForm unique_together validation的可能重复 【参考方案1】:问题是您专门排除了唯一检查涉及的字段之一,在这种情况下 Django 不会运行检查 - 请参阅 django.db.models.base
的第 722 行中的 _get_unique_checks
方法。
我不会排除 owner
字段,而是考虑将其排除在模板之外,并在实例化时传入的数据上明确设置值:
data = request.POST.copy()
data['owner'] = request.user.id
form = JournalForm(data, instance=journal)
请注意,您在这里并没有真正使用模型表单的强大功能。您不需要在初始 GET 上显式设置数据字典 - 事实上,您不应该在那里传递 data
参数,因为它会触发验证:如果您需要传递在与实例不同的值中,您应该改用initial
。但大多数时候,只需传递instance
就足够了。
而且,在 POST 上,您无需明确设置值:您可以这样做:
journal = form.save()
这将正确更新实例并返回它。
【讨论】:
谢谢,这一切都说得通。关于在初始获取时设置数据字典的一件事 - 每个期刊与内容具有一对多的关系。在保存时,我解析返回的 HTML 并将其分成块并保存。在加载 (GET) 时,我将这些块“重组”为单个 HTML 块。是否有更多 Django 原生或“Pythonic”方式来定义此数据转换以利用 ModelForm? 顺便说一句,这个问题被报告为 django bug 13091 code.djangoproject.com/ticket/13091【参考方案2】:我认为这里的哲学是 unique_together 是一个 ORM 概念,而不是表单的属性。如果您想对特定表单强制执行 unique_together,您可以编写自己的 clean 方法,该方法简单、直接且非常灵活:
http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
这将替换您编写的 clean_title 方法。
【讨论】:
除了唯一的一个unique_together字段,标题,是可编辑的。另一个基本上是私有的——它是将期刊链接到当前用户的外键。所以从这个角度来看,我应该将验证保留在 clean_title() 中,因为它是我要验证的唯一表单字段,对吧? 我明白了。在这种情况下,您可以尝试使用 ComboField? docs.djangoproject.com/en/dev/ref/forms/fields/…以上是关于为啥 Django 不将我的 unique_together 约束强制为 form.ValidationError 而不是抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Visual Studio 不将我的更改应用于 HTML 文件?
xts 函数不将我的 POSIXct 日期视为适当的基于时间的对象
xts 函数不将我的 POSIXct 日期视为适当的基于时间的对象