Django的auth_user.username可以是varchar(75)吗?怎么做到?

65

auth_userusername 改为 varchar(75) 来容纳电子邮件是否存在问题?这样做会对什么产生影响?

如果将 auth_user.username 改为 varchar(75),那么在django中需要进行哪些修改?是否只需在源代码中将30更改为75?

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

还是这个字段上有其他的验证需要更改,或者有其他的后果吗?

请参见下面与巴特克的评论讨论有关为什么要这样做。

编辑:回顾了几个月后。对于那些不知道前提的人:有些应用程序不需要或不想使用用户名,它们只使用电子邮件进行注册和认证。不幸的是,在django auth.contrib中,需要用户名。您可以开始将电子邮件放入用户名字段中,但该字段仅限30个字符,而在真实世界中,电子邮件可能很长。潜在地甚至比这里建议的75个字符长,但75个字符可以容纳大多数合理的电子邮件地址。这个问题针对的是这种情况,即由基于电子邮件身份验证的应用程序遇到的情况。


3
编辑之前,这不是一个问题,而是一篇有关auth.contrib的冗长抱怨。你将其编辑得更文明了,但是 Stack Overflow 声称“投票时间过久,无法更改”。 - T. Stone
我编辑了一下,让它更加简洁,我不认同你暗示它不够礼貌的说法。我是在解释为什么要询问auth_user.username = varchar(75)的逻辑。这是一个很多人都会遇到的情况,所以值得解释。但如果这导致人们不愿意花时间回答问题,那就没有必要了。 - Purrell
4
为后人留个记录:Django 1.5 版本拥有一些更加优雅地解决这些问题的功能。请查看http://procrastinatingdev.com/django/using-configurable-user-models-in-django-1-5/。 - Andrew Gorcester
13个回答

78

有一种方法可以在不触及核心模型且不使用继承的情况下实现,但这显然是hackish的,我建议小心使用。

如果你查看Django文档关于信号,你会发现有一个叫做class_prepared的信号,它基本上是在任何实际的模型类被元类创建后发送的。那个时刻是在任何魔术发生之前修改任何模型的最后机会(例如:ModelFormModelAdminsyncdb等)。

所以计划很简单,你只需要注册该信号与处理程序,以便检测是否对User模型调用它,然后更改username字段的max_length属性。

现在问题是,这段代码应该放在哪里?它必须在加载User模型之前执行,因此通常意味着非常早。不幸的是,你不能(django 1.1.1,尚未检查其他版本)将其放在settings中,因为在那里导入signals会破坏事情。

更好的选择是将其放在虚拟应用程序的模型模块中,并将该应用程序置于INSTALLED_APPS列表/元组的顶部(以便在任何其他内容之前导入它)。以下是你可以在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)

那就可以了。

不过有几点需要注意:

  • 您可能还想更改字段的help_text,以反映新的最大长度
  • 如果您想使用自动管理界面,您将需要对UserChangeFormUserCreationFormAuthenticationForm进行子类化,因为最大长度不是从模型字段中推导出来的,而是直接在表单字段声明中设置的。

如果您正在使用South,您可以创建以下迁移来更改底层数据库中的列:

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

1
@perrierism: 是的,你必须自己执行alter table操作,因为Django不会为你自动完成(你可以尝试使用south来解决这个问题)。但是,从头开始的syncdb操作将会创建具有正确类型(varchar(75))的字段。 - Clément
4
我已建议对答案进行编辑,以包括南部的迁移情况,因为这需要一些调查研究才能确定,并且太长而不能作为评论。 - Matt Miller
1
请注意,此SQL仅供参考(比编写South迁移更短):ALTER TABLE auth_user ALTER COLUMN username TYPE varchar(75); - Dick
1
对于django1.3,我需要在调用setup_environ之后,在execute_manager之前将此内容放入manage.py中。我还需要添加sender._meta.get_field('username').validators[0].limit_value = 75。有没有更好的方法在django1.3上完成这个任务? - Collin Anderson
1
这可以使用1.7迁移完成吗?我不确定如何将应用标签指定为“auth”。 - Bufke
显示剩余5条评论

25

我尝试了这个,但是出现了错误:“无法为您的数据库找到 South 数据库模块 'south.db.mysql'。请在安装的应用程序中选择一个支持的数据库、检查 SOUTH_DATABASE_ADAPTER[S] 设置,或者移除 South。” - Vikram Singh Chandel

21

对于 Django 1.3 版本的更新解决方案(不需要修改 manage.py):

创建新的 Django 应用:

monkey_patch/
    __init__.py
    models.py

首先安装它:(settings.py)

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

以下是 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()

我尝试了这个解决方案,因为它看起来更简单,但是我得到了相同的结果“值过长,无法适应字符变量类型(30)”。我按照您所述的一切都做了。我使用的是django 1.3.3版本。 - dnuske

5
上述解决方案似乎确实更新了模型长度。但是,为了在管理界面中反映您的自定义长度,您还需要覆盖管理表单(令人沮丧的是,它们不会简单地从模型继承长度)。
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
你可能也想将相同的逻辑应用到django.contrib.auth.forms中的AuthenicationForm。 - davidtingsu

2
据我了解,自Django 1.5起,用户可以重写用户模型来解决问题。这里有一个简单的例子。详情请参见此处这里。请注意,HTML标记已保留。

1

基本上,问题在于有些人想要使用电子邮件地址作为唯一标识符,而 Django 中的用户身份验证系统需要一个最多为 30 个字符的唯一用户名。也许将来会改变,但在我撰写本文时,这是 Django 1.3 的情况。

我们知道,对于许多电子邮件地址来说,30 个字符太短了;甚至 75 个字符也无法表示某些电子邮件地址,正如数据库中电子邮件地址的最佳长度是什么?所解释的那样。

我喜欢简单的解决方案,所以我建议将电子邮件地址哈希成适合 Django 用户名限制的用户名。根据Django 中的用户身份验证,用户名必须最多包含 30 个字符,由字母数字字符和 _, @, +, . 和 - 组成。因此,如果我们使用 Base64 编码并仔细替换特殊字符,我们可以获得高达 180 位。因此,我们可以使用一个 160 位的哈希函数,如 SHA-1,如下所示:

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('=', '')

简而言之,此函数将为任何电子邮件地址关联一个用户名。我知道哈希函数存在微小碰撞的可能性,但在大多数应用程序中,这不应该是问题。

1
为什么要浪费计算哈希值和存储磁盘空间的周期?另外请注意,您需要编写自定义授权后端或自定义登录表单才能使其正常工作。虽然这并不是一个很大的问题,但对我来说似乎都有些奇怪的曲折。 - Purrell
在我的情况下,哈希循环和磁盘空间比开发时间便宜得多。你的情况可能会有所不同。 - Greg Glockner

1

如果您只是修改数据库表,仍然需要处理Django的验证,因此它不会让您创建超过30个字符的字段。 此外,用户名验证不能包含特殊字符,如@,因此仅修改字段长度也无法解决问题。我的错,看起来它已经处理了这个问题。这是来自django.contrib.auth中models.py的用户名字段:

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

创建电子邮件认证并不难。这里有一个超级简单的电子邮件认证后端,您可以使用它。之后,您只需要添加一些验证来确保电子邮件地址是唯一的,并且您就完成了。很容易。

还不太对。您对用户名字段做什么?它仍然是主键。 - Purrell
你对用户名中的@符号也是错误的。实际上,它允许@符号。事实上,我无法在代码中看到它强制限制用户名小于30个字符。我不是说它不存在,但你确定它存在吗? - Purrell
这不是主键。用户名很容易。你有他们的电子邮件和数据库中的主键,也许还有他们的姓氏。哦,你还在Python中内置了一个随机数生成器等等。只需将用户名设置为唯一即可。在我的某个应用程序中,我只使用了电子邮件的第一部分加上他们的用户ID。对于基本应用程序来说非常简单。 - Bartek
1
如果你仔细想一下,那不是一个足够的解决方案。你如何保证用户名是唯一的?请注意,在现实中,你不能在用户名中使用id,因为当你创建用户对象时,你实际上还没有id,而且用户名是必需的。也许那不是一个Django应用程序。所以,如果你真的想使它强大,你只能使用哈希。但我想知道的是,如果你可以修改Django来接受电子邮件地址作为用户名,为什么要费这么大的劲呢? - Purrell
1
我的IT运行良好。有简单的方法可以检查某些内容是否唯一(将您的值与数据库进行比较,如果需要,重试)。您正在运行企业应用程序吗?那么您可能需要寻找其他解决方案。如果您正在运行具有几千个用户的应用程序,则我认为您不会遇到任何问题。修改django代码和表本身的问题是...想象一下当您必须更新django时。现在它已经损坏了,并且从长远来看会带来维护上的噩梦。 - Bartek
显示剩余3条评论

1

可以做到。至少我认为这样做应该有效;我最终替换了整个auth模型,所以如果这行不通,我准备接受更正...

如果您没有关心的用户记录:

  1. 删除auth_user表
  2. 在模型中将用户名更改为max_length=75
  3. syncdb

如果您有需要保留的用户记录,则更加复杂,因为您需要以某种方式迁移它们。最简单的方法是从旧表备份和恢复数据到新表,类似于以下内容:

  1. 备份用户表数据
  2. 删除该表
  3. syncdb
  4. 将用户数据重新导入新表;注意恢复原始id值

或者,使用您疯狂的Python-Django技能,将老用户模型实例复制到新模型并替换:

  1. 创建自定义模型并暂时将其与默认模型一起设置
  2. 编写一个脚本,将实例从默认模型复制到新模型
  3. 用自定义模型替换默认模型

后者并不像听起来那么难,但显然涉及更多工作。


0
只需将以下代码添加到settings.py底部即可。
from django.contrib.auth.models import User
User._meta.get_field("username").max_length = 75

无法在Django 1.7及更高版本中工作,因为模型尚未被导入。 - kaleissin

0

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

first_name = models.CharField(_('名字'), max_length=30, blank=True)

修改为

first_name = models.CharField(_('名字'), max_length=75, blank=True)

保存

并在数据库中进行更改


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接