开发者

Django 1.3 CreateView/ModelForm: unique_together validation with one field excluded from form

I am looking for a simple answer by example to this common problem. The answers I found so far leave out critical points for us beginners.

I have an app where almost every model has a ForeignKey to User, and there is a unique_toget开发者_如何转开发her constraint, where one of the fields is always 'user'.

For example:

class SubscriberList(models.Model):

user = models.ForeignKey(User)
name = models.CharField(max_length=70)
date_created = models.DateTimeField(auto_now_add=True)

class Meta:
    unique_together = (
        ('user', 'name',),
        )   

def __unicode__(self):
    return self.name

A SubscriberList is always created by a logged in User, and thus in the form to create a Subscriber List, I exclude the user field and give it a value of self.request.user when saving the form, like so:

class SubscriberListCreateView(AuthCreateView):
model = SubscriberList
template_name = "forms/app.html"
form_class = SubscriberListForm
success_url = "/app/lists/"

def form_valid(self, form):
    self.object = form.save(commit=False)
    self.object.user = self.request.user
    return super(SubscriberListCreateView, self).form_valid(form) 

And here is the accompanying form:

class SubscriberListForm(ModelForm):
class Meta:
    model = SubscriberList
    exclude = ('user')

With this code, valid data is fine. When I submit data that is not unique_together, I get an Integrity Error from the database. The reason is clear to me - Django doesn't validate the unique_together because the 'user' field is excluded.

How do I change my existing code, still using CreateView, so that submitted data that is not unique_together throws a form validation error, and not an Integrity Error from the db.


Yehonatan's example got me there, but I had to call the messages from within the ValidationError of form_valid, rather than a separate form_invalid function.

This works:

class SubscriberCreateView(AuthCreateView):
    model = Subscriber
    template_name = "forms/app.html"
    form_class = SubscriberForm
    success_url = "/app/subscribers/"

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.user = self.request.user

        try:
            self.object.full_clean()
        except ValidationError:
            #raise ValidationError("No can do, you have used this name before!")
            #return self.form_invalid(form)
            from django.forms.util import ErrorList
            form._errors["email"] = ErrorList([u"You already have an email with that name man."])
            return super(SubscriberCreateView, self).form_invalid(form)

        return super(SubscriberCreateView, self).form_valid(form) 


Taking from the docs at: https://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects

You should only need to call a model’s full_clean() method if you plan to handle validation errors yourself, or if you have excluded fields from the ModelForm that require validation.

Taking from the docs at: https://docs.djangoproject.com/en/dev/ref/class-based-views/#formmixin

Views mixing FormMixin must provide an implementation of form_valid() and form_invalid().

This means that in order to view the error (which isn't form related) you'll need to implement your own form_invalid, add the special error message there, and return it.

So, running a full_clean() on your object should raise the unique_together error, so your code could look like this:

def form_valid(self, form):
    self.object = form.save(commit=False)
    self.object.user = self.request.user
    # validate unique_together constraint
    try:
        self.object.full_clean()
    except ValidationError:
        # here you can return the same view with error messages
        # e.g.
        return self.form_invalid(form)
    return super(SubscriberListCreateView, self).form_valid(form)

def form_invalid(self, form):
    # using messages
    # from django.contrib import messages
    # messages.error('You already have a list with that name')
    # or adding a custom error
    from django.forms.util import ErrorList
    form._errors["name"] = ErrorList([u"You already have a list with that name"])
    return super(SubscriberListCreateView, self).form_invalid(form)

HTH


adding another example that might be a bit easier for noobs.

forms.py

class GroupItemForm(ModelForm):
    def form_valid(self):
        self.object = self.save(commit=False)
        try:
            self.object.full_clean()
        except ValidationError:
            # here you can return the same view with error messages
            # e.g. field level error or...
            self._errors["sku"] = self.error_class([u"You already have an email with that name."])
            # ... form level error
            self.errors['__all__'] = self.error_class(["error msg"]
            return False
        return True

views.py

def add_stock_item_detail(request, item_id, form_class=GroupItemForm, template_name="myapp/mytemplate.html"):
    item = get_object_or_404(Item, pk=item_id)
    product = Product(item=item)
    if request.method == 'POST':
        form = form_class(request.POST, instance=product)
        if form.is_valid() and form.form_valid():
            form.save()
            return HttpResponseRedirect('someurl')
    else:
        form = form_class(instance=product)

    ctx.update({
        "form" : form,
     })

    return render_to_response(template_name, RequestContext(request, ctx))
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜