【问题标题】:Django User Editing, AbstractUserDjango 用户编辑,AbstractUser
【发布时间】:2021-09-07 10:00:07
【问题描述】:

我们正在尝试构建端点以允许用户从我们的前端编辑他们自己的配置文件,我们在尝试编辑“登录用户”时遇到了问题。这个问题也发生在 django admin 中。

这篇文章的其余部分都是专门指 django admin 中的“用户”。我已经扩展了用户并建立了一个自定义管理员。

如果我们有 3 个用户,(现在假设这三个用户都是超级用户/is_staff)。登录用户 1,我可以编辑用户 2 和 3,但是当我去编辑用户 1(登录用户)时,消息说它已更新但数据库没有更改。

如果我以用户 2 身份登录并更新用户 1,我可以将用户 1 更新为登录用户,但不能更新用户 2。

同样的行为也发生在我们的 request.user 端点上。 request.user 可以编辑除登录用户之外的任何用户。

代码

accounts/models.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

在设置中定义:

AUTH_USER_MODEL = 'accounts.User'

accounts/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)

迁移 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()),
            ],
        ),
]

我的测试截图

这是 UPDATE 的输出,您可以看到名字被保存为空白字符串。所以也许这与形式有关?

当我们添加自定义用户模型时,我们可能做错了什么,因为我认为它是在项目创建之后添加的。

因此,我假设登录的用户可能正在更改数据库中的错误表。不过,我不确定如何识别它,通常我会使用 shell,但由于我们的自定义模型,原始 auth_user 模型被禁止导入。

如果我可以提供更多上下文,请告诉我。

更新 1

看起来更新确实有效,然后它会立即被原始数据覆盖。请参阅此屏幕截图,这发生在一次更新中。您可以看到一个 UPDATE 语句,其中姓氏有一个值,然后是第二个带有原始数据的 UPDATE。

【问题讨论】:

  • minimal reproducible example 肯定会有所帮助。 You can make django log all executed sql queries (example),这可能会提示您正确的方向。只是为了确保:更新后,request.user 没有更新,你必须打电话给refresh_from_db 才能看到那里的更新——我猜你是这样做的?
  • 可以添加用户编辑相关的代码吗?如果代码使用了错误的表,我认为不应该这样做,那么登录用户也不应该能够更新其他用户。另外,当您说您收到用户已更新的消息时,您是在打开用户编辑页面还是在单击保存后收到它?你如何确定用户没有真正更新?
  • 我添加了更多代码来显示我所指的 admin.py,并添加了一些屏幕截图来向您展示我是如何测试它的。
  • @He3lixxx,我能够打印出 SQL,但有些东西很时髦,因为我正在尝试更新名字,但 SQL 传递的是空白字符串而不是我的值。
  • 添加了更新,sql 显示更新,然后显示原始数据的重复更新。添加了要显示的屏幕截图。

标签: django


【解决方案1】:

tl,dr:更新已正确完成,但中间件导致request.user.save()(或类似),将旧值写回数据库。


来自 cmets 的调查结果扩展为答案:

事实证明更新查询按预期执行,与消息“用户已成功更改”相匹配。启用logging of all sql queries 可以确认这一点。

但是,在正确的更新查询之后,还有另一个查询将用户重置为之前的状态。在这里,它有助于知道当表单被保存时,它不会更新通过request.user 访问的python 对象(因为它不知道更新的用户是request.user)。仅当有人在其上调用refresh_from_db 时才会更新。

因此,我怀疑有一个名为request.user.save() 的东西,它会存储回过时的状态。这将匹配我们观察到的查询。但是 django 管理视图不应该这样做。在互联网上,有一些关于如何获取stack traces together with the query log 的页面,应该可以找出查询是从哪里发出的。

但是,即使没有启用日志的麻烦,在这种情况下的罪魁祸首也可以确定为某些中间件。这可以通过简单地注释掉任何不必要的(自定义)中间件来轻松测试。

【讨论】:

  • 这正是问题所在。我有一个调用 request.user.save() 的中间件。这导致了“双重保存”,第二个是 request.user 的原始状态。保留所有内容,我将 request.user.save() 更改为 request.user.save(update_fields=['']) 并解决了问题。
【解决方案2】:

我认为您可能在创建 CUSTOM_USER

之前进行了初始迁移

现在您可以删除所有 migration files 并运行新的迁移。

【讨论】:

  • 所有这些都已经投入生产了一段时间。我不特别记得了,但我记得当这个项目第一次构建时我根本不了解迁移,所以很可能我在应用程序的初始迁移之后做了自定义用户。你是说:1.删除应用程序中的所有迁移文件2.重新运行makemigrations 3.重新运行迁移我认为这会出错所以我假设我只是伪造迁移?
  • 这实际上可能是我最好的路线,重置此特定应用程序上的迁移:simpleisbetterthancomplex.com/tutorial/2016/07/26/…
  • 我查看了应用程序中的第一个迁移文件 0001,似乎我可能已经尝试正确执行此操作,我可以看到创建模型并添加我的自定义字段
  • @KrisTryber,您必须在初始迁移之前设置您的 CustomUser 。但是,你做了与 Django 不支持的相反的事情。现在您可以通过答案找到您的解决方案。
  • 是的,听起来我需要像你说的那样做,删除所有迁移并重新运行,但也要为每个具有依赖迁移的应用程序执行此操作
猜你喜欢
  • 2021-07-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 2021-10-03
  • 1970-01-01
  • 2015-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多