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.

the
  • 21,007
  • 11
  • 68
  • 101
Purrell
  • 12,461
  • 16
  • 58
  • 70
  • 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". – T. Stone Apr 09 '10 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. – Purrell Apr 09 '10 at 19:41
  • 4
    Note for posterity: Django 1.5 has some features that solve these problems more elegantly. See http://procrastinatingdev.com/django/using-configurable-user-models-in-django-1-5/ – Andrew Gorcester Jan 19 '13 at 20:06

13 Answers13

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
Matt Miller
  • 3,501
  • 5
  • 27
  • 26
Clément
  • 6,670
  • 1
  • 30
  • 22
  • Interesting solution, I may try this. One note to anyone who comes across this post who wants to try this and is also using mysql: With this solution, since the field is still varchar(30) in the db (unless you do run alter table to modify username to be varchar(75)), this will only work if mysql is running in 'strict' mode: http://dev.mysql.com/doc/refman/5.0/en/char.html, otherwise values longer than 30 will be truncated in the db – Purrell Apr 11 '10 at 23:31
  • 1
    @perrierism: yes, you do have to `alter table` yourself as django won't do it for you (you might want to try out [south](http://south.aeracode.org/) for that). But a `syncdb` from scratch will create the field with the correct type (varchar(75)). – Clément Apr 12 '10 at 12:34
  • 4
    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. – Matt Miller Feb 25 '11 at 16:41
  • 1
    FYI, the SQL just for reference (shorter than writing the South migration): ALTER TABLE auth_user ALTER COLUMN username TYPE varchar(75); – Dick May 04 '11 at 05:20
  • 1
    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? – Collin Anderson Jun 15 '11 at 03:43
  • I wasn't aware of the `class_prepared` signal. Interesting approach. +1 – Michael Mior Feb 27 '12 at 15:17
  • Did you mean "max_length=30" instead of "max_length=35" in South backwards migration? – Ctrl-C Jul 05 '12 at 13:50
  • 1
    Can this be done with 1.7 migrations? I'm not sure how to specify the app label as 'auth'. – Bufke Oct 09 '14 at 20:40
  • No it won't work in Django 1.7. The good news is that the core team is looking into it: https://code.djangoproject.com/ticket/20846 // https://groups.google.com/forum/#!topic/django-developers/v3wYKWWYFT4/discussion – François Constant Feb 22 '15 at 22:59
  • @François I don't think it was implemented on Django 1.9.2. – Ev. Mar 02 '16 at 15:47
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

Jay Taylor
  • 13,185
  • 11
  • 60
  • 85
skoczen
  • 1,438
  • 1
  • 12
  • 10
  • 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." – Vikram Singh Chandel Sep 23 '15 at 12:19
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()
Rost
  • 3,602
  • 2
  • 21
  • 21
  • 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 – dnuske Nov 13 '12 at 18:06
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))
Cerin
  • 60,957
  • 96
  • 316
  • 522
2

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

Dmytriy Voloshyn
  • 1,032
  • 12
  • 27
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.

Bartek
  • 15,269
  • 2
  • 58
  • 65
  • Not quite. What do you do with the username field? It's still a primary key. – Purrell Apr 09 '10 at 19:15
  • 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? – Purrell Apr 09 '10 at 19:19
  • 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. – Bartek Apr 09 '10 at 19:19
  • 1
    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? – Purrell Apr 09 '10 at 19:27
  • 1
    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 – Bartek Apr 09 '10 at 19:36
  • Yeah, enterprise app. Putting a hash in username and exception handling to retry on collision is the best thing to do for this *without* modifying django it seems (?). I'm using base64(md5) (so it fits). I'm really wondering if this is less of a hassle than modifying the few places in django where you have the username length defined and used. Obviously, you have to maintain that if/when you update django. But I wouldn't necessarily call that a nightmare. – Purrell Apr 09 '10 at 20:14
  • If you found a hash method that will work and be unique then great. I don't think adding a custom backend and then some additional validation within your forms is that much work compared to modifying Django source code. – Bartek Apr 09 '10 at 20:20
  • It depends on how much work it really is. All I can see is the model definition you posted above. Didn't look very hard but didn't see any other type of validation. If it's as easy as changing that line to max_length(75) (django likes 75 char emails) well that's pretty easy. And you don't have to maintain your own authentication backend or waste all this space in the db and compute cycles fulfulling the unique constraint on username. – Purrell Apr 09 '10 at 21:55
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.

John Mee
  • 50,179
  • 34
  • 152
  • 186
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.

Community
  • 1
  • 1
Greg Glockner
  • 5,433
  • 2
  • 20
  • 23
  • 1
    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. – Purrell Nov 13 '12 at 12:10
  • In my case, hash cycles and disk space are much cheaper than development time. Your mileage may vary. – Greg Glockner Apr 12 '13 at 20:13
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
xiaowl
  • 5,177
  • 3
  • 27
  • 28
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

-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'
Crasched
  • 19
  • 2
-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.

haipt
  • 1
-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.

lprsd
  • 84,407
  • 47
  • 135
  • 168
  • 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. – Purrell Apr 11 '10 at 23:14
  • 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). – lprsd Apr 12 '10 at 08:29
  • 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. – Purrell Apr 12 '10 at 19:18