Login

Model Forms: Clean unique field

Author:
johnboxall
Posted:
December 1, 2008
Language:
Python
Version:
1.0
Score:
5 (after 5 ratings)

Often I want fields in my models to be unique - Django's unique works too late in model form validation and will throw an exception unless you check for it by hand. This is a bit of code that cleans up the boiler plate of checking if different fields are unique.

Set exclude_initial to False if you want to raise an error if the unique field cannot be set to whatever value the instance had before.

Update

Thanks fnl for rightly pointing out that Django's unique=True does check for this - just make sure to pass instance=obj when you're initializing your forms. HOWEVER a problem you'll typically run into with this is though you want a field to unique, you also want multiple entries to be able to be blank. This can help you!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from django import forms

def clean_unique(form, field, exclude_initial=True, 
                 format="The %(field)s %(value)s has already been taken."):
    value = form.cleaned_data.get(field)
    if value:
        qs = form._meta.model._default_manager.filter(**{field:value})
        if exclude_initial and form.initial:
            initial_value = form.initial.get(field)
            qs = qs.exclude(**{field:initial_value})
        if qs.count() > 0:
            raise forms.ValidationError(format % {'field':field, 'value':value})
    return value

# Usage:

class DeployForm(forms.ModelForm):
    """We want both the slug and cname fields to be unique"""

    class Meta:
        model = Website
        fields = ['slug', 'cname']
        
    def clean_slug(self):
        return clean_unique(self, 'slug')
        
    def clean_cname(self):
        return clean_unique(self, 'cname') 

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

fnl (on December 4, 2008):

Django actually supports this problem if you know how, ie the snippet might not really be the right solution:

class MyModel(models.Model):
    email = models.EmailField(unique=True)

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel

def my_model_view(request, model_id):
    obj = get_object_or_404(MyModel, pk=model_id)
    if request.method == 'POST':
        form = MyModelForm(request.POST, instance=obj)
        if form.is_valid():
            obj = form.save()
    else:
        form = MyModelForm(instance=obj)
    render_to_response(
        'my_model_template.html', {'form': form},
        context_instance=RequestContext(request)
    )

The whole trick is using the instance parameter when creating the form instance from the post. django will load and know about the unique field, ie the line:

form = MyModelForm(request.POST, instance=obj)

is essential.

#

johnboxall (on December 4, 2008):

Whoa, you're right - not sure how I missed that!

#

duh386 (on February 19, 2013):

We can try a little easily method.

class MyModel(models.Model):
    email = models.EmailField(unique=True)

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        exclude = ('email',)
    email = models.CharField(max_length=40)

On this way we must redefine email-field at all, but we don't need to override init method. This may be useful for remove validation for 'unique' attribute for one or few field (not for all).

#

Please login first before commenting.