65

Is there anything wrong with running alter table on auth_user to make username be varchar(75) so it can fit an email? What does that break if anything?

If you were to change auth_user.username to be varchar(75) where would you need to modify django? Is it simply a matter of changing 30 to 75 in the source code?

username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters")) 

Or is there other validation on this field that would have to be changed or any other repercussions to doing so?

See comment discussion with bartek below regarding the reason for doing it.

Edit: Looking back on this after many months. For anyone who doesn't know the premise: Some apps don't have a requirement or desire to use a username, they use only email for registration & auth. Unfortunately in django auth.contrib, username is required. You could start putting emails in the username field, but the field is only 30 char and emails may be long in the real world. Potentially even longer than the 75 char suggested here, but 75 char accommodates most sane email addresses. The question is aimed at this situation, as encountered by email-auth-based applications.

3
  • 3
    Before the edit it wasn't a question by a lengthy complaint about auth.contrib. You edited it to be more civil but SO claims the "vote is too old to change". Commented Apr 9, 2010 at 19:24
  • I edited it to be more concise, I do resent your implying it was less civil (?). I was explaining the logic for asking about auth_user.username = varchar(75). It's a situation that I see a lot of other people in so it's worthwhile to explain. But not if it causes people not to take the time to address the question. Commented Apr 9, 2010 at 19:41
  • 4
    Note for posterity: Django 1.5 has some features that solve these problems more elegantly. See procrastinatingdev.com/django/… Commented Jan 19, 2013 at 20:06

13 Answers 13

78

There's a way to achieve that without touching the core model, and without inheritance, but it's definitely hackish and I would use it with extra care.

If you look at Django's doc on signals, you'll see there's one called class_prepared, which is basically sent once any actual model class has been created by the metaclass. That moment is your last chance of modifying any model before any magic takes place (ie: ModelForm, ModelAdmin, syncdb, etc...).

So the plan is simple, you just register that signal with a handler that will detect when it is called for the User model, and then change the max_length property of the username field.

Now the question is, where should this code lives? It has to be executed before the User model is loaded, so that often means very early. Unfortunately, you can't (django 1.1.1, haven't check with another version) put that in settings because importing signals there will break things.

A better choice would be to put it in a dummy app's models module, and to put that app on top of the INSTALLED_APPS list/tuple (so it gets imported before anything else). Here is an example of what you can have in myhackishfix_app/models.py :

from django.db.models.signals import class_prepared def longer_username(sender, *args, **kwargs): # You can't just do `if sender == django.contrib.auth.models.User` # because you would have to import the model # You have to test using __name__ and __module__ if sender.__name__ == "User" and sender.__module__ == "django.contrib.auth.models": sender._meta.get_field("username").max_length = 75 class_prepared.connect(longer_username) 

That will do the trick.

A few notes though:

  • You might want to change also the help_text of the field, to reflect the new maximum length
  • If you want to use the automatic admin, you will have to subclass UserChangeForm, UserCreationForm and AuthenticationForm as the maximum length is not deduced from the model field, but directly in the form field declaration.

If you're using South, you can create the following migration to change the column in the underlying database:

import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): # Changing field 'User.username' db.alter_column('auth_user', 'username', models.CharField(max_length=75)) def backwards(self, orm): # Changing field 'User.username' db.alter_column('auth_user', 'username', models.CharField(max_length=35)) models = { # ... Copy the remainder of the file from the previous migration, being sure # to change the value for auth.user / usename / maxlength 
Sign up to request clarification or add additional context in comments.

10 Comments

@perrierism: yes, you do have to alter table yourself as django won't do it for you (you might want to try out south for that). But a syncdb from scratch will create the field with the correct type (varchar(75)).
I've suggested edits to the answer to include the migration for South, since it took a bit of research for me to work out and it is too long for a comment.
FYI, the SQL just for reference (shorter than writing the South migration): ALTER TABLE auth_user ALTER COLUMN username TYPE varchar(75);
for django1.3 I needed to put this in manage.py after calling setup_environ, before execute_manager. I also needed to add sender._meta.get_field('username').validators[0].limit_value = 75 Does anyone have any better ways to accomplish this on django1.3?
Can this be done with 1.7 migrations? I'm not sure how to specify the app label as 'auth'.
|
25

Based on Clément and Matt Miller's great combined answer above, I've pulled together a quick app that implements it. Pip install, migrate, and go. Would put this as a comment, but don't have the cred yet!

https://github.com/GoodCloud/django-longer-username

EDIT 2014-12-08

The above module is now deprecated in favor of https://github.com/madssj/django-longer-username-and-email

1 Comment

I tried this but I am getting error "There is no South database module 'south.db.mysql' for your database. Please either choose a supported database, check for SOUTH_DATABASE_ADAPTER[S] settings, or remove South from INSTALLED_APPS."
21

Updated solution for the Django 1.3 version (without modifying manage.py):

Create new django-app:

monkey_patch/ __init__.py models.py 

Install it as first: (settings.py)

INSTALLED_APPS = ( 'monkey_patch', #... ) 

Here is models.py:

from django.contrib.auth.models import User from django.core.validators import MaxLengthValidator NEW_USERNAME_LENGTH = 300 def monkey_patch_username(): username = User._meta.get_field("username") username.max_length = NEW_USERNAME_LENGTH for v in username.validators: if isinstance(v, MaxLengthValidator): v.limit_value = NEW_USERNAME_LENGTH monkey_patch_username() 

1 Comment

I tryied this solution because seems a lot simpler, but I have the same result "value too long for type character varying(30)". I did everything exacly as you stated. I'm on django 1.3.3
5

The solutions above do seem to update the model length. However, to reflect your custom length in admin, you also need to override the admin forms (frustratingly, they don't simply inherit the length from the model).

from django.contrib.auth.forms import UserChangeForm, UserCreationForm UserChangeForm.base_fields['username'].max_length = NEW_USERNAME_LENGTH UserChangeForm.base_fields['username'].widget.attrs['maxlength'] = NEW_USERNAME_LENGTH UserChangeForm.base_fields['username'].validators[0].limit_value = NEW_USERNAME_LENGTH UserChangeForm.base_fields['username'].help_text = UserChangeForm.base_fields['username'].help_text.replace('30', str(NEW_USERNAME_LENGTH)) UserCreationForm.base_fields['username'].max_length = NEW_USERNAME_LENGTH UserCreationForm.base_fields['username'].widget.attrs['maxlength'] = NEW_USERNAME_LENGTH UserCreationForm.base_fields['username'].validators[0].limit_value = NEW_USERNAME_LENGTH UserCreationForm.base_fields['username'].help_text = UserChangeForm.base_fields['username'].help_text.replace('30', str(NEW_USERNAME_LENGTH)) 

1 Comment

you might also want to apply the same logic to AuthenicationForm in django.contrib.auth.forms
2

As far as I know one can override user model since Django 1.5 which will solve a problem. Simple example here

Comments

1

If you simply modify the database table, you'll still have to deal with Django's validation, so it won't let you make one over 30 characters anyways. Additionally, the username validates so that it can't have special characters like @ so simply modifying the length of the field wouldn't work anyways. My bad, looks like it handles that. Here's the username field from models.py in django.contrib.auth:

username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters")) 

Creating email auth is not hard. Here's a super simple email auth backend you can use. Al l you need to do after that is add some validation to ensure the email address is unique and you're done. That easy.

8 Comments

Not quite. What do you do with the username field? It's still a primary key.
You're also wrong about the @ sign in usernames. It does allow @. In fact I can't quite see where it enforces < 30 on username looking at the code. I'm not saying it's not there but, are you sure it's there?
It's not a primary key. The username is easy. You have their email and you and a primary key from the database, perhaps their last name too. Oh, you also have a random number generator built into python .. and so forth. Just make the username something unique. In one of my apps I just used the first part of the email plus their user id. Simple for basic apps.
Yeah that's not really a sufficient solution if you think about it. How do you guarantee the username is unique. Note that in reality you can't use the id in the username because when you create the user object you don't actually have an id yet, and the username is required. Maybe that wasn't a django app. So you're left with hashes if you really want to make it robust. But what I'm wondering is why go through all this trouble if you can just modify django to accept email addresses in usernames?
IT works fine for me. There are easy ways to check if something is unique (compare your value to the database, retry if required.) Are you running an enterprise app? Then you may need to look into other solutions. If you're running an app with a few thousand users, I don't think you'll have any issues. The issue with modifying the django code and the table itself is well .. imagine when you have to update django. That's now broken, and you have a maintenance nightmare in the long run
|
1

Yes, it can be done. At least I think this should work; I wound up replacing the whole auth model, so am ready to be corrected if this doesn't work out...

If you have no user records you care about:

  1. drop the auth_user table
  2. change username to max_length=75 in the model
  3. syncdb

If you have user records you need to retain then it's more complicated as you need to migrate them somehow. Easiest is backup and restore of the data from old to new table, something like:

  1. backup the user table data
  2. drop the table
  3. syncdb
  4. reimport user data to the new table; taking care to restore the original id values

Alternatively, using your mad python-django skillz, copy the user model instances from old to new and replace:

  1. create your custom model and temporarily stand it alongside the default model
  2. write a script which copies the instances from the default model to the new model
  3. replace the default model with your custom one

The latter is not as hard as it sounds, but obviously involves a bit more work.

Comments

1

Fundamentally, the problem is that some people want to use an email address as the unique identifier, while the user authentication system in Django requires a unique username of at most 30 characters. Perhaps that will change in the future, but that's the case with Django 1.3 as I'm writing.

We know that 30 characters is too short for many email addresses; even 75 characters is not enough to represent some email addresses, as explained in What is the optimal length for an email address in a database?.

I like simple solutions, so I recommend hashing the email address into a username that fits the restrictions for usernames in Django. According to User authentication in Django, a username must be at most 30 characters, consisting of alphanumeric characters and _, @, +, . and -. Thus, if we use base-64 encoding with careful substitution of the special characters, we have up to 180 bits. So we can use a 160-bit hash function like SHA-1 as follows:

import hashlib import base64 def hash_user(email_address): """Create a username from an email address""" hash = hashlib.sha1(email_address).digest() return base64.b64encode(hash, '_.').replace('=', '') 

In short, this function associates a username for any email address. I'm aware that there is a tiny probability of a collision in the hash function, but this should not be an issue in most applications.

2 Comments

Why waste the cycles computing the hash and the disk space storing it? Also note, you need to write either a custom authorization backend or a custom login_form to make this work. Not a huge deal, but it all seems like a strange contortion to me.
In my case, hash cycles and disk space are much cheaper than development time. Your mileage may vary.
0

Just adding the below code at the bottom of settings.py

from django.contrib.auth.models import User User._meta.get_field("username").max_length = 75 

1 Comment

Won't work in Django 1.7 and later, as models haven't been imported yet
0

C:...\venv\Lib\site-packages\django\contrib\auth\models.py

first_name = models.CharField(_('first name'), max_length=30, blank=True)

change to

first_name = models.CharField(_('first name'), max_length=75, blank=True)

save

and change in the database

Comments

-1

I am using django 1.4.3 which makes it pretty easy and I did not have to change anything else in my code after realising I wanted to use long email addresses as usernames.

If you have direct access to the database, change it there to the amount of characters you would like to, in my case 100 characters.

In your app model (myapp/models.py) add the following

from django.contrib.auth.models import User class UserProfile(models.Model): # This field is required. User._meta.get_field("username").max_length = 100 user = models.OneToOneField(User) 

Then in your settings.py you specify the model:

AUTH_USER_MODEL = 'myapp.UserProfile' 

Comments

-2

If you are using venv (virtual environment), the simplest solution probably is just update the core code directly, i.e. opening the following two files: - - venv/lib/python2.7/sites-packages/django/contrib/auth/model.py - venv/lib/python2.7/sites-packages/django/contrib/auth/forms.py Search for all username field and change max_length from 30 to 100. It is safe since you are already using venv so it won't affect any other Django project.

Comments

-3

The best solution is to use email field for email and the username for username.

In the input login form validation, find whether the data is username or the email and if email, query the email field.

This only requires monkey patching the contrib.auth.forms.login_form which is a few lines in the corresponding view.

And it is far better than trying to modify the models and the database tables.

3 Comments

I think you may have misunderstood the issue, it's about when you're not using usernames, you're using emails. And to solve it without touching django, the generally accepted solution is more than what you describe here. It's actually to populate the username with something unique (wasting the field) and creating an AUTHORIZATION_BACKENDS class to handle authentication on email. The problem is with creating users. There is an email field but there is also a required username field which does not fit an email. You are still required to create a unique username every time you're creating a user.
perrierism: The solution to use emails alone, without touching django, is obviously, using a random auto-generated username and using emails for querying (with the email auth backend).
It wasn't 'obviously' to you before because that's actually a different solution than what you posted. The solution you're mentioning now doesn't require changing contrib.auth.forms.login_form. Also the issue of auto-generated usernames is not so straightforward if you want a robust implementation, (see discussion with bartek). Finally, note the question isn't, "how do you do this without modifying django?". The generally accepted solution for that (which you haven't quite actually stated) is known but it has limitations. Namely that you still need unique usernames on user creation.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.