Django field cleaning ValidationErrors don't prevent form from processing - django

I'm designing a Django form with custom cleaning methods for both fields, and the form itself.
Ideally, I'd love for my form to stop processing and throw an error if my fields don't validate. However, the custom clean() method that I wrote for my form runs even when fields throw ValidationErrors.
Here's an example:
forms.py
from django import forms
from .models import User
class BasicsForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128)
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError("E-mail is already taken.")
return email
def clean(self):
# Call the parent clean method
cleaned_data = super().clean()
email = cleaned_data.get('email')
u = User(email=email)
u.save()
If I attempt to submit the above form with an invalid e-mail address (for example, an e-mail already registered on the site, which will raise the ValidationError within clean_email), I get an IntegrityError that says I can't add a NULL e-mail to my database. What's happening is that even though clean_email catches an error, the lines that create a user in my clean() method (i.e., u = User(email=email) and u.save()) still run.
How do I make it so that if my clean_email() method catches an error, clean() doesn't run, and my form instead shows the error thrown by clean_email()?

Related

forms validationerror sends me to a ValidationerError page

Ive been struggling with this problem for days now. As you se the validation error works but i want the error to show in the form and not redirect the user to the ValidationError page. What am i missing? I use django Alluth
def custom_signup(self, request, user):
user.profile.pid = self.cleaned_data[_("pid")]
data = User.objects.filter(profile__pid=user.profile.pid)
if data.exists():
raise forms.ValidationError(
_('This user exists in our system. Please try another.'),
code='unique_pid'
)
else:
user.save()
return user
Ok, so first you need to create a custom signup form, I've detailed how that is done in answer to this question
What you're seeing there is a 500 page, in debug mode (so you get all the information about what's happened). The reason that you're seeing this is that you are raising an error.
What you want to do, is to add an error to a form as part of that form's validation.
Once you've created your custom signup form, you can add your validation as part of the form's clean method;
def clean(self):
"""
Clean the form
"""
cleaned_data = super().clean()
pid = self.cleaned_data["pid"]
if User.objects.filter(profile__pid=pid).exists():
self.add_error(
'pid',
_('This user exists in our system. Please try another.'),
)
return cleaned_data
Please note, you're also using translation (_("")) to access the form's cleaned_data - you don't need to do this.

How to validate data in django forms?

I have a feature on my website where a user can share content with another user registered on the site. They do this by entering in an email belonging to another user. This is then posted, setting the desired user to as a shared owner of content in the model.
What is the best way to check that the email address belongs to a registered user of the site?
Thanks!
I think the efficient way is to search for the user with the given mail. Django User already has a mail field that is unique.
if you want to write from basic:
from django.core.validators import validate_email
class SampleForm(forms.Form):
mail = forms.CharField(max_length=50)
def clean(self):
cleaned_data = super(SampleForm, self).clean()
mail = cleaned_data.get('mail')
# validate the structure of the mail address
try:
validate_email(mail)
except validate_email.ValidationError:
raise forms.ValidationError('email is not valid')
# now find if mail has registered
try:
User.objects.get(email=mail)
except User.DoesNotExist:
raise forms.ValidationError('This mail address is not registered')
return cleaned_data

error overiding save method while extending Django-registration app

I am using django-registration app. and have following code in forms.py
from django.contrib.auth.forms import UserCreationForm
from registration.forms import RegistrationFormUniqueEmail
from django import forms
from django.contrib.auth.models import User
from accounts.models import UserProfile
from pprint import pprint
class UserRegistrationForm(RegistrationFormUniqueEmail):
#email = forms.EmailField(label = "Email")
fullname = forms.CharField(label = "Full name")
class Meta:
model = User
fields = ("fullname", "email", )
def __init__(self, *args, **kwargs):
super(UserRegistrationForm, self).__init__(*args, **kwargs)
del self.fields['username']
def save(self, commit=True):
user = super(UserRegistrationForm, self).save(commit=False)
user.userprofile.full_name = self.cleaned_data["fullname"]
user.email = self.cleaned_data["email"]
if commit:
user.save()
return user
I inherited from Django-registraion app's class RegistrationFormUniqueEmail so I when called save method at user = super(UserRegistrationForm, self).save(commit=False) it says, that save attribute doesnot exist. I actually wrote this code with inheritance from UserCreationForm .
I have just read the comment for SuperClass of RegistrationFormUniqueEmail that is :
"""
Form for registering a new user account.
Validates that the requested username is not already in use, and
requires the password to be entered twice to catch typos.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
These comments ask to not define another save method but I need to. So is there way that I can do define save method and call parent save method too to define additional fields? Following is the code of django-registration apps's forms.py:
"""
Forms and validation code for user registration.
"""
from django.contrib.auth.models import User
from django import forms
from django.utils.translation import ugettext_lazy as _
# I put this on all required fields, because it's easier to pick up
# on them with CSS or JavaScript if they have a class of "required"
# in the HTML. Your mileage may vary. If/when Django ticket #3515
# lands in trunk, this will no longer be necessary.
attrs_dict = {'class': 'required'}
class RegistrationForm(forms.Form):
"""
Form for registering a new user account.
Validates that the requested username is not already in use, and
requires the password to be entered twice to catch typos.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
widget=forms.TextInput(attrs=attrs_dict),
label=_("Username"),
error_messages={'invalid': _("This value may contain only letters, numbers and #/./+/-/_ characters.")})
email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("E-mail"))
password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Password"))
password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Password (again)"))
def clean_username(self):
"""
Validate that the username is alphanumeric and is not already
in use.
"""
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
if existing.exists():
raise forms.ValidationError(_("A user with that username already exists."))
else:
return self.cleaned_data['username']
def clean(self):
"""
Verifiy that the values entered into the two password fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields didn't match."))
return self.cleaned_data
class RegistrationFormTermsOfService(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which adds a required checkbox
for agreeing to a site's Terms of Service.
"""
tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
label=_(u'I have read and agree to the Terms of Service'),
error_messages={'required': _("You must agree to the terms to register")})
class RegistrationFormUniqueEmail(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which enforces uniqueness of
email addresses.
"""
def clean_email(self):
"""
Validate that the supplied email address is unique for the
site.
"""
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
return self.cleaned_data['email']
class RegistrationFormNoFreeEmail(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which disallows registration with
email addresses from popular free webmail services; moderately
useful for preventing automated spam registrations.
To change the list of banned domains, subclass this form and
override the attribute ``bad_domains``.
"""
bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com',
'googlemail.com', 'hotmail.com', 'hushmail.com',
'msn.com', 'mail.ru', 'mailinator.com', 'live.com',
'yahoo.com']
def clean_email(self):
"""
Check the supplied email address against a list of known free
webmail domains.
"""
email_domain = self.cleaned_data['email'].split('#')[1]
if email_domain in self.bad_domains:
raise forms.ValidationError(_("Registration using free email addresses is prohibited. Please supply a different email address."))
return self.cleaned_data['email']
I just want to know that how can I override save() method or else how can I create new additional fields?
I have found solution of my own posted problem:
I have removed that django-registration app's RegistrationFormUniqueEmail, and instead I am inheriting from UserCreationForm and added the required method into my own UserRegistrationForm so I am able to override save method and able to do the things that I wanted to .

I'm extending User model in django and I'm not able to authenticate because of raw password

So the problem is I have extended User model in django. and I have written views for it.
Here is my models code :-
class StudentProfile(User):
batch = models.CharField(max_length=10)
course = models.CharField(max_length=20)
date_of_birth = models.DateField()
answer = models.CharField(max_length=20)
contact = models.CharField(max_length=20)
here is my auth backend file :-
from quizapp.models import StudentProfile
class StudentAuthenticationBackend(object):
def authenticate(self, username=None, password=None):
try:
student = StudentProfile.objects.get(username=username)
if student.check_password(password):
return student
except StudentProfile.DoesNotExist:
pass
return None
def get_user(self, user_id):
try:
return StudentProfile.objects.get(pk=user_id)
except StudentProfile.DoesNotExist:
return None
And I have made changes in seetings.py
AUTHENTICATION_BACKENDS = (
'quizapp.backends.StudentAuthenticationBackend',
'django.contrib.auth.backends.ModelBackend',
)
I'm printing username,password and authentication user. This is what i got :-
When using django created superuser
>> a = authenticate(username="super",password="super")
>> print(a)
>> super
But when using user created by form,
>> b = authenticate(username="test",password="123")
>> print(b)
>> None
I have cross checked username and password and it's true.
So but in auth_user table, username is super and password is encrypted but for test user, username is user and password is 123.
So the problem must be django is taking 123 is encrypted password and using decrypted version of it to authenticate.
Is there any way to solve this?
I have used OneToOneField and added extra fields in StudentProfile model. Now I'm using forms and registering user with it.
This is the view code :-
def register_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = StudentProfile.objects.create(
username=form.cleaned_data['username'],
password=form.cleaned_data['password1'],
batch=form.cleaned_data['batch'],
first_name=form.cleaned_data['first_name'],
last_name=form.cleaned_data['last_name'],
course=form.cleaned_data['course'],
date_of_birth=form.cleaned_data['date_of_birth'],
secret_question=form.cleaned_data['secret_question'],
answer=form.cleaned_data['answer'],
contact=form.cleaned_data['contact']
)
return HttpResponseRedirect('/register/success/')
else:
form = RegistrationForm()
variables = RequestContext(request, {'form': form})
return render_to_response('registration/register.html',variables)
And I'm getting IntegrityError at /register/
null value in column "user_id" violates not-null constraint error.
Is there any way to fix this?
From the Django authenication docs section on storing additional information about users:
If you'd like to store additional information related to your users, Django provides a method to specify a site-specific related model -- termed a "user profile" -- for this purpose.
To make use of this feature, define a model with fields for the additional information you'd like to store, or additional methods you'd like to have available, and also add a OneToOneField named user from your model to the User model. This will ensure only one instance of your model can be created for each User.
So you shouldn't subclass User at all -- that's the root of your problem. Instead, you should create another model with a one-to-one relationship with User and add your fields there.

Showing custom model validation exceptions in the Django admin site

I have a booking model that needs to check if the item being booked out is available. I would like to have the logic behind figuring out if the item is available centralised so that no matter where I save the instance this code validates that it can be saved.
At the moment I have this code in a custom save function of my model class:
def save(self):
if self.is_available(): # my custom check availability function
super(MyObj, self).save()
else:
# this is the bit I'm stuck with..
raise forms.ValidationError('Item already booked for those dates')
This works fine - the error is raised if the item is unavailable, and my item is not saved. I can capture the exception from my front end form code, but what about the Django admin site? How can I get my exception to be displayed like any other validation error in the admin site?
In django 1.2, model validation has been added.
You can now add a "clean" method to your models which raise ValidationError exceptions, and it will be called automatically when using the django admin.
The clean() method is called when using the django admin, but NOT called on save().
If you need to use the clean() method outside of the admin, you will need to explicitly call clean() yourself.
http://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects
So your clean method could be something like this:
from django.core.exceptions import ValidationError
class MyModel(models.Model):
def is_available(self):
#do check here
return result
def clean(self):
if not self.is_available():
raise ValidationError('Item already booked for those dates')
I haven't made use of it extensively, but seems like much less code than having to create a ModelForm, and then link that form in the admin.py file for use in django admin.
Pretty old post, but I think "use custom cleaning" is still the accepted answer. But it is not satisfactory. You can do as much pre checking as you want you still may get an exception in Model.save(), and you may want to show a message to the user in a fashion consistent with a form validation error.
The solution I found was to override ModelAdmin.changeform_view(). In this case I'm catching an integrity error generated somewhere down in the SQL driver:
from django.contrib import messages
from django.http import HttpResponseRedirect
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
try:
return super(MyModelAdmin, self).changeform_view(request, object_id, form_url, extra_context)
except IntegrityError as e:
self.message_user(request, e, level=messages.ERROR)
return HttpResponseRedirect(form_url)
The best way is put the validation one field is use the ModelForm... [ forms.py]
class FormProduct(forms.ModelForm):
class Meta:
model = Product
def clean_photo(self):
if self.cleaned_data["photo"] is None:
raise forms.ValidationError(u"You need set some imagem.")
And set the FORM that you create in respective model admin [ admin.py ]
class ProductAdmin(admin.ModelAdmin):
form = FormProduct
I've also tried to solve this and there is my solution- in my case i needed to deny any changes in related_objects if the main_object is locked for editing.
1) custom Exception
class Error(Exception):
"""Base class for errors in this module."""
pass
class EditNotAllowedError(Error):
def __init__(self, msg):
Exception.__init__(self, msg)
2) metaclass with custom save method- all my related_data models will be based on this:
class RelatedModel(models.Model):
main_object = models.ForeignKey("Main")
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.main_object.is_editable():
super(RelatedModel, self).save(*args, **kwargs)
else:
raise EditNotAllowedError, "Closed for editing"
3) metaform - all my related_data admin forms will be based on this (it will ensure that admin interface will inform user without admin interface error):
from django.forms import ModelForm, ValidationError
...
class RelatedModelForm(ModelForm):
def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data.get("main_object")
raise ValidationError("Closed for editing")
super(RelatedModelForm, self).clean() # important- let admin do its work on data!
return cleaned_data
To my mind it is not so much overhead and still pretty straightforward and maintainable.
from django.db import models
from django.core.exceptions import ValidationError
class Post(models.Model):
is_cleaned = False
title = models.CharField(max_length=255)
def clean(self):
self.is_cleaned = True
if something():
raise ValidationError("my error message")
super(Post, self).clean()
def save(self, *args, **kwargs):
if not self.is_cleaned:
self.full_clean()
super(Post, self).save(*args, **kwargs)