I've been using Django for a couple of months now on a new project and I've come across something which I think might be improved upon. It deals with the nature of UserProfile, which I think is a very handy tool overall.
The thing I'm not totally satisfied with is the way that UserProfile and User are totally separate objects once you have them at the python level. An example of an area where this separation might be an issue is making a JSON web service that needs to send a full User object to a client. In a typical client app, your User object is most likely one robust object which you have to express in Django as both User and UserProfile. So when you go to pipe this information through a tool like Django-Piston, you have to do a bit of slicing and dicing in your handler code to get both objects through in the same request, or manually create a nice neat object for Piston to deliver to the client in a way the client will be able to consume without having to worry about matchmaking Users and Profile.user_id fields.
Now, I am really not even advanced in Python, and I've only been using Django for a couple of months, but it seems to me like having a convenience method similar to get_profile() that automatically enumerates through your profile object and produces a flattened version of your client friendly user object might be a very useful thing to have.
Maybe there is already an easy and kosher way to do this in Python that I am unaware of, regardless, it just seems like something that would be handy to have for people who deal primarily with using Django to serve non-web clients.
I don't have a good sense of if this is something that is appropriate for Django Auth or should just be a property of my custom profile object, but I thought I might throw it out there for consideration.
Thanks for your time,
Michael
It's kind of hard to talk about "typical" Django app because there are
so many different of them :-). However what's for sure is that
significant share of Django projects consist of many apps. And those app
may store their own profile information related to users. For example
imagine some community site with a forum (an app), news comment system
(another app) and anti-spam plugin (yet another app). A forum keeps
track of users' achievements, comment system knows users' OpenID and
anti-spam have a notion of users' karma. All those things are parts of a
user profile.
If you think in these terms then the whole notion of a single default
UserProfile becomes moot. Yes, it exists and does its job for simple
situations but it's not a good ground for extensibility.
> So when you go to pipe this information through
> a tool like Django-Piston, you have to do a bit of slicing and dicing
> in your handler code
Actually, you will have to do it anyway. Your API shouldn't just expose
your internal model structure because it strongly couples interface to
implementation. In any non-trivial systems APIs cannot be auto-generated.
On 03/18/2011 08:09 AM, Alexander Schepanovski wrote:
> I think using subclass of User model for your fields has the same
> flexibility as separate profile model (since you can have only one).
You can have multiple subclasses of the User model (not that I recommend
subclassing).
> contrib.auth can be fairly simply adjusted to use custom User model
> from settings instead of always using its own User.
Yes, it could, but the entire ecosystem of reusable apps with FKs
pointing to contrib.auth.models.User can't.
> In that sense I am totally for homogenization because it will make
> code cleaner and eliminate unnecessary sql request not because of
> serialization.
Subclassing doesn't solve the extra SQL request unless the base User
model is turned into an abstract model, which would completely break
third-party FKs.
"Custom User model" is definitely a problem I'd like to see solved, but
to do it in a way that's backwards-compatible and allows reusable code
to point FKs at User is a difficult problem that will require adding
significant new indirection capabilities to the ORM, and no-one has yet
proposed a full solution AFAIK.
> Such code is really an eyesore:
> first_name = user.first_name
> middle_name = user.get_profile().middle_name
> last_name = user.last_name
I agree it's ugly, but if it bothers you it's fairly easy to wrap it up
into properties on your profile that proxy to the User model, and then
just always use your profile (you can even add a middleware that creates
a lazy-profile attribute directly on the request, if you want).
> What do you do in such situation? Create a profile class containing
> all required fields? And then if you update some app you need to
> update your composite UserProfile model?
Don't use AUTH_PROFILE_MODULE or .get_profile(). As far as I'm concerned
they bring almost nothing to the table except for the "there can be only
one" restriction, and I'd be just as happy to see them deprecated at
some point. The only justification I've heard for them is that they
allow reusable code to access the user profile, but I'm not sure what
reusable code is going to usefully do with a custom profile model when
it has no idea what properties it has.
Just use OneToOneField and the regular ORM access descriptors, and you
can have as many "user profiles" as you need.
Carl
Could one do something like this in contrib.auth.models:
from django.conf import settings
class BaseUser(models.Model):
#same as current User model except..
class Meta:
abstract = True
if hasattr(settings, 'USER_MODEL'):
import_model(settings.USER_MODEL)
User = settings.USER_MODEL
else:
class User(BaseUser):
pass
Then foreign keys would link to the 'right' user model. There might
have to be some magic with app_name..
Personally I prefer multiple profiles attached to the User model, but
I can see the appeal of a unified User/UserProfile in simple projects.
Cheers
Tom
+1
> Just use OneToOneField and the regular ORM access descriptors, and you
> can have as many "user profiles" as you need.
True. Long ago I've coded a custom AutoOneToOneField[1] that implicitly
creates a dependent profile object on first access. It turned out to be
useful in many cases.
[1]:
http://bazaar.launchpad.net/~isagalaev/+junk/cicero/view/head:/fields.py#L19