Extend django’s User class
There are many days when I think that Python is pretty good, and there are a couple of days when I think Python is divine. Yesterday was one of them. Actually it was a pretty easy task, you just need to know how :-).
I am using django’s default class User that already comes with django. Now I wanted to add some methods to this class. Extending a django model is not the way to go, since that has a different meaning here.
So I got my class, that actually contains additional functionality that should go into the User class.
class UserFunctions:
@staticmethod
def get_shortcut(user):
return "user#" + user.id
...
For a couple of days I was very unhappily using this class via static calls, like so:
u = User.objects.get(id=3)
UserFunctions.get_shortcut(u)
How ugly is that?!
That was pretty annoying, blew up the code, was not handy and it could not be used inside a template and many more drawbacks I guess. I needed something better!
So I thought about mixins, but never used them. Reading a bit online I found out that it should be as easy as:
User.__bases__ += (UserFunctions,)
Alright.
So I changed my UserFunctions class as follows and mixed it in :-).
class UserFunctions:
def get_shortcut(self):
return "user#" + self.id
...
User.__bases__ += (UserFunctions,)
Awesome! Now I can do the following.
u = User.objects.get(id=3)
u.get_shortcut()
And therefore I can also use those functions from within the template, etc, etc. …
That was one of the highlights of my day yesterday … but I guess most of you knew already how to do that
Tony said,
April 20, 2007 at 12:18 pm
I didn’t!
- thanks for that Wolfram, very neat indeed!
Orestis Markou said,
April 20, 2007 at 12:50 pm
Heh, I was thinking of writing down something like that. I didn’t use mixins, because I wanted class methods:
User.score = classmethod(_score)
where _score is a method…
I also used some dispatcher mojo to change the way it appears on the admin page:
def hack_user(sender, args , kwargs):
sender._meta.admin.list_display=(’username’, ‘email’, ‘first_name’, ‘last_name’, ‘is_staff’, ‘date_joined’)
pass
dispatcher.connect(hack_user, sender=User, signal=signals.pre_init)
Maybe when I get my updated blog engine running I’ll look into this further
christian said,
April 20, 2007 at 1:16 pm
Hi,
do you add the line User.__bases__ += (UserFunctions,) in your model in which place do you put it? I’m searching for a similar solution.
Martijn Faassen said,
April 20, 2007 at 2:38 pm
If I understand you correctly, you could also have done the following:
def get_shortcut(self):
return “user#” + self.id
User.get_shortcut = get_shortcut
But I don’t *want* to do things like that unless I’m in a hacking emergency. Adding methods to a class managed by another framework is called monkey-patching and many people dislike it. To me and many others it seems to invite long-term maintenance problems. If I see this use of get_shortcut and look it up in the framework APIs, I can’t find it. And what if the framework developers add a method get_shortcut to the framework class?
One shouldn’t have to change framework code in order to use it in a flexible way (the open closed principle). In Zope 3, the general pattern to do this kind of thing without changing the original class is to use an adapter. Briefly:
class UserFunctions(object):
def __init__(self, context):
self.context = context
def get_shortcut(self):
return ‘user#’ + self.context.id
Then whenever you want to use your user functions, you’d write:
uf = UserFunctions(user)
uf.get_shortcut()
Zope 3 adds facilities on top of this so look up the adapters by interface, which can add all kinds of power to the application but is too much to go into now.
Of course using adapters is more verbose than your trick. It’s also more explicit and less prone to confusion. The adapter APIs can be documented separately.
Ben said,
April 20, 2007 at 6:16 pm
>Extending a django model is not the way to go,
>since that has a different meaning here.
Could you explain that a bit more? I’m new to django…
Wolfram said,
April 20, 2007 at 6:30 pm
@christian
I am adding that right below the UserFunctions class I wrote, which is locate in my models.py yes.
Wolfram said,
April 20, 2007 at 6:38 pm
As Martijn is saying, it is quite hacky. But hey I am using the tools I get
Seriously, there is a problem of course when the framework adds a function with the same name, definitely. I got to think about that.
Though I see the solution I posted as valid, as long as you don’t mess with the actual User class internals, in whichever way.
The suggested adapter way is of course viable, but i think it adds another class, which actually should not be needed. Instead I think the framework needs to offer the extending mechanism, if default class-extending is occupied otherwise.
@Ben, model inheritance is supposed to do the following:
I can not tell if that is the right path, my gut feeling says no, but thats very subjective!
and described here http://code.djangoproject.com/wiki/ModelInheritance
Jay P said,
April 20, 2007 at 9:48 pm
I think that using Django’s built AUTH_PROFILE_MODULE would have been the “Django” way of doing this. Wouldn’t have involved any monkey-patching, and it would be easy recognizable by any other Django developer using your code.
Your final result would then look something like:
u = User.objects.get(id=3)
u.get_profile().get_shortcut()
See http://www.b-list.org/weblog/2006/06/06/django-tips-extending-user-model for the best explanation of this technique.
aaloy said,
April 21, 2007 at 10:27 am
Thank you for sharing this info with us.
The post itself and the comments are very interesting.
I like the trick/hack and perhaps the way to avoid some suprises if the Django framwors someday uses the same funtion name could be writing some unit tests for the model that could test the existance of the function.
Other trick, you can use another language to write the hack extensions, for example I would use a name something like get_nombreCorto in Spanish or get_NomCurt in Catalan. The chances that Django some day could use that names are very, very low
So, its a nice trick for me.
Thank you.
Wolfram said,
April 21, 2007 at 9:06 pm
@Jay that is when you really need the profile, which is another query. in my case i dont need the profile every time, so it would be a waste of query, just for being able to call the one function.
And btw I think the userprofile thing is also a pretty quick+dirty solution, it bares so many drawbacks. it just doesnt feel natural.
Michael said,
April 24, 2007 at 4:52 pm
Now the question is, how would manage.py’s syncdb handle this. If you don’t have a user database already made, could you, in theory, add fields by using something like:
class UserExtensions(object):
blah_field = models.DateField()
User.__bases__ += (UserExtentions,)
I’m guessing that wouldn’t work, though it might depend on how, and what order, syncdb builds tables?
I could just be completely off-base, too. I’m still fairly new to Django.
Artur Gajowy said,
August 4, 2009 at 9:00 am
For all those, who use django 1.1 and read this - there are the Glorius Proxy Models (http://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models)
Extending Django’s User Profile « Weblog of a bee, thts me VB said,
November 6, 2009 at 3:14 pm
[...] 6. http://wolfram.kriesing.de/blog/index.php/2007/extend-djangos-user-class [...]
fasdfqyety said,
January 5, 2010 at 5:29 am
福音的空间