简体   繁体   中英

Django admin UserChangeForm with UUID field as primary key

I'm working on a Django app that will contain sensitive User data, so I'm taking a number of steps to anonymise User objects and their data.

I created custom 'User' object that subclassses the AbstractBaseUser model like so:

class User(AbstractBaseUser, PermissionsMixin):
    (...)
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    (...)

It has the following linked ModelAdmin object:

from django.contrib.auth.forms import UserChangeForm


@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    form = UserChangeForm

I'm using UUID fields for primary keys to ensure maximum anonymity, yet I would like to be able to reset User passwords in the Django Admin (like you can do with the default User object)

However, when I open a User in the admin and press the link to change the password I get the following error message:

User with ID "21362aca-6918-47ea-9b29-275350a89c54/password" doesn't exist. Perhaps it was deleted?

The admin url is still expecting a url with the an integer as its pk value.

So it seems that I have to override the admin url configuration in the ModelAdmin definition, but I was wondering if there was a simpler way to achieve the desired result - as I imagine that replacing the User.pk with an UUID field is a fairly regular occurrence and I image many developers have ran into this problem. I tried to find some kind of settings / toggle to achieve this but to no avail, am I missing something?

Your 'UserAdmin' inherits from ModelAdmin , which provides the urls via the get_urls method for the add, change, delete, etc. views but also a 'fallback' url:

urlpatterns = [
    #...
    url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
    # For backwards compatibility (was the change url before 1.9)
    path('<path:object_id>/', wrap(RedirectView.as_view(
        pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info)
    ))),
]

The url you are following looks like /user/<UUID>/password/ which only fits the regex of the fallback pattern - which redirects you to the change page in such a way that it uses <UUID>/password as the object_id .

Try inheriting from django.contrib.auth.admin.UserAdmin instead as its get_urls method provides the url pattern you need.


Some more poking around...
If your primary key field was an AutoField, the whole process would raise a ValueError('invalid literal for int') when trying to cast int('some_integer/password') in django.db.models.fields.AutoField.get_prep_value in order to prepare the value for a query. Understandable!

HOWEVER : UUIDField uses the get_prep_value method of the base class Field . Field.get_prep_value just simply returns the value without even checking (after all, validating the value should be the job of UUIDField ). So you end up with a query that looks for the bogus uuid '<uuid>/password' , which obviously doesn't exist.

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