简体   繁体   中英

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.

All the rest of this post is specifically referring to the "user" in 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). 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.

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.

This same behavior happens on our endpoints with request.user. request.user can edit any user except for the logged in user.

CODE

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

It is defined in settings:

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)

MIGRATIONS 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. 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.

Let me know if I can provide anymore context.

UPDATE 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.

在此处输入图片说明

I think you probably did initial migration before creating CUSTOM_USER


Now you can delete all migration files and can run a fresh migration.

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.


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.

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 ). It would only be updated if someone called refresh_from_db on it.

Thus, I suspected something called request.user.save() , which would then store back the outdated state. This would match the queries we observed. But the django admin view should not do that. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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