将 Django 的 auth_user 与现有用户 table 合并

merge django's auth_user with existing user table

目前我有一个遗留应用程序,它引用了一个包含所有自定义字段的 user table。由于有大量遗留代码引用 table,我不能简单地将 table 重命名为 auth_user。所以我想做的就是以某种方式合并(我不知道这是正确的术语)auth_useruser

下面是user table:

+-------------------+--------------+------+-----+---------+----------------+
| Field             | Type         | Null | Key | Default | Extra          |
+-------------------+--------------+------+-----+---------+----------------+
| user_id           | int(10)      | NO   | PRI | NULL    | auto_increment |
| name              | varchar(100) | NO   |     | NULL    |                |
| address           | varchar(100) | NO   |     | NULL    |                |
| phone_no          | varchar(15)  | NO   |     | NULL    |                |
| city              | varchar(100) | NO   |     | NULL    |                |
| state             | varchar(100) | NO   |     | NULL    |                |
| pin_no            | int(10)      | NO   |     | NULL    |                |
| type              | varchar(100) | NO   |     | NULL    |                |
| email             | varchar(100) | NO   |     | NULL    |                |
| password          | varchar(100) | NO   |     | NULL    |                |
| is_active         | tinyint(1)   | NO   |     | NULL    |                |
| role              | varchar(40)  | NO   |     | NULL    |                |
| creation_date     | int(100)     | NO   |     | NULL    |                |
| edit_date         | int(100)     | NO   |     | NULL    |                |
| country           | varchar(255) | NO   |     | NULL    |                |
| district          | varchar(255) | NO   |     | NULL    |                |
| ip                | varchar(255) | NO   |     | NULL    |                |
| added_by          | int(11)      | NO   |     | NULL    |                |
| is_phone_verified | binary(1)    | NO   |     | 0       |                |
| remember_token    | varchar(100) | YES  |     | NULL    |                |
| disclaimer_agreed | int(11)      | YES  |     | 0       |                |
| mobile_login      | tinyint(4)   | NO   |     | 0       |                |
+-------------------+--------------+------+-----+---------+----------------+

和django的auth_user table:

+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | int(11)      | NO   | PRI | NULL    | auto_increment |
| password     | varchar(128) | NO   |     | NULL    |                |
| last_login   | datetime(6)  | YES  |     | NULL    |                |
| is_superuser | tinyint(1)   | NO   |     | NULL    |                |
| username     | varchar(150) | NO   | UNI | NULL    |                |
| first_name   | varchar(30)  | NO   |     | NULL    |                |
| last_name    | varchar(30)  | NO   |     | NULL    |                |
| email        | varchar(254) | NO   |     | NULL    |                |
| is_staff     | tinyint(1)   | NO   |     | NULL    |                |
| is_active    | tinyint(1)   | NO   |     | NULL    |                |
| date_joined  | datetime(6)  | NO   |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

我想要的是一个 table,django 将在使用 contrib.auth 相关内容时引用它,同时也不会弃用我的遗留代码。也许是这样的:

+-------------------+--------------+------+-----+---------+----------------+
| Field             | Type         | Null | Key | Default | Extra          |
+-------------------+--------------+------+-----+---------+----------------+
| user_id           | int(10)      | NO   | PRI | NULL    | auto_increment |
| name              | varchar(100) | NO   |     | NULL    |                |
| address           | varchar(100) | NO   |     | NULL    |                |
| phone_no          | varchar(15)  | NO   |     | NULL    |                |
| city              | varchar(100) | NO   |     | NULL    |                |
| state             | varchar(100) | NO   |     | NULL    |                |
| pin_no            | int(10)      | NO   |     | NULL    |                |
| type              | varchar(100) | NO   |     | NULL    |                |
| email             | varchar(100) | NO   |     | NULL    |                |
| password          | varchar(100) | NO   |     | NULL    |                |
| is_active         | tinyint(1)   | NO   |     | NULL    |                |
| role              | varchar(40)  | NO   |     | NULL    |                |
| creation_date     | int(100)     | NO   |     | NULL    |                |
| edit_date         | int(100)     | NO   |     | NULL    |                |
| country           | varchar(255) | NO   |     | NULL    |                |
| district          | varchar(255) | NO   |     | NULL    |                |
| ip                | varchar(255) | NO   |     | NULL    |                |
| added_by          | int(11)      | NO   |     | NULL    |                |
| is_phone_verified | binary(1)    | NO   |     | 0       |                |
| remember_token    | varchar(100) | YES  |     | NULL    |                |
| disclaimer_agreed | int(11)      | YES  |     | 0       |                |
| mobile_login      | tinyint(4)   | NO   |     | 0       |                |
| last_login        | datetime(6)  | YES  |     | NULL    |                |
| is_superuser      | tinyint(1)   | NO   |     | NULL    |                |
| username          | varchar(150) | NO   | UNI | NULL    |                |
| first_name        | varchar(30)  | NO   |     | NULL    |                |
| last_name         | varchar(30)  | NO   |     | NULL    |                |
| is_staff          | tinyint(1)   | NO   |     | NULL    |                |
| is_active         | tinyint(1)   | NO   |     | NULL    |                |
| date_joined       | datetime(6)  | NO   |     | NULL    |                |
+-------------------+--------------+------+-----+---------+----------------+

The motive here is to take advantage of Django's builtin authentication and permission system but without breaking legacy code. I think there must be a solution for this as this is not the first time someone is porting some legacy application to django.

我还想提一下 link How to Extend Django User Model,但我不确定哪种方法最好,或者我应该做一些完全不同的事情

Django 明确支持自定义用户模型。为您现有的 table 创建一个模型,使其继承自 AbstractBaseUser,并将 AUTH_USER_MODEL 设置设置为指向您的新模型。见 comprehensive docs.

我们可以从 Django ticket 中更详细地了解如何从内置用户模型迁移到自定义用户模型。

引用自Carsten Fuchs

Assumptions

  • Your project doesn't have a custom user model yet.
  • All existing users must be kept.
  • There are no pending migrations and all existing migrations are applied.
  • It is acceptable that all previous migrations are lost and can no longer be unapplied, even if you use version control to checkout old commits that still have the migration files. This is the relevant downside of this approach.

Preparations

  • Make sure that any third party apps that refer to the Django User model only use the ​generic referencing methods.
  • Make sure that your own reusable apps (apps that are intended to be used by others) use the generic reference methods.
  • I suggest to not do the same with your project apps: The switch to a custom user model is only done once per project and never again. It is easier (and in my opinion also clearer) to change from django.contrib.auth.models import User to something else (as detailed below) than replacing it with generic references that are not needed in project code.
  • Make sure that you have a backup of your code and database!

Update the code

  • You can create the new user model in any existing app or a newly created one. My preference is to create a new app:

    ./manage.py startapp Accounts
    

I chose the name "Accounts", but any other name works as well.

  • Aymeric: „Create a custom user model identical to auth.User, call it User (so many-to-many tables keep the same name) and set db_table='auth_user' (so it uses the same table).“ In Accounts/models.py:

    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    
    class User(AbstractUser):
    class Meta:
            db_table = 'auth_user'
    
  • In settings.py, add the app to INSTALLED_APPS and update the AUTH_USER_MODEL setting:

    INSTALLED_APPS = (
        # ...
        'Accounts',
    )
    
    AUTH_USER_MODEL = 'Accounts.User'
    
  • In your project code, replace all imports of the Django user model:

    from django.contrib.auth.models import User with the new, custom one:
    

    from Accounts.models import User

  • Delete all old migrations. (Beforehand, see if comment 14 is relevant to you!) For example, in the project root:

    find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
    find . -path "*/migrations/*.pyc" -delete
    
  • Create new migrations from scratch:

    ./manage.py makemigrations
    
  • Make any changes to your admin.py files as required. (I cannot give any solid information here, but this is not crucial for the result and so the details can still be reviewed later.)

  • Make sure that your testsuite completes successfully! (A fresh test database must be used, it cannot be kept from previous runs.)
  • At this point, the changes to the code are complete. This is a good time for a commit.

Note that we're done – except that the new migration files mismatch the contents of the django_migrations table.

(It may even be possible to serve your project at this point: It's easy to back off before the database is actually changed. Only do this if you understand that you cannot even touch the migrations system as long as the steps below are not completed!)

Update the database

  • Truncate the django_migrations table. MySQL 8 example:

      TRUNCATE TABLE django_migrations;
    

This is possibly different for other databases or verions of MySQL < 8.

  • Fake-apply the new set of migrations ./manage.py migrate --fake

  • Check the ContentTypes as described at comment 12

Conclusion

  • The upgrade to the custom user model is now complete. You can make changes to this model and generate and apply migrations for it as with any other models.
  • As a first step, you may wish to unset db_table and generate and apply the resulting migrations.
  • In my opinion, the startproject management command should anticipate the introduction of a custom user model.