Skip to content

Instantly share code, notes, and snippets.

@fohlin
Created January 8, 2011 18:50
Show Gist options
  • Save fohlin/771052 to your computer and use it in GitHub Desktop.
Save fohlin/771052 to your computer and use it in GitHub Desktop.
A version of Django's UserCreationForm that uses email instead of username, with some nifty features. (Maybe not super robust yet, in terms of concurrency...)
import re
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class UniqueUserEmailField(forms.EmailField):
"""
An EmailField which only is valid if no User has that email.
"""
def validate(self, value):
super(forms.EmailField, self).validate(value)
try:
User.objects.get(email = value)
raise forms.ValidationError("Email already exists")
except User.MultipleObjectsReturned:
raise forms.ValidationError("Email already exists")
except User.DoesNotExist:
pass
class ExtendedUserCreationForm(UserCreationForm):
"""
Extends the built in UserCreationForm in several ways:
* Adds an email field, which uses the custom UniqueUserEmailField,
that is, the form does not validate if the email address already exists
in the User table.
* The username field is generated based on the email, and isn't visible.
* first_name and last_name fields are added.
* Data not saved by the default behavior of UserCreationForm is saved.
"""
username = forms.CharField(required = False, max_length = 30)
email = UniqueUserEmailField(required = True, label = 'Email address')
first_name = forms.CharField(required = True, max_length = 30)
last_name = forms.CharField(required = True, max_length = 30)
def __init__(self, *args, **kwargs):
"""
Changes the order of fields, and removes the username field.
"""
super(UserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['email', 'first_name', 'last_name',
'password1', 'password2']
def __generate_username(self, email):
"""
A simple way of deriving a username from an email address.
Hat tip: http://bit.ly/eIUR5R
>>> User.objects.all().order_by('-id')[0].id
1
>>> self.__generate_username("[email protected]")
abcabc2
>>> self.__generate_username("[email protected]")
heysup3
"""
# TODO: Something more efficient?
highest_user_id = User.objects.all().order_by('-id')[0].id
leading_part_of_email = email.split('@',1)[0]
leading_part_of_email = re.sub(r'[^a-zA-Z0-9+]', '',
leading_part_of_email)
truncated_part_of_email = leading_part_of_email[:3] \
+ leading_part_of_email[-3:]
derived_username = truncated_part_of_email + str(highest_user_id+1)
return derived_username
def clean(self, *args, **kwargs):
"""
Normal cleanup + username generation.
"""
cleaned_data = super(UserCreationForm, self).clean(*args, **kwargs)
if cleaned_data.has_key('email'):
cleaned_data['username'] = self.__generate_username(
cleaned_data['email'])
return cleaned_data
def save(self, commit=True):
"""
Saves the email, first_name and last_name properties, after the normal
save behavior is complete.
"""
user = super(UserCreationForm, self).save(commit)
if user:
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
@nueverest
Copy link

This helped me tremendously. Thank you.

Here is what I used to generate a username from an email address. I like it better because it actually checks the database first making it more deterministic. The method also attempts to use as much of the users current email username as possible. Random digits are only appended incrementally as a last resort.

import random
import string
import re

def generate_username_from_email(self, email_address=u''):
        """
        Generates a unique username based on the email address provided.
        * Removed the email address prefix i.e. '[email protected]' becomes 'some.guy'
        * Removes all non-alphanumeric characters 'some.guy' becomes 'someguy'
        * The string length is limited to 30 characters max.
        * In the while-loop the username is tested for uniqueness.
        * If the username is not unique numeric digits are appended to the end one at a time.
        * The looping stops once it is determined that the username does not exist.
        """
        # Strip up to 30 characters of the prefix before the '@'
        index = email_address.find(u'@')
        username = email_address[:index][:30]

        # Django only allows letters and numbers in username so replace non-alpha numeric characters.
        username = re.sub(r'[^a-zA-Z0-9+]', '', username)

        # Tests for Uniqueness.
        unique = False
        extra_digits = 1
        while not unique:
            try:
                user = User.objects.get(username=username)

                # Append random numbers to username
                random_numeric_suffix = u''.join(random.choice(string.digits) for _ in range(extra_digits))
                username = username[:30 - extra_digits] + random_numeric_suffix
                unique = False
                if extra_digits < 5:
                    extra_digits += 1
            except User.MultipleObjectsReturned:
                # Append random numbers to username
                random_numeric_suffix = u''.join(random.choice(string.digits) for _ in range(extra_digits))
                username = username[:30 - extra_digits] + random_numeric_suffix
                unique = False
                if extra_digits < 5:
                    extra_digits += 1
            except User.DoesNotExist:
                unique = True           # Break the while-loop
                # print 'generated username: {0}'.format(username)
        return username

@umakantmane
Copy link

great, worked lot for me

@tangorboyz
Copy link

instead of:
User.objects.get(email = value)
you could just call if User.objects.filter(email=value).exists():
and then just raise ValidationError if email exists.
that way you don't have to use try catch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment