简体   繁体   English

Django 用户编辑,AbstractUser

[英]Django User Editing, AbstractUser

We are trying to build endpoints to allow users to edit their own profiles from our front end and we've encountered a problem while trying to edit the "logged in user".我们正在尝试构建端点以允许用户从我们的前端编辑他们自己的配置文件,我们在尝试编辑“登录用户”时遇到了问题。 This issue happens in django admin as well.这个问题也发生在 django admin 中。

All the rest of this post is specifically referring to the "user" in django admin.这篇文章的其余部分专门指的是 django admin 中的“用户”。 I have extended the user and built a custom admin.我扩展了用户并构建了一个自定义管理员。

if we have 3 users, (imagine all three are super users/is_staff for now).如果我们有 3 个用户,(假设这三个用户现在都是超级用户/is_staff)。 Logged in user 1, I can edit users 2 and 3, but when I go to edit user 1 (logged in user), the message says it was updated but the database does not change.登录用户 1,我可以编辑用户 2 和 3,但是当我去编辑用户 1(登录用户)时,消息说它已更新但数据库没有更改。

If I then login as user 2 and update user 1, I can update user 1 but not user 2 as the logged in user.如果我然后以用户 2 登录并更新用户 1,我可以更新用户 1,但不能以登录用户身份更新用户 2。

This same behavior happens on our endpoints with request.user.同样的行为发生在我们的 request.user 端点上。 request.user can edit any user except for the logged in user. request.user 可以编辑除登录用户之外的任何用户。

CODE代码

accounts/models.py帐户/模型.py

class User(AbstractUser):
    timezone = models.CharField(max_length=255, blank=True, null=True)
    is_customer = models.BooleanField(default=False)
    is_agent = models.BooleanField(default=False)
    is_carrier = models.BooleanField(default=False)
    is_shipper = models.BooleanField(default=False)
    is_tracking = models.BooleanField(default=False)

    class Meta:
        db_table = 'auth_user'

    def __str__(self):
        return self.first_name

It is defined in settings:它在设置中定义:

AUTH_USER_MODEL = 'accounts.User'

accounts/admin.py帐户/admin.py

CustomUser = get_user_model()


class UserInline(admin.StackedInline):
    model = User


class AgentInline(admin.StackedInline):
    model = Agent


class CustomUserAdmin(UserAdmin):
    model = CustomUser

    agent_fields = ('timezone', 'is_agent', 'is_customer', 'is_shipper', 'is_carrier', 'is_tracking')
    fieldsets = UserAdmin.fieldsets + (
        ('Agent Info', {'fields': agent_fields}),
    )
    inlines = [
        AgentInline,
    ]

admin.site.register(CustomUser, CustomUserAdmin)

MIGRATIONS 0001_initial.py迁移 0001_initial.py

class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('auth', '0009_alter_user_last_name_max_length'),
    ]

    operations = [
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('password', models.CharField(max_length=128, verbose_name='password')),
                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
                ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
                ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
                ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
                ('is_customer', models.BooleanField(default=False)),
                ('is_agent', models.BooleanField(default=False)),
                ('is_carrier', models.BooleanField(default=False)),
                ('is_shipper', models.BooleanField(default=False)),
            ],
            options={
                'verbose_name': 'user',
                'verbose_name_plural': 'users',
                'abstract': False,
            },
            managers=[
                ('objects', django.contrib.auth.models.UserManager()),
            ],
        ),
]

Screenshots of my testing我的测试截图

在此处输入图片说明 在此处输入图片说明 在此处输入图片说明

Here's the output of the UPDATE, you can see that first name is being saved as blank string.这是 UPDATE 的输出,您可以看到名字被保存为空白字符串。 So maybe this is form related?所以也许这与形式有关?

在此处输入图片说明

It's likely that when we added the custom user model we did something wrong as I believe it was added after the project creation.很可能当我们添加自定义用户模型时,我们做错了,因为我相信它是在项目创建之后添加的。

Because of this i'm assuming the logged in user is maybe making a change to the wrong table in the DB.因此,我假设登录用户可能对数据库中的错误表进行了更改。 I'm not exactly sure how to identify this though, normally I would shell in but the original auth_user model is disabled from being imported because of our custom model.不过,我不确定如何识别这一点,通常我会进入,但由于我们的自定义模型,原始 auth_user 模型被禁止导入。

Let me know if I can provide anymore context.如果我可以提供更多上下文,请告诉我。

UPDATE 1更新 1

It looks like the update is actually working, then it's being immediately overwritten by the original data.看起来更新实际上正在运行,然后它会立即被原始数据覆盖。 See this screenshot, this happens on a single update.请参阅此屏幕截图,这是在一次更新中发生的。 You can see an UPDATE statement with the last name having a value, then a second UPDATE with the original data.您可以看到姓氏具有值的 UPDATE 语句,然后是带有原始数据的第二个 UPDATE。

在此处输入图片说明

I think you probably did initial migration before creating CUSTOM_USER我认为您可能在创建CUSTOM_USER之前进行了初始迁移


Now you can delete all migration files and can run a fresh migration.现在您可以删除所有migration files并运行全新的迁移。

tl,dr: The update was done properly, but a middleware caused a request.user.save() (or similar), writing back the old values to the db. tl,dr:更新正确完成,但中间件导致request.user.save() (或类似),将旧值写回数据库。


Findings from the comments expanded into an answer:评论中的发现扩展为答案:

It turns out the update query was executed just as expected, which matches the message "User was changed successfully".事实证明,更新查询按预期执行,与“用户已成功更改”消息匹配。 Enabling logging of all sql queries allowed to confirm this.启用所有 sql 查询的日志记录以确认这一点。

However, right after the correct update query, there was another query resetting the user to its state before.然而,在正确的更新查询之后,还有另一个查询将用户重置为之前的状态。 Here, it helps to know that when the form is saved, it does not update the python object accessible via request.user (because it doesn't know that the updated user is request.user ).在这里,知道保存表单时,它不会更新可通过request.user访问的 python 对象(因为它不知道更新的用户是request.user )是有帮助的。 It would only be updated if someone called refresh_from_db on it.只有在有人调用refresh_from_db时它才会更新。

Thus, I suspected something called request.user.save() , which would then store back the outdated state.因此,我怀疑有一个叫做request.user.save()东西,它会存储回过时的状态。 This would match the queries we observed.这将匹配我们观察到的查询。 But the django admin view should not do that.但是 django 管理视图不应该这样做。 On the internet, there are some pages on how to get stack traces together with the query log , which should allow to find out where the query is issued from.在互联网上,有一些关于如何将堆栈跟踪与查询日志一起获取的页面,这应该可以找出查询是从哪里发出的。

However, even without the hassle of enabling the log, the culprit in this case could be identified to be some middleware.然而,即使没有启用日志的麻烦,这种情况下的罪魁祸首也可以被确定为某些中间件。 This can be easily tested by simply commenting out any (custom) middleware that is not essential.这可以通过简单地注释掉任何不必要的(自定义)中间件来轻松测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM