Extending auth.User model, proxied fields, and Django admin
(Edit: I know that there's a totally separate feature in Django called "Proxy Models". That feature doesn't help me, because I need to be able to add fields to UserProfile.)
So I'm starting a new Django app and I'm creating a UserProfile model which is a extension of django.contrib.auth.models.User and failed attribute requests back to User, as follows:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
def __getattr__(self, name, *args):
if name == 'user' or name == '_user_cache':
raise AttributeError(name)
try:
return getattr(self.user, name, *args)
except AttributeError, e:
raise AttributeError(name)
This works fine in general, but breaks when I try to use a User
field in UserProfileAdmin.list_display. The problem is in the admin validation code here:
def validate(cls, model):
"""
Does basic ModelAdmin option validation. Calls custom validation
classmethod in the end if it is provided in cls. The signature of the
custom validation classmethod should be: def validate(cls, model).
"""
# Before we can introspect models, they need to be fully loaded so that
# inter-relations are set up correctly. We force that here.
models.get_apps()
opts = model._meta
validate_base(cls, model)
# list_display
if hasattr(cls, 'list_display'):
check_isseq(cls, 'list_display', cls.list_display)
for idx, field in enumerate(cls.list_display):
if not callable(field):
if not hasattr(cls, field):
if not hasattr(model, field):
try:
o开发者_JAVA百科pts.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
The problem is that while an instance of UserProfile will have proxied fields, e.g. email, the UserProfile class itself doesn't. Demoed in the Django shell:
>>> hasattr(UserProfile, 'email')
False
>>> hasattr(UserProfile.objects.all()[0], 'email')
True
After some digging, it looks like I'd want to override django.db.models.options.Options.get_field for UserProfile._meta. But there doesn't seem to be a non-hacky way to do this (I have a very hacky solution right now, which involves monkey-patching UserProfile._meta.[get_field, get_field_by_name])...any suggestions? Thanks.
Keep it simple. Here's an example of a UserProfile model from a library we use:
class UserProfile(models.Model):
user = models.OneToOneField(User)
accountcode = models.PositiveIntegerField(null=True, blank=True)
That's it. Don't bother with the __getattr__
override. Customise the admin interface instead:
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class UserProfileInline(admin.StackedInline):
model = UserProfile
class StaffAdmin(UserAdmin):
inlines = [UserProfileInline]
# provide further customisations here
admin.site.register(User, StaffAdmin)
This allows you to CRUD the User
object, with access to the UserProfile as an Inline. Now you don't have to proxy attribute lookups from the UserProfile to the User model. To access the UserProfile
from an instance of User u
, use u.get_profile()
This is not a proxy class, it is a relationship. See more on Proxy Models, which are a subclass of the original model, with the Meta.proxy = True
If you just want a field from User to be in list_display in your UserProfileAdmin, try:
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user__email',)
If you want to have it as part of the form, add it into your UserProfileForm as an extra field, and validate it in the form.
精彩评论