I'm having a hard time trying to figure out the following code:
from django import forms
from django.contrib.auth.hashers import check_password
class CheckPasswordForm(forms.Form):
password = forms.CharField(label='password_check', widget=forms.PasswordInput(
attrs={'class': 'form-control',}),
)
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = self.user.password
if password:
if not check_password(password, confirm_password):
self.add_error('password', 'password is wrong')
here, I don't get the self.add_error('password', 'password is wrong') part. In the documentation, it says that the password here is the field in add_error('field', 'error').
So, is the error being added to the password field? or is it being added to the following part?
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
because if I have to access this error in the html template, I would have to do something like this,
{% if password_form.password.errors %}
and if it means accessing errors from password field, it should mean that the error is added to the password field... but the self.add_error part confuses me
Form clean method is responsible for validation of your form when the data is sent to it
normally it will run validation on all field using clean_<fieldname>() method and if there are errors store them in fieldname.errors
As documented
Since the field validation methods have been run by the time clean()
is called, you also have access to the form’s errors attribute which
contains all the errors raised by cleaning of individual fields.
Note that any errors raised by your Form.clean() override will not be
associated with any field in particular. They go into a special
“field” (called __all__)
if you don't want error to end into __all__ you can attach it to particular field in your case you added it to password
If you want to attach errors to a specific field in the form, you
need to call add_error().
Related
I am trying to prohibit duplicate signups from a single ip. Here is my code.
class SignupForm(forms.Form):
def signup(self, request, user):
ip = get_client_ip(request)
if UserProfile.objects.filter(registration_ip=ip).exists():
self.add_error(
None, "You cannot register more than one account from the same IP")
else:
user.userprofile.registration_ip = ip
user.userprofile.save()
user.save()
Currently, when a user registers having the same ip as another registered user, the form still validates. I need the form to return an error. Can anyone help? Thanks in advance.
(The above code is an override of the SignupForm of the django-allauth package)
The if statement works fine. At first I tried using raise ValidationError and that worked fine, but that is not good for UX. I need the form to invalidate and return my custom error on the form page.
Use the clean function:
def clean_registration_ip(self):
registration_ip = self.cleaned_data.get('registration_ip')
if : # your logic
raise forms.ValidationError("Your msg")
return registration_ip
That filtering, returns an istance and it does not check the IP if they are same or not just checks if exist.
First you need to make uniqe registration_ip via Model.
Then you can use clean method via forms like:
def clean_registration_ip(self,request):
registration_ip = self.cleaned_data['registration_ip']
if UserProfile.objects.filter(registration_ip=registration_ip).exists():
raise forms.ValidationError("This IP exist")
return registration_ip
In the admin site, I have a custom form. However, I want to override the save method so that if a certain keyword is entered, I do not save it into the database. Is this possible?
class MyCustomForm (forms.ModelForm):
def save(self, commit=True):
input_name = self.cleaned_data.get('input_name', None)
if input_name == "MyKeyword":
//Do not save
else:
return super(MyCustomForm, self).save(commit=commit)
but this returns the error:
AttributeError 'NoneType' object has no attribute 'save'
Edit
Following this: Overriding the save method in Django ModelForm
I tried:
def save(self, force_insert=False, force_update=False, commit=True):
m = super(MyCustomCategoryForm, self).save(commit=False)
input_name = self.cleaned_data.get('input_name', None)
if input_name != "MyKeyword":
m.save()
return m
But the admin site still creates a new entry if the input_name is "MyKeyword"
The save() method is supposed to return the object, whether saved or not. It should rather look like:
def save(self, commit=True):
input_name = self.cleaned_data.get('input_name')
if input_name == "MyKeyword":
commit = False
return super().save(commit=commit)
However, as you can see here and here, the form is already called with commit=False and the object is saved later by the save_model() method of your ModelAdmin. This is the reason why you got AttributeError 'NoneType' object has no attribute 'save'. If you check the traceback of the exception, I'm pretty sure the error comes from this line.
Thus you should, in addition, override your ModelAdmin's save_model() method:
def save_model(self, request, obj, form, change):
if form.cleaned_data.get('input_name') == 'MyKeyword':
return # Don't save in this case
super().save_model(request, obj, form, change)
# And you'll also have to override the `ModelAdmin`'s `save_related()` method
# in the same flavor:
def save_related(self, request, form, formsets, change):
if form.cleaned_data.get('input_name') == 'MyKeyword':
return # Don't save in this case
super().save_related(request, form, formsets, change)
Given your comment in this answer
It is working now but I just have one related question. The admin site will still display a green banner at the top saying "The model was added successfully". Can I override some method so that it is red and says "The model already exists"?
It looks that what you want to do is not overriding the save method but controlling form validation. Which is completely different. You just have to override your form's clean method.
from django import forms
def clean(self):
"""
super().clean()
if self.cleaned_data.get('input_name') == 'MyKeyword':
raise forms.ValidationError("The model already exists")
Or, even better since it looks like you want to clean a single field:
from django import form
def clean_input_name(self):
data = self.cleaned_data['input_name']
if data == 'MyKeyWord':
raise forms.ValidationError("The model already exists")
However, I guess input_name is also a field of your model and you don't want to raise this error only on one form but across all your project. In which case, what you are looking for are Model validators:
from django.core.exceptions import ValidationError
def validate_input_name(value):
if value == 'MyKeyword':
raise ValidationError("The model already exists")
I have a form wizard with 3 forms. The first form has only one field and uses that field to do a lookup in an external API to validate the data.
I noticed that the requests were taking unusually long, so I just added a print statement to
the form's init method and to the external API client call.
It seems that my first form is being initialized and cleaned exactly 28 times every time I execute that step in the form wizard.
My first form looks like this:
forms.py
class MyForm1(forms.Form):
issue_id = forms.CharField()
def __init__(self, *args, **kwargs):
print "init form 1"
super(MyForm1, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(MyForm1, self).clean()
print "clean issue id"
issue_id = cleaned_data.get("issue_id")
if issue_id is not None:
try:
issue = ApiCLient.get_issue(issue_id)
except APIError:
raise forms.ValidationError("Issue not found. Try again!")
else:
return cleaned_data
raise forms.ValidationError("Issue not found. Try again!")
The wizard stuff is pretty standard from the Django Docs, but here it goes:
views.py
class MyWizard(SessionWizardView):
def done(self, form_list, **kwargs):
# some logic at the end of the wizard
urls.py
wizard_forms = [MyForm1, MyForm2, MyForm3]
...
url(r'wizard/$', login_required(views.MyWizard.as_view(wizard_forms))),
As I said before, executing the first step results in
init form 1
clean issue id
Being printed to console exactly 28 times.
Any ideas? Is this a feature or a bug in the Django Form Wizard?
I've had a read of this
http://docs.b-list.org/django-registration/0.8/backend-api.html
and i've had a shot at making my own backend. I am doing this because I want to create a backend that disallows having the same email used for registration, and I wanted to change the email-error message. I also wanted to add in my own field!
Here is what I came up with:
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from registration.forms import attrs_dict
class customRegistrationForm(RegistrationForm):
email2 = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("Confirm email"))
def clean_email(self):
"""
Validate that the email is alphanumeric and is not already
in use.
"""
try:
email = User.objects.get(email__iexact=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError(_("That email already exists - if you have forgotten your password, go to the login screen and then select \"forgot password\""))
def clean(self):
"""
Verifiy that the values entered into the two email fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'email' in self.cleaned_data and 'email2' in self.cleaned_data:
if self.cleaned_data['email'] != self.cleaned_data['email2']:
raise forms.ValidationError(_("The two email fields didn't match."))
return super(RegistrationForm,clean)
The above goes in my init.py file (whetever that is)
Then, I have in my urls.py code:
url(r'^accounts/register/$',
register,
{ 'backend': 'myapp.forms.customRegistrationForm' },
name='registration_register'),
... #other urls here!
Now, when i go to the /accounts/register page, I get the following error:
AttributeError at /accounts/register/
'customRegistrationForm' object has no attribute 'registration_allowed'
Which is weird. It seems to be telling me I need a "registration_allowed" method added to my subclass. BUT, the subclass is a subclass of the RegistrationForm, which works fine and doesn't have that stuff defined... I know I can add these members in, but it seems to beat the purpose of extension, right?
UPDATE
Here is the code now that it works!
I broke up the varying classes into varying init.py files in different folders - one called "forms" and one called "backends", both of which sit in a folder "djangoRegistration" under my main project.
/forms/init.py
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from registration.forms import attrs_dict
class customRegistrationForm(RegistrationForm):
def __init__(self, *args, **kw):
super(RegistrationForm, self).__init__(*args, **kw)
self.fields.keyOrder = [
'username',
'email',
'email2',
'password1',
'password2'
]
email2 = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("Confirm email"))
def clean_email(self):
"""
Validate that the email is alphanumeric and is not already
in use.
"""
try:
email = User.objects.get(email__iexact=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError(_("That email already exists - if you have forgotten your password, go to the login screen and then select \"forgot password\""))
def clean(self):
"""
Verifiy that the values entered into the two email fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'email' in self.cleaned_data and 'email2' in self.cleaned_data:
if self.cleaned_data['email'] != self.cleaned_data['email2']:
raise forms.ValidationError(_("The two email fields didn't match."))
return super(RegistrationForm,clean)
/backends/init.py
from registration.backends.default import DefaultBackend
from dumpstownapp.djangoRegistration.forms import customRegistrationForm
class customDefaultBackend(DefaultBackend):
def get_form_class(self, request):
"""
Return the default form class used for user registration.
"""
return customRegistrationForm
and finally, my urls.py just references the new backend:
url(r'^accounts/register/$',
register,
{ 'backend': 'myapp.djangoRegistration.backends.customDefaultBackend' },
name='registration_register'),
#more urls here! yay!
As a final note, I had to add some code to "order" the way the fields were presented, which is what the init method in the customRegistrationForm is doing
thanks!
You're trying to use a form as a backend, but that's not what a backend is at all. As the document you link to explains, a backend is a class that implements certain methods, including registration_allowed. The form doesn't implement any of those, which is not surprising, because it's meant for user input and validation, not the backend actions.
However, that page does give a hint as to the correct way to implement this. One of the methods that the backend can define is get_form_class(), which returns the form class to use. So, it seems that what you need is a custom backend that inherits from registration.backends.default.DefaultBackend and overrides just the get_form_class method, which simply returns customRegistrationForm.
I'm trying to make a form that handles the checking of a domain: the form should fail based on a variable that was set earlier in another form.
Basically, when a user wants to create a new domain, this form should fail if the entered domain exists.
When a user wants to move a domain, this form should fail if the entered domain doesn't exist.
I've tried making it dynamic overload the initbut couldn't see a way to get my passed variabele to the clean function.
I've read that this dynamic validation can be accomplished using a factory method, but maybe someone can help me on my way with this?
Here's a simplified version of the form so far:
#OrderFormStep1 presents the user with a choice: create or move domain
class OrderFormStep2(forms.Form):
domain = forms.CharField()
extension = forms.CharField()
def clean(self):
cleaned_data = self.cleaned_data
domain = cleaned_data.get("domain")
extension = cleaned_data.get("extension")
if domain and extension:
code = whoislookup(domain+extension);
#Raise error based on result from OrderFormStep1
#raise forms.ValidationError('error, domain already exists')
#raise forms.ValidationError('error, domain does not exist')
return cleaned_data
Overriding the __init__ is the way to go. In that method, you can simply set your value to an instance variable.
def __init__(self, *args, **kwargs):
self.myvalue = kwargs.pop('myvalue')
super(MyForm, self).__init__(*args, **kwargs)
Now self.myvalue is available in any form method.
Do you have a model that stores the domains? If so, you want to use a ModelForm and set unique=True on whichever field stores the actual domain in the model. As of Django 1.2, you can even do any additional validation inside the model, rather than the form.