How to validate/clean() a unique=True field without using a ModelForm?
In a custom Form, how does one validate a Model's field's uniqueness (i.e., has unique=True
set)?
I know that django's ModelForm automatically performs a validate_unique()
function that is called within the BaseModelForm's clean()
method -- so, when using ModelForm, this will be handled correctly (as it is in the Admin).
However, I am creating my own form from scratch and wonder how I can go about handling this myself? I think my biggest stumbling block is knowing which object is attached to the form when the data is being cleaned ...
Some code:
class UserProfile(CreatedModifiedModel):
user = models.ForeignKey(User, unique=True)
display_name = models.CharField('Display Name',max_length=30,
blank=True,unique=True)
class EditUserProfileForm(forms.Form):
display_name = forms.CharField(required=False,max_length=30)
# "notifications" are created from a different model, not the UserProfile
notifications = forms.MultipleChoiceField(
label="Email Notifications",
required=False,
widget=forms.CheckboxSelectMultiple,)
def clean_display_name(self):
# how do I run my own validate_unique() on this form?
# how do I know which UserProfile object I am wor开发者_如何学Pythonking with?
# more code follows, including the __init__ which sets up the notifications
Unique validation is hard to get completely right, so I would recommend using a ModelForm anyways:
class EditUserProfileForm(forms.ModelForm):
# "notifications" are created from a different model, not the UserProfile
notifications = forms.MultipleChoiceField(
label="Email Notifications",
required=False,
widget=forms.CheckboxSelectMultiple,)
class Meta:
model = UserProfile
fields = ('display_name',)
Making a form from multiple models is not easy, but in this case you can just add the notifications
field onto the ModelForm and pull it out of .cleaned_data
as usual:
# view
if request.method == 'POST':
form = EditUserProfileForm(request.POST, instance=user_profile)
if form.is_valid():
user_profile = form.save()
notifications = form.cleaned_data['notifications']
# Do something with notifications.
That's how I would do it, but if you're set on validating unique yourself, you can always do something like:
def clean_display_name(self):
display_name = self.cleaned_data['display_name']
if UserProfile.objects.filter(display_name=display_name).count() > 0:
raise ValidationError('This display name is already in use.')
return display_name
There are two problems I see here. First, you can run into concurrency issues, where two people submit the same name, both pass unique checks, but then one gets a DB error. The other problem is that you can't edit a user profile because you don't have an ID to exclude from the search. You'd have to store it in your __init__
and then use it in the cleaning:
def __init__(self, *args, **kwargs):
...
if 'instance' in kwargs:
self.id = kwargs['instance'].id
...
def clean_display_name(self):
display_name = self.cleaned_data['display_name']
qs = UserProfile.objects.filter(display_name=display_name)
if self.id:
qs = qs.exclude(pk=self.id)
if qs.count() > 0:
raise ValidationError('This display name is already in use.')
return display_name
But at that point you're just duplicating the logic in ModelForms.
Working Example !
I am using email as unique field
In models.py use below code
User._meta.get_field('email')._unique = True
In forms.py use below code
class ProfileForm(forms.ModelForm):
email = forms.EmailField(max_length=255, required=True)
def clean_email(self):
user_id = self.cleaned_data['user'].id
email = self.cleaned_data['email']
obj = User.objects.filter(email=email).exclude(id = user_id)
if obj:
raise forms.ValidationError('User with this email is already exists.Try a new email')
精彩评论