Django Custom User Email Account Verification - django

I am looking to add email account verification in Django. I have attempted using the django-registration app to do so, but it doesn't appear that it has been updated to be fully compatible with custom user models which causes too many problems. Is there another reliable and well-documented app out there which will allow me to send a verification email on user registration in django?

How I handle the email registration personally:
First of all, my Profile extending Django Users (models.py):
class Profile(models.Model):
user = models.OneToOneField(User, related_name='profile') #1 to 1 link with Django User
activation_key = models.CharField(max_length=40)
key_expires = models.DateTimeField()
In forms.py, the Registration class :
class RegistrationForm(forms.Form):
username = forms.CharField(label="",widget=forms.TextInput(attrs={'placeholder': 'Nom d\'utilisateur','class':'form-control input-perso'}),max_length=30,min_length=3,validators=[isValidUsername, validators.validate_slug])
email = forms.EmailField(label="",widget=forms.EmailInput(attrs={'placeholder': 'Email','class':'form-control input-perso'}),max_length=100,error_messages={'invalid': ("Email invalide.")},validators=[isValidEmail])
password1 = forms.CharField(label="",max_length=50,min_length=6,
widget=forms.PasswordInput(attrs={'placeholder': 'Mot de passe','class':'form-control input-perso'}))
password2 = forms.CharField(label="",max_length=50,min_length=6,
widget=forms.PasswordInput(attrs={'placeholder': 'Confirmer mot de passe','class':'form-control input-perso'}))
#recaptcha = ReCaptchaField()
#Override clean method to check password match
def clean(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if password1 and password1 != password2:
self._errors['password2'] = ErrorList([u"Le mot de passe ne correspond pas."])
return self.cleaned_data
#Override of save method for saving both User and Profile objects
def save(self, datas):
u = User.objects.create_user(datas['username'],
datas['email'],
datas['password1'])
u.is_active = False
u.save()
profile=Profile()
profile.user=u
profile.activation_key=datas['activation_key']
profile.key_expires=datetime.datetime.strftime(datetime.datetime.now() + datetime.timedelta(days=2), "%Y-%m-%d %H:%M:%S")
profile.save()
return u
#Sending activation email ------>>>!! Warning : Domain name is hardcoded below !!<<<------
#The email is written in a text file (it contains templatetags which are populated by the method below)
def sendEmail(self, datas):
link="http://yourdomain.com/activate/"+datas['activation_key']
c=Context({'activation_link':link,'username':datas['username']})
f = open(MEDIA_ROOT+datas['email_path'], 'r')
t = Template(f.read())
f.close()
message=t.render(c)
#print unicode(message).encode('utf8')
send_mail(datas['email_subject'], message, 'yourdomain <no-reply#yourdomain.com>', [datas['email']], fail_silently=False)
Now, in views.py, we need to handle all that, let's go :
The register view:
def register(request):
if request.user.is_authenticated():
return redirect(home)
registration_form = RegistrationForm()
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
datas={}
datas['username']=form.cleaned_data['username']
datas['email']=form.cleaned_data['email']
datas['password1']=form.cleaned_data['password1']
#We generate a random activation key
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
usernamesalt = datas['username']
if isinstance(usernamesalt, unicode):
usernamesalt = usernamesalt.encode('utf8')
datas['activation_key']= hashlib.sha1(salt+usernamesalt).hexdigest()
datas['email_path']="/ActivationEmail.txt"
datas['email_subject']="Activation de votre compte yourdomain"
form.sendEmail(datas)
form.save(datas) #Save the user and his profile
request.session['registered']=True #For display purposes
return redirect(home)
else:
registration_form = form #Display form with error messages (incorrect fields, etc)
return render(request, 'siteApp/register.html', locals())
The activation views :
#View called from activation email. Activate user if link didn't expire (48h default), or offer to
#send a second link if the first expired.
def activation(request, key):
activation_expired = False
already_active = False
profile = get_object_or_404(Profile, activation_key=key)
if profile.user.is_active == False:
if timezone.now() > profile.key_expires:
activation_expired = True #Display: offer the user to send a new activation link
id_user = profile.user.id
else: #Activation successful
profile.user.is_active = True
profile.user.save()
#If user is already active, simply display error message
else:
already_active = True #Display : error message
return render(request, 'siteApp/activation.html', locals())
def new_activation_link(request, user_id):
form = RegistrationForm()
datas={}
user = User.objects.get(id=user_id)
if user is not None and not user.is_active:
datas['username']=user.username
datas['email']=user.email
datas['email_path']="/ResendEmail.txt"
datas['email_subject']="Nouveau lien d'activation yourdomain"
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
usernamesalt = datas['username']
if isinstance(usernamesalt, unicode):
usernamesalt = usernamesalt.encode('utf8')
datas['activation_key']= hashlib.sha1(salt+usernamesalt).hexdigest()
profile = Profile.objects.get(user=user)
profile.activation_key = datas['activation_key']
profile.key_expires = datetime.datetime.strftime(datetime.datetime.now() + datetime.timedelta(days=2), "%Y-%m-%d %H:%M:%S")
profile.save()
form.sendEmail(datas)
request.session['new_link']=True #Display: new link sent
return redirect(home)
Finally, in urls.py:
url(r'^register/$', 'register'),
url(r'^activate/(?P<key>.+)$', 'activation'),
url(r'^new-activation-link/(?P<user_id>\d+)/$', 'new_activation_link'),
With all that you should have something to start with, use the appropriate templatetags in the .txt emails and HTML and it should work.
NB: This code isn't perfect, there is duplication (for instance, the generation of the random key could be defined in a function), but it does the job. Also: the activation key is not generated using proper cryptographic functions. An alternative is to use a function like the following to generate the keys:
from django.utils.crypto import get_random_string
def generate_activation_key(username):
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!##$%^&*(-_=+)'
secret_key = get_random_string(20, chars)
return hashlib.sha256((secret_key + username).encode('utf-8')).hexdigest()
NB2: Django send_mail doesn't provide any tools to authenticate your emails. If you want to authenticate your emails (DKIM, SPF), I advise you to look into this: https://djangosnippets.org/snippets/1995/
NB3: There is a security issue with the view new_activation_link: it should check if the user requesting the re-send is the right one and also if he isn't already authenticated. I let you correct that.

You may also be interested in the simple but powerful django-verified-email-field.
Simply use VerifiedEmailField in Your forms:
from django import forms
from verified_email_field.forms import VerifiedEmailField
class RegistrationForm(forms.ModelForm):
email = VerifiedEmailField(label='email', required=True)
Or in Your models:
from django.db import models
from verified_email_field.models import VerifiedEmailField
class User(models.Model):
email = VerifiedEmailField('e-mail')
It renders two input fields: e-mail and verification code. The verification code is sent to the e-mail address using AJAX or during field's clean if there is no valid code for given e-mail, so it works even without javascript.

Related

AttributeError: 'AcceptInvite' object has no attribute 'email'

Im doing some unit testing in my Django project, and am getting error
"AttributeError: 'SignUp' object has no attribute 'email'"
when I run this test.
def test_signup(self):
response = self.c.post('/accounts/signup/', {'email': 'test#test.com', 'password': 'test123', 'password_conf': 'test123',
'org_name': 'test org', 'org_username': 'test org username', 'invite': '4013'})
code = response.status_code
self.assertTrue(code == 200)
The view this is testing simply takes a signup form, and creates a new account with it.
def signup(request):
# """Register a new account with a new org."""
if request.method == "POST":
form = SignUp(request.POST)
if not form.email or not form.password:
raise Exception("Email and Password are required")
if form.password != form.password_conf:
raise Exception("Password does not match confirmation")
if not form.org_name or not form.org_username:
raise Exception('Organization name and username are required')
if not form.invite:
raise Exception('Invitation code is required')
if form.is_valid():
cleaned_data = form.cleaned_data
email = cleaned_data['email']
password = cleaned_data['password']
org_name = cleaned_data['org_name']
org_username = cleaned_data['org_username']
invite_token = cleaned_data['invite']
invitation = OrgInvite.objects.get(token=invite_token)
if invitation.used:
raise Exception("invitation code is invalid")
account = Account(email=email, password=password)
account.save()
org = Org(org_name=org_name, org_username=org_username)
org.save()
invitation.used = False
invitation.save()
login(request)
# Send Email
md = mandrill.Mandrill(settings.MANDRILL_API_KEY)
t = invite_token.replace(' ', '+')
url = "https://www.humanlink.co/verify/{}".format(t)
message = {
'global_merge_vars': [
{'name': 'VERIFICATION_URL', 'content': url},
],
'to': [
{'email': account.email},
],
}
message['from_name'] = message.get('from_name', 'Humanlink')
message['from_email'] = message.get('from_email', 'support#humanlink.co')
try:
md.messages.send_template(
template_name='humanlink-welcome', message=message,
template_content=[], async=True)
except mandrill.Error as e:
logging.exception(e)
raise Exception('Unknown service exception')
The Signup form has an email field, and the data in request.POST should have the email I am sending it with my Client's post method being used in my unit test, so I am really not sure why it still wouldn't have an 'email' attribute.
Form:
class SignUp(forms.Form):
email = forms.EmailField()
password = forms.CharField()
password_conf = forms.CharField()
org_name = forms.CharField()
org_username = forms.CharField()
invite = forms.CharField()
You code suffers from multiple errors. To address your question, in your view method signup you were creating a form, but you shouldn't do form.email or form.password because that's not how django handles form data.
Other related issues, first, you need to call form.is_valid() before you could get any data from form object. Even so, you should use form.cleaned_data['email'] to access the form data.
Secondly. You shouldn't do empty check like that. If you put:
email = forms.EmailField(required=True)
django will automatically verify the emptiness for you already.
Thirdly, raising Exception in views.py method doesn't get your form to return the message to the template you want. If you have custom form validation, you should do it in form class's clean method.
Please check django doc about how to use form properly.

Django AbstractUsers create_user not working properly

I have my models defined as:
class MyUser(AbstractUser):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=256)
phone = models.IntegerField()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'name', 'phone']
MyUser._meta.get_field_by_name('email')[0]._unique=True
now when I am calling create_user by sending all the arguments in the form
my view.py looks like
def new_user(request):
email = request.POST.get('email','')
phone = request.POST.get('phone','')
name = request.POST.get('name','')
password = request.POST.get('password','')
confirm_pass = request.POST.get('confirm_password','')
if password and confirm_pass and password == confirm_pass:
try:
user = MyUser.objects.get(email=email)
except MyUser.DoesNotExist:
user = None
if user:
message = 'User with the following email id already exists'
return render(request, 'signup.html', {'message':message})
else:
user = My.objects.create_user(email, email, name, phone, password)
user.save()
login(request, user)
return render(request, 'details.html', {'username':request.user.username})
else:
message = 'Paaswords does not match each other'
return render(request, 'signup.html', {'message':message})
Now each time this function executes it shows me the following error
create_user() takes at most 4 arguments (6 given)
I could'nt understand why is create_user not working.
create_user function takes the parameters as *args, so you need to call the function like that:
create_user(email = email, name = name, phone = phone, password = password)
or easily pass your request.POST:
create_user(request.POST)
I'm seeing that your view is a little bit complex, have you ever try Class Based Views?
With class based views you can create method do deal with get querysets and form rendering.
That a look https://docs.djangoproject.com/en/dev/topics/class-based-views/
At last advice, you can create a register_form to deal with data validation:
https://docs.djangoproject.com/en/dev/topics/forms/
You're sending 6 arguments from your views.py where as you have only 4 required.
Note that password and confirm pass count as your 2 additional arguments. That's what I think anyway
Take a look at THIS example. It helped me with creating my abstract user.
If you'd like some code examples, HERE is a good breakdown (if you're on Django 1.5 that is)

Django 1.4 Modifying Custom Account Model for Uniqueness of E-mail Addresses

I've already defined a custom user account that utilizes several built in's from the auth User model and, using the user link, links these with some additional fields that I needed to register a user on the database.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
relevant from my models.py
# additional model to incorporate our custom fields to the auth user model
class Account(models.Model):
user = models.OneToOneField(User) #link (pointer) to the users other information in User model
birthdate = models.DateField(blank = True, ) # True makes this field optional
gender = models.CharField(max_length = 1, choices = GENDER_CHOICE, null = True, blank = True)
def __unicode__(self): # define a unicode for the user to access
return u'%s %s' % (self.user.first_name, self.user.last_name) # return first and last name in shell
# custom form to gather information from the user for a new account
class UserRegistration(UserCreationForm):
#class RegistrationForm(forms.ModelForm):
class Meta:
model = User
fields = ("first_name", "last_name", "email", "username", "password1", "password2",)
# ensures uniqueness of user email addresses when registering
def clean_email(self):
print "In custom creation"
email = self.cleaned_data.get(email = 'email')
username = self.cleaned_data.get(username = 'username')
# checks if email address already exists
if User.objects.filter(email__iexact = self.cleaned_data['email']):
print "Email exists"
# if email and User.objects.filter(email__iexact = email).exclude(username=username).exists():
raise forms.ValidationError(u'Email Address is currently used by another user.')
return email
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
relevant from views.py
def Main(request):
if request.user.is_authenticated():
latest_events = Event.objects.all().order_by('-created')[:10] # Returns latest 10 events
my_events = Event.objects.filter(creator=request.user)[:10] # Returns up to 10 events created by current User
my_calendars = Calendar.objects.filter(creator=request.user) # Returns all calendars created by the user
authForm = None
loginForm = None
headerType = "header.html"
else:
latest_events = None
my_events = None
my_calendars = None
headerType = "header_main.html"
authForm = UserRegistration(request.POST or None)
print "Creating account UserRegistration" # TESTING PRINT
print "User email = %s " %(User._meta.get_field('email'))
if request.method == 'POST':
if authForm.is_valid():
newUser = authForm.save(commit=False)
newUser.save()
newUser = authenticate(username=request.POST['username'], password=request.POST['password1'])
login(request, newUser)
return HttpResponseRedirect('/signup/')
....
....
more code on success redirection
....
....
~~~~~~~~~~~~~~~~~~~~~~~~~~~
(I hope I didn't post too much code, just wanted to be thorough)
As you can see there are a few commented out attempts I've made recently. I tried using thee built in RegistrationFormUniqueForm() by downloading 'registration' but I don't actually want to make a new registration form since I already have a working one.
I moved on to trying another suggestion, the code under the comment
# custom form to display additional sign up information
When I tried registering a new user with an already registered email it did not throw any error and allowed the registration. I also tried changing the users email and it allowed the change to an already taken email with no objection.
Can anyone suggest a method for making user registration maintain a unique lock on each individual attempting to register with an email address that may already be taken? As well as preventing them from changing their email to one that is taken by a current user?
Thanks in advance.
EDIT: I made changes to the Models registration form def clean_email() and the def in views to reflect what I currently have that still does not work.
The indentation of your clean_email methods is wrong for both forms. At the moment, they are methods of the Meta class, so will never be called. For example, the registration form should be:
class RegistrationForm(UserCreationForm):
#class RegistrationForm(forms.ModelForm):
class Meta:
model = User
fields = ("first_name", "last_name", "email", "username", "password1", "password2",)
def clean_email(self):
"""ensures uniqueness of user email addresses when registering"""
email = self.cleaned_data.get('email')
This might not be the real problem -- it's easy to get the indentation wrong when pasting code into stack overflow. If that's the case, I'll delete the answer.

Updating First and Last Name During Registration - Django CMS

I'm currently working on an application for user registration on a Django CMS site.
My registration form is working just fine and creates the user, plus adds my custom fields to my registration model.
What I'd like to know is how to update auth_user to contain the user's first and last name.
Currently the user is being created in this manner:
import newform
from betaregistration.newform import RegistrationFormZ
def user_created(sender, user, request, **kwargs):
form = RegistrationFormZ(request.POST)
data = newform.BetaProfile(user=user)
data.first_name = form.data["first_name"]
data.last_name = form.data["last_name"]
data.address = form.data["address"]
data.city = form.data["city"]
data.state = form.data["state"]
data.postal_code = form.data["postal_code"]
data.country = form.data["country"]
data.phone= form.data["phone"]
data.email = form.data["email"]
data.save()
# this was the solution
user.first_name = form_data['first_name']
user.last_name = form_data['last_name']
user.save()
# end solution
from registration.signals import user_registered
user_registered.connect(user_created)
Any help is appreciated. I'm using django-registration for my registration.
Since you are passing user into user_created could you simply modify things as the following:
data.first_name = user.first_name = form.data["first_name"]
data.last_name = user.last_name = form.data["last_name"]
....
user.save()
I may be missing something but if you really do have access to user in user_created that should work. You could also potentially do the same using request.user instead of user.

django testing problems

This is my view that I want to be tested.
def logIn(request):
"""
This method will log in user using username or email
"""
if request.method == 'POST':
form = LogInForm(request.POST)
if form.is_valid():
user = authenticate(username=form.cleaned_data['name'],password=form.cleaned_data['password'])
if user:
login(request,user)
return redirect('uindex')
else:
error = "Nie prawidlowy login lub haslo.Upewnij sie ze wpisales prawidlowe dane"
else:
form = LogInForm(auto_id=False)
return render_to_response('login.html',locals(),context_instance=RequestContext(request))
And here's the test
class LoginTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_response_for_get(self):
response = self.client.get(reverse('logIn'))
self.assertEqual(response.status_code, 200)
def test_login_with_username(self):
"""
Test if user can login wit username and password
"""
user_name = 'test'
user_email = 'test#test.com'
user_password = 'zaq12wsx'
u = User.objects.create_user(user_name,user_email,user_password)
response = self.client.post(reverse('logIn'),data={'name':user_name,'password':user_password},follow=True)
self.assertEquals(response.request.user.username,user_name)
u.delete()
And when i run this test i got failure on test_login_with_username:
AttributeError: 'dict' object has no attribute 'user'
When i use in views request.user.username in works fine no error this just fails in tests. Thanks in advance for any help
edit:Ok I replace the broken part with
self.assertEquals(302, response.status_code)
But now this test breaks and another one too.
AssertionError: 302 != 200
Here is my code for the view that now fail. I want email and username to be unique.
def register(request):
"""
Function to register new user.
This function will have to care for email uniqueness,and login
"""
if request.method == 'POST':
error=[]
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
email = form.cleaned_data['email']
if form.cleaned_data['password'] == form.cleaned_data['password_confirmation']:
password = form.cleaned_data['password']
if len(User.objects.filter(username=username)) == 0 and len(User.objects.filter(email=email)) == 0:
#email and username are bouth unique
u = User()
u.username = username
u.set_password(password)
u.email = email
u.is_active = False
u.is_superuser = False
u.is_active = True
u.save()
return render_to_response('success_register.html',locals(),context_instance=RequestContext(request))
else:
if len(User.objects.filter(username=username)) > 0:
error.append("Podany login jest juz zajety")
if len(User.objects.filter(email=email)) > 0:
error.append("Podany email jest juz zajety")
else:
error.append("Hasla nie pasuja do siebie")
#return render_to_response('register.html',locals(),context_instance=RequestContext(request))
else:
form = RegisterForm(auto_id=False)
return render_to_response('register.html',locals(),context_instance=RequestContext(request))
And here is the test that priviously work but now it is broken
def test_user_register_with_unique_data_and_permission(self):
"""
Will try to register user which provided for sure unique credentials
And also make sure that profile will be automatically created for him, and also that he he have valid privileges
"""
user_name = 'test'
user_email = 'test#test.com'
password = 'zaq12wsx'
response = self.client.post(reverse('register'),{'username': user_name,'email':user_email,
'password':password,'password_confirmation':password},follow=True)
#check if code is 200
self.assertEqual(response.status_code, 200)
u = User.objects.get(username=user_name,email = user_email)
self.assertTrue(u,"User after creation coudn't be fetched")
self.assertFalse(u.is_staff,msg="User after registration belong to staff")
self.assertFalse(u.is_superuser,msg="User after registration is superuser")
p = UserProfile.objects.get(user__username__iexact = user_name)
self.assertTrue(p,"After user creation coudn't fetch user profile")
self.assertEqual(len(response.context['error']),0,msg = 'We shoudnt get error during valid registration')
u.delete()
p.delete()
End here is the error:
AssertionError: We shoudnt get error during valid registration
If i disable login test everything is ok. How this test can break another one? And why login test is not passing. I try it on website and it works fine.
The documentation for the response object returned by the test client says this about the request attribute:
request
The request data that stimulated the response.
That suggests to me one of two things. Either it's just the data of the request, or it's request object as it was before you handled the request. In either case, you would not expect it to contain the logged in user.
Another way to write your test that the login completed successfully would be to add follow=False to the client.post call and check the response code:
self.assertEquals(302, response.status_code)
This checks that the redirect has occurred.
response.request is not the HttpRequest object in the view you are expecting. It's a dictionary of data that stimulated the post request. It doesn't have the user attribute, hence the AttributeError
You could rewrite your test to:
use the RequestFactory class introduced in Django 1.3 and call logIn in your test directly instead of using client.post.
inspect client.session after the post to check whether the user has been logged in.
Why one failing test can break another
When you edited the question, you asked
How this test can break another one?
The test_login_with_username was failing before it reached u.delete, so the user created in that test was not deleted. That caused test_user_register_with_unique_data_and_permission because the user test already existed.
If you use the django.test.TestCase class, the database will be reset in between each test, so this wouldn't be a problem.