为什么没有Django强制我的unique_together约束作为一个form.ValidationError而不是抛出一个异常? [英] Why doesn't Django enforce my unique_together constraint as a form.ValidationError instead of throwing an exception?

查看:284
本文介绍了为什么没有Django强制我的unique_together约束作为一个form.ValidationError而不是抛出一个异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑:这篇文章与 Django的ModelForm unique_together验证,在这里,从ModelForm中删除'exclude'的接受的答案是一个比另一个问题中接受的答案更清晰的解决方案。



这是此次的后续行动问题



如果我没有明确检查clean_title()函数中的unique_together约束,django会抛出一个异常:


/ journal / journal / 4中的IntegrityError



重复键值违反唯一约束journal_journal_owner_id_key



请求方法:POST



请求URL: http:// localhost:8000 / journal / journal / 4



异常类型:IntegrityError



异常值:重复键值违反唯一约束journal_journal_owner_id_key



异常位置:/Library/Python/2.6/site-packages/django/db/后端/ 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元:
unique_together =(owner,title)

def __unicode __(self):
return self.title

表单代码:

 类JournalForm(ModelForm) :
class Meta:
model = models.Journal
exclude =('owner',)

html_input = forms.CharField(label = u'Journal内容: ',widget = TinyMCE(attrs = {'cols':'85','rows':'40'},),)

def clean_title(self):
title = self .cleaned_data ['title']
如果self.instance.id:
如果models.Journal.objects.filter(owner = self.instance.owner,title = title).exclude(id = self.instance.id).count()> 0:
raise forms.ValidationError(你已经有了这个标题的日记帐,请改变你的标题,所以它是唯一的。)
else:
如果models.Journal.objects。 filter(owner = self.instance.owner,title = title).count()> 0:
raise forms.ValidationError(你已经有了这个标题的日记,请更改你的标题,这样才是唯一的。)
return title

查看代码:

  id =''):
如果没有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,})
journal = models.Journal.objects.get(id = id)
如果request.user.id!= journal.owner.id:
返回http。 HttpResponseForbidden('< h1>访问被拒绝< / h1>')
data = {
'title':journal.title,
'html_input':_journal_fields_t o_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':
如果LOGIN_FORM_KEY在request.POST:
返回_handle_login(请求)
else:
如果''== id:
journal = models.Journal()
journal.owner = owner
else:
journal = models。 Journal.objects.get(id = id)
form = forms.JournalForm(data = request.POST,instance = journal)
如果form.is_valid():
journal.owner = owner
journal.title = form.cleaned_data ['title']
journal.published = form.cleaned_data ['published']
journal.save()
如果_HTML_to_journal_fields(journal,form.cleaned_data ['html_input']):
html_memo =保存成功。
else:
html_memo =无法保存日志。
return shortcuts.render_to_response('journal / Journal.html',{'form':form,'saved':html_memo})
else:
return shortcuts.render_to_response('journal / Journal .html',{'form':form})
返回http.HttpResponseNotAllowed(['GET','POST'])
除了models.Journal.DoesNotExist:
返回http.HttpResponseNotFound ('< h1>请求的日记未找到< / h1>')

更新工作代码:
感谢Daniel Roseman。



模型代码与上述保持一致。 b
$ b

表单代码 - 删除exclude语句和clean_title函数:

 类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)
如果request.user.id!= journal.owner.id:
返回http.HttpResponseForbidden ('< h1>访问被拒绝< / h1>')
如果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':
如果LOGIN_FORM_KEY在request.POST:
return _handle_logi n(请求)
data = request.POST.copy()
data ['owner'] = request.user.id
如果''== id:
form = form.JournalForm(data)
else:
form = forms.JournalForm(data,instance = journal)
如果form.is_valid():
journal = form.save()
如果_HTML_to_journal_fields(journal,form.cleaned_data ['html_input']):
html_memo =保存成功。
else:
html_memo =无法保存日志。
return shortcuts.render_to_response('journal / Journal.html',{'form':form,'saved':html_memo})
else:
如果form.unique_error_message:
err_message =你已经有一个实验室日记本的标题。
else:
err_message = form.errors
返回shortcuts.render_to_response('journal / Journal.html',{'form':form,') error_message':err_message}
返回http.HttpResponseNotAllowed(['GET','POST'])
除了models.Journal.DoesNotExist:
返回http.HttpResponseNotFound('< h1>


解决方案

麻烦的是,您特别排除了唯一检查中涉及的一个字段,Django将不会在这种情况下运行检查 - 请参阅第722行中的 _get_unique_checks 方法的 django.db.models.base



而不是排除所有者字段,我会考虑将它从模板中删除,并将其显式地设置在您传递的数据上即时iation:

  data = request.POST.copy()
data ['owner'] = request.user。 id
form = JournalForm(data,instance = journal)

请注意,真的在这里使用模型的力量。您不需要在初始GET上显式设置数据字典 - 实际上,您不应该数据参数传递给,因为它触发验证:如果您需要传递与实例不同的值,则应该使用 initial 。但大部分时间,只是通过实例就足够了。



而且,在POST上,再也不要需要明确设置值:你可以做:

  journal = form.save()

这将正确更新实例并返回。


Edit: While this post is a duplicate of Django's ModelForm unique_together validation, the accepted answer here of removing the 'exclude' from the ModelForm is a much cleaner solution than the accepted answer in the other question.

This is a follow-up to this question.

If I don't explicitly check the unique_together constraint in the clean_title() function, django throws an exception:

IntegrityError at /journal/journal/4

duplicate key value violates unique constraint "journal_journal_owner_id_key"

Request Method: POST

Request URL: http://localhost:8000/journal/journal/4

Exception Type: IntegrityError

Exception Value: duplicate key value violates unique constraint "journal_journal_owner_id_key"

Exception Location: /Library/Python/2.6/site-packages/django/db/backends/util.py in execute, line 19

However I was under the impression that Django would enforce this constraint nicely by raising a ValidationError, not with an exception I need to catch.

Below is my code with an additional clean_title() method I use as a work-around. But I want to know what I'm doing wrong such that django is not enforcing the constraint in the expected manner.

Thanks.

Model code:

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 

Form code:

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

View Code:

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

UPDATE WORKING CODE: Thanks to Daniel Roseman.

Model code stays the same as above.

Form code - remove exclude statement and clean_title function:

class JournalForm (ModelForm):
    class Meta:
        model = models.Journal

    html_input = forms.CharField(label=u'Journal Content:', widget=TinyMCE(attrs={'cols':'85', 'rows':'40'},),)

View Code - add custom uniqueness error message:

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

解决方案

The trouble is that you're specifically excluding one of the fields involved in the unique check, and Django won't run the check in this circumstance - see the _get_unique_checks method in line 722 of django.db.models.base.

Instead of excluding the owner field, I would consider just leaving it out of the template and setting the value explicitly on the data you're passing in on instantiation:

 data = request.POST.copy()
 data['owner'] = request.user.id
 form = JournalForm(data, instance=journal)

Note that you're not really using the power of the modelform here. You don't need to explicitly set the data dictionary on the initial GET - and, in fact, you shouldn't pass a data parameter there, as it triggers validation: if you need to pass in values that are different to the instance's, you should use initial instead. But most of the time, just passing instance is enough.

And, on POST, again you don't need to set the values explicitly: you can just do:

journal = form.save()

which will update the instance correctly and return it.

这篇关于为什么没有Django强制我的unique_together约束作为一个form.ValidationError而不是抛出一个异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆