django form is_valid always fails (extending django-registration form) - django

I'm trying to extend Django registration to include my own registration form. In principle this is fairly simple. I just have to write my own form (CustomRegistrationForm) which is a child of the original one (RegistrationForm). Then I can process my specific input by using the user_registered signal of django registration.
So here is what I did:
urls.py:
from django.conf.urls import patterns, include, url
from registration.views import register
from forms import CustomRegistrationForm
from django.contrib import admin
import regbackend
admin.autodiscover()
urlpatterns = patterns('',
url(r'^register/$', register, {'backend': 'registration.backends.default.DefaultBackend', 'form_class': CustomRegistrationForm, 'template_name': 'custom_profile/registration_form.html'},
name='registration_register'),
)
regbackend.py:
from django import forms
from models import UserProfile
from forms import CustomRegistrationForm
def user_created(sender, user, request, **kwargs):
form = CustomRegistrationForm(data=request.POST, files=request.FILES)
if form.is_valid(): # HERE: always fails
user_profile = UserProfile()
user_profile.user = user
user_profile.matriculation_number = form.cleaned_data['matriculation_number']
user_profile.save()
from registration.signals import user_registered
user_registered.connect(user_created)
forms.py:
from models import UserProfile
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
attrs_dict = {'class': 'required'}
class CustomRegistrationForm(RegistrationForm):
matriculation_number = forms.CharField(widget=forms.TextInput(attrs=attrs_dict),
label=_("Matriculation number"),
max_length=12,
error_messages={'invalid': _("This value has to be unique and may contain only numbers.")},
initial=108)
def clean_matriculation_number(self):
print "Validating matriculation number."
data = self.cleaned_data['matriculation_number']
if len(data) != 12:
raise forms.ValidationError(_("Matriculation number consists of exactly 12 digits."))
return data
So the problem is the is_valid() function, because it always returns False. Even if there are no errors! So what is wrong? I spent hours on this one and I have no idea anymore :(
Any help is much appreciated!!

The reason form.is_valid() fails is probably because the form's "clean_username" function checks if the username passed to it already exists. Since the signal is sent after the User object is created and added to the database, the form will fail to pass this test every time. My guess would be if you logged form.cleaned_data after is_valid() returns False, you'll get a list of all of the fields except the username.
form.data might not contain changed values for the fields if the form clean_ function makes any changes (I couldn't find much documentation on form.data).
To avoid the potential problems this might cause, I made two classes for my custom registration form:
# I use this one for validating in my create_user_profile function
class MyRegistrationFormInternal(forms.Form):
# Just an example with only one field that holds a name
name = forms.CharField(initial="Your name", max_length=100)
def clean_name(self):
# (Optional) Change the name in some way, but do not check to see if it already exists
return self.cleaned_data['name'] + ' foo '
# This one is actually displayed
class MyRegistrationForm (MyRegistrationFormInternal):
# Here is where we check if the user already exists
def clean_name(self):
modified_name = super(MyRegistrationForm, self).clean_name()
# Check if a user with this name already exists
duplicate = (User.objects.filter(name=modified_name)
if duplicate.exists():
raise forms.ValidationError("A user with that name already exists.")
else:
return modified_name
Then, instead of using the form.data (which may still be "unclean" in some ways), you can run your POST data through MyRegistrationFormInternal, and is_valid() shouldn't always return false.
I realize this isn't the cleanest solution, but it avoids having to use the (possibly raw) form.data.

Ok I think I solved it (more or less).
I'm still not really sure, why the form did not validate. But as I said I was extending django-registration and the 'register' view already called is_valid() of the form, so I can assume that the form is valid when I process the posted data any futher. The view then calls the backend
backend.register(request, **form.cleaned_data)
with the request and the cleaned data (which is just username, email and password). So I can't use it for registration because my additional information is missing. The backend then fires the signal that I am using and what I did is, is that I created the form again with the provided request. This form, however, will NOT validate (and I tried everything!!) I looked it up, I am doing the exact same thing as django-registration, but it's not working in my code.
So I did not really solve the problem, because the form is still not validating. But I found peace with this, when I realized that the form was already validated by the 'register' view. So I am using form.data[..] instead of form.cleaned_data[..] now which shouldn't be a problem...

Related

Write a django Alluth adapter that checks if a value i unique

Im trying to check if a value for pid is uniqe but i cant manage to make alluth adapter to find the pid field. Any tips? Nothin happend, but if i change pid value to email the code works on the email field. I cant get the pid field to work whit this function. Maybe im referencing it in a wrong way and the filed does not belong to the default account adaptet-
class PidMaxAdapter(DefaultAccountAdapter):
def clean_pid(self, pid, user):
user.profile.pid = pid
if len(pid) > 9:
raise ValidationError('Please enter a username value\
less than the current one')
# For other default validations.
return DefaultAccountAdapter.clean_pid(self, pid)
Create a custom signup form and do your validation in there.
Here's an example where I have a setting to disable recaptcha
from django import forms
from django.conf import settings
from allauth.account.forms import SignupForm as BaseSignupForm
from captcha.fields import ReCaptchaField
from .widgets import RefreshingCaptchaV3
class SignupForm(BaseSignupForm):
""" Our signup form to integrate captcha """
captcha = ReCaptchaField(
label='',
widget=RefreshingCaptchaV3()
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if settings.DISABLE_RECAPTCHA:
del self.fields['captcha']
You can get allauth to use your custom forms with the following setting;
ACCOUNT_FORMS = {
'change_password': 'apps.accounts.forms.ChangePasswordForm',
'reset_password': 'apps.accounts.forms.ResetPasswordForm',
'signup': 'apps.accounts.forms.SignupForm',
}
If you pass a pid to the form, you can then make it an attribute of the form instance in the __init__ and assign it to the profile during the save. Or have pid as a field, pass it a value in the initial data for the form and have it as a hidden input (forms.HiddenInput() is the widget).
Then in the clean_pid() you could validate that the value is unique against the other rows in the table. Depending on what you're doing with this data, it'd make most sense to use something like a UUID so that you don't have to worry so much about clashing values.

Django Invalidate Form Test If Extra Field Supplied

I have had some issue where a leak of User variables onto the registration form allowed users to set these on user creation.
Can I ensure through testing that this will not be possible again?
Let's say for example I don't want it to be possible that users can set their own 'coupon_code'. How would I test for that?
Accounts/forms.py:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import User
class RegisterUserForm(UserCreationForm):
email = forms.EmailField(required=True, help_text='Required.')
class Meta(UserCreationForm.Meta):
model = User
fields = ('username', 'password1', 'password2', 'email')
exclude = ('coupon_code',)
Accounts/tests/test_forms.py:
from django.test import TestCase
from Accounts.forms import RegisterUserForm, UpdateUserForm
# Create your tests here.
class RegisterUserFormTest(TestCase):
##classmethod
#def setUpTestData(cls):
# Set up non-modified objects used by all test methods
def valid_data(self):
return {'username':'abc', 'email':'abc#abc.com', 'password1': 'test123hello', 'password2':'test123hello'}
def test_register_user_form_cannot_set_coupon_code(self):
data = self.valid_data()
data['coupon_code'] = '42'
form = RegisterUserForm(data=data)
self.assertFalse(form.is_valid())
Currently the above test fails, as the form is considered valid, even though excluded field is present
From what I understand, the is_valid() method ignores extra data passed to Form. If is_valid() it returns True, that only means data was valid for the prescribed fields, and only those fields will be in the cleaned_data dictionary (Django docs about this).
I think the test you want to perform must be done on the view function that uses this form. It could look like this:
from django.test import Client
def test_your_user_creation_view(self):
c = Client()
c.post(reverse(<your view name>),<valid data with 'coupon_code':42>)
new_user = User.objects.get(username='abc')
self.assertTrue(new_user.coupon_code != '42')
self.assertEqual(new_user.coupon_code,<your default value for coupon_code>)

Django password_reset Form email validation

Django password_reset Form does not check if email exists or not. Also validates form whatever email address is given.
The question is, how do i check and throw error for non existing emails with custom form?
By the way, i found below solution at here but it is not working for me. (using Django 2.1). And if this should work, i couldn't found what i am missing.
forms.py
class EmailValidationOnForgotPassword(PasswordResetForm):
def clean_email(self):
email = self.cleaned_data['email']
if not User.objects.filter(email__iexact=email, is_active=True).exists():
raise ValidationError("There is no user registered with the specified email address!")
return email
urls.py
path('sifre-sifirla/', PasswordResetView.as_view(), {'password_reset_form':EmailValidationOnForgotPassword}, name='password_reset'),
Thank you in advance.
EDIT:
For other users information, question is answered by #Alasdair and working but #pypypy 's point of view is also important.
changes in urls.py as below:
path('sifre-sifirla/', PasswordResetView.as_view(form_class=EmailValidationOnForgotPassword), name='password_reset'),
Summarizing the discussion above with parts taken from #Alasdair
#forms.py
from django.contrib.auth.forms import PasswordResetForm
class EmailValidationOnForgotPassword(PasswordResetForm):
def clean_email(self):
email = self.cleaned_data['email']
if not User.objects.filter(email__iexact=email, is_active=True).exists():
msg = _("There is no user registered with the specified E-Mail address.")
self.add_error('email', msg)
return email
And
#urls.py
from accounts.forms import EmailValidationOnForgotPassword
path('sifre-sifirla/', PasswordResetView.as_view(form_class=EmailValidationOnForgotPassword), name='password_reset'),
#pypypy, is correct in saying that this approach can be used to obtain usernames. One way to reduce this issue is to respond with a 429 Too Many Requests as soon an user tries 3 different E-Mails. That can be achived using for example django-ratelimit

Automatically assign User to user group after register/sign up form submission - Django

I am relatively new to Django, and web development in general. I'm trying to build a website with two types of users, customers, and suppliers. I need to be able to show these two types of customers different things on the website. For example, different links in the header section for the suppliers.
I am under the impression the best way to do this is to create two user groups ('suppliers' and 'customers') in my /admin, create two different sign up forms (one for suppliers and one for customers), and send each to their respective user group on sign up form submission. From there I can decide what the user sees based on their user group. Correct?
Unfortunately, I'm nearly at my wits end with this! I've created the user groups, created the different sign-up forms, but for the last 2 days I have been trying to figure out how to send the signups to their respective group and I just can't manage to do it! I've searched high and low and tried every suggested line of code I could find: no luck.
Most of the stuff I have tried is along these lines:
views.py
from __future__ import unicode_literals
from django.contrib.auth import (
authenticate,
get_user_model,
login,
logout,
)
from django.contrib.auth.models import User, Group
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render, get_object_or_404
from .forms import SupplierRegisterForm
def supplier_signup_view(request):
form = SupplierRegisterForm(request.POST or None)
if form.is_valid():
user = form.save(commit=False)
user.groups.add(Group.objects.get(name='suppliers'))
password = form.cleaned_data.get('password')
user.set_password(password)
user.save()
new_user = authenticate(username=user.username, password=password)
login(request, new_user)
if next:
return redirect(next)
return redirect("/")
context = {
"form": form,
"title": title,
}
return render (request, "supplier_signup.html", context)
forms.py
from django import forms
from django.contrib.auth import (
authenticate,
get_user_model,
login,
logout,
)
from django.contrib.auth.models import User, Group
User = get_user_model()
class SupplierRegisterForm(forms.ModelForm):
username = forms.CharField()
email = forms.EmailField(label="Email Address")
email2 = forms.EmailField(label="Confirm Email", widget=forms.TextInput(attrs={'autocomplete':'false'}))
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = [
'username',
'email',
'email2',
'password',
]
def clean_email2(self):
email = self.cleaned_data.get('email')
email2 = self.cleaned_data.get('email2')
if email != email2:
raise forms.ValidationError("Emails must match")
email_qs = User.objects.filter(email=email)
if email_qs.exists():
raise forms.ValidationError("This email has already been registered")
return email
It would be much appreciated if anyone could give me a beginners walkthrough of how I would go about automatically sending users to a user group on form submission. Please include all of the code I would need to use, whether in settings, urls.py, models.py, forms.py, views.py, or templates (including imports etc), and any commands I would need to perform and when (eg migrate).
Many thanks!
EDIT: I've changed the answer to make use of Django's API.
Call user.save_m2m() after user.save():
if form.is_valid():
...
user.save()
user.save_m2m()
...
Explanation (from Django docs):
If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you've manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.

Django - how to add email to required things

I create registration form:
#urls.py
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
from account.views import Register
urlpatterns = patterns('',
url(r'^register/$', Register.as_view(template_name='account/register.html')),
)
#views.py
from django.views.generic import CreateView
from django.contrib.auth.models import User
class Register(CreateView):
model = User
success_url = '/account/'
And i have question: how I can add that email be require (now I must only enter username, password and 2 times time).
#edit
And how "repair" password? When i create user (in this form) and then go to admin panel, in user i see "Invalid password format or unknown hashing algorithm.". How i can repair this?
The reason that email is not required is because you're using a ModelForm, which takes a lot of cues from the underlying User model. Specifically, the required=True attribute is not present on the email field of the model.
One solution is to create your own form with the necessary attributes, perhaps by using a ModelForm and adding a required email field.
Another solution, and probably the better one, is to use something like django-registration as mentioned by Aamir Adnan in the comments to your question. It'll simplify things a lot for you.
As far as your repair password goes, you can't set the password to a raw string value as you're doing with your CreateView. To set a password, you have to call user.set_password(raw_string) which will take care of hashing and salting for you. Look how the built in UserCreationForm works, and try to mimic it if you decide to build the form yourself, rather than using a library (you shouldn't).
To solve these two problems you can use form like:
class UserCreationForm(forms.ModelForm):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(UserCreationForm).__init__(*args, **kwargs)
self.fields['email'].required = True
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
class Register(CreateView):
model = User
form_class = UserCreationForm
success_url = '/account/'
But there'll be other problems like duplication of username.