Reputation: 2231
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?
Upvotes: 0
Views: 1641
Reputation: 2921
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.
Upvotes: 1