views:

464

answers:

4

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.

+1  A: 

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
Not quite. What do you do with the username field? It's still a primary key.
perrierism
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?
perrierism
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
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?
perrierism
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
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.
perrierism
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
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.
perrierism
+1  A: 

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
+6  A: 

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 and UserCreationForm as the maximum length is not deduced from the model field, but directly in the form field declaration.
Clément
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
perrierism
@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
A: 

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.

Lakshman Prasad
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
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).
Lakshman Prasad
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.
perrierism