Django: Extending User Model
One of first issue one faces with starting developing with django is extending User. There are three approaches various people are using.
User Profile
Django has a concept of UserProfile model. This is the recommended way to extend User model in django, recommended by the django book and django's official documentation. Lets say the name of your project is myproj, you create an app to manage the accounts user registration etc, lets call it accounts. After "startapp accounts" go to the accounts/models.py, and create a model:
# extending user # {{{
class UserProfile(models.Model):
user = models.ForeignKey(User)
openid = models.URLField(blank=True)
def _get_comments(self):
return Comment.objects.get(user=self, is_public=True)
public_comments = property(_get_comments)
del _get_comments
class Admin: pass
# }}}
While this is enough to store more information about user, django adds a shortcut to access this user profile model more conveniently by calling user.get_profile() if in your settings.py file you add the following line:
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
While this is easy to get started, and "official" way of doing things, it has one major drawback: you put your relations in user profile model. What I mean is, if for example you had a concept of friends, you will say:
# extending user # {{{
class UserProfile(models.Model):
user = models.ForeignKey(User)
openid = models.URLField(blank=True)
friends = models.ManyToManyField('self', symmetrical=True)
...# }}}
and to access it you will have to use something like:
# show_friends # {{{
@login_required
def show_friends(request, userid):
user = get_object_or_404(User, id=userid)
friends = user.get_profile().friends
# do other things with friends and return
return HttpResposne(
",".join(
[
friend.get_profile().username
for friend in user.get_profile().friends
]
)
# }}}
See the problem? You have to remember to call get_profile() at appropriate times, and this sounds a little less right. Some variable are user.XYZ where as some are user.get_profile().ABC, and you are friends with "user", and not his profile! Keeps me wanting a neater solution. Then there is the hassle of making sure that one to one relationship between User and UserProfile is maintained, which on deleting User, you have to delete UserProfile and vice versa.
The replaces_module Method
This is an incredibly hacky but works with issues method that is on django's wiki on the same topic.
Lost-Theories Solution
I came across another solution for it via Jeff Craft 's lost-theories.com source code; that I am actually using for my current project. In this case he created a LostUser that has a foreignkey to django's User model, and uses LostUser throughout the code.
class LostUser(models.Model):
user = models.ForeignKey(User, unique=True)
# Location info
city = models.CharField(maxlength=200, blank=True)
state = models.CharField(maxlength=200, blank=True)
country = models.CharField(maxlength=200, blank=True)
...
The primary problem I faced was the lack of request.user in views and user object in template niceties. I solved that by writing the following middleware:
from vakow.accounts.models import MyUser
class MyUserMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated():
request.vuser = MyUser.objects.get(duser=request.user)
else: request.vuser = None
and the following context-processor:
# context_processor # {{{
def context_processor(request):
d = {
'media_url': settings.MEDIA_URL,
}
d['duser'] = request.user
if request.user.is_authenticated():
d['vuser'] = request.vuser
else: d['vuser'] = None
return d
# }}}
This has the drawback of having to deal with two objects about a user, duser and vuser, where duser is an instance of django's User models, and vuser in my project's user extension. Such naming conventions helped in disambiguating what instance I am talking about, and since almost all aspect of my code worked with my user class, this was not really a problem. I had added a few properties in my derived class for username, email to query it from self.duser.username when self.username is requested. Life was good, and the only time I had any issues was when dealing with django's comment framework, as comment objects contained a foreignkey to duser, and not vuser. I hacked my comment to added a property vuser in Comment model.
The Final Solution
This is partially derived from the cool tip I got on Pythoneer. Here is what you can do:
# Extending User # {{{
User.add_to_class("openid", models.URLField(blank=True))
User._meta.admin.fields += (
("AmitCom Extensions", { 'fields': ('openid', ) }),
)
class UserExtension(object):
def _get_comments(self):
return Comment.objects.get(user=self, is_public=True)
public_comments = property(_get_comments)
del _get_comments
User.__bases__ = User.__bases__ + ( UserExtension, )
# }}}
This sounds the best solution to me. All fields, relations and custom methods are on User model and no multiple Models to keep track of.
PS: If you are curios about all the "# {{{" and "# }}}" in the code above, they are code folding markers, learn about code folding for python in vim here.
Labels: Python Programming Invented Here Django
If you find this post useful, please conside buying me a pizza!


1 Comment
-
<< Home<a href='http://www.luludroppy.com/pollykandros.html'>pollykandros</a> delivery [url=http://www.luludroppy.com/pollykandros.html]pollykandros[/url] delivery
<a href='http://www.luludroppy.com/kezzymarget.html'>kezzymarget</a> best [url=http://www.luludroppy.com/kezzymarget.html]kezzymarget[/url] best
By kezzymarget, at Thursday, July 26, 2007