django-viewsdjango-formsdjango-1.3

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_together 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.


Solution

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