On 03/04/2012, at 8:35 AM, Jacob Kaplan-Moss wrote: > Hi folks -- > > I've written up a proposal for how *I* would like to address refactoring > auth.user: https://gist.github.com/2245327. > > In essence, this does two things: > > * Vastly "prunes" the required fields on auth.user. The only things left are > an "identifier" (which could be username, email, url, uuid, whatever), and a > password. > * Introduces a new "profile" system that provides a way to contribute extra > related fields. Multiple profiles are supported, along with some syntactic > sugar for dealing with multiple profiles in a reasonably reusable way. > > And that's about it. I'm deliberately trying to find a middle ground between > "do the minimum to allow people to move on" and "throw out and rewrite > django.contrib.auth entirely". I'm not expecting everyone to be thrilled by > this idea, but I'm hoping that this is "Good Enough" for almost everyone. > > For more please see the document. Please do try to read the whole thing: I've > had a few rounds of feedback incorporated already, and there's even an FAQ at > the end.
I've added a summary of this option to the wiki: https://code.djangoproject.com/wiki/ContribAuthImprovements#Solution5:Profile-basedsingleusermodel As always, feel free to correct/update as necessary. >From my reading of the proposal, here are some questions/edge cases. For some >of these questions, I fully expect the answer may be "You just can't do that"; >however, given that they're plausible edge cases, it's worth being explicit >about what we're aiming at. * Auto-profile creation: What if my profile is: class MyProfile(Profile): first_name = CharField(max_length=100) last_name = CharField(max_length=100) i.e., first_name and last_name are both required fields. How does the profile get automatically instantiated in this case? Doesn't the auto-instantiation of profiles essentially mean that there can be no required fields on a profile (or, at least, on an auto-instantiated profile)? * Regarding AUTH_PROFILE and collisions: What if I have 2+ profiles, but no clear order of precedence? e.g., class MyProfile1(Profile): name = CharField() email = EmailField() color = CharField() class MyProfile2(Profile): name = CharField(unique=True) email = EmailField() age = IntegerField() Lets say that for some internal logic reason for profile processing, I need both email and age on MyProfile2, but name from MyProfile1. Under these circumstances, there's no way I can specify a single AUTH_PROFILE ordering that will satisfy my requirements. Is this a case where I have to resort to explicit naming? Or, alternatively, should we be treating AUTH_PROFILE as a mechanism for purely specifying the resolution to specific lookup problems? i.e., instead of just specifying an order, we specify which model we want ambiguous fields to come from: AUTH_PROFILE_LOOKUP = { 'name': 'myapp1.MyProfile1', 'email': 'myapp1.MyProfile2', 'color': 'myapp1.MyProfile2', } That way, if a field is unambiguous, it's returned from whatever model provides it; if a field is ambiguous, we return the one specified; and if the ambiguity isn't resolved, we return a KeyError (or catch this case as a validation error on startup). * Required fields and AUTH_PROFILE Combining the previous two points; what if MyProfile2 has a required field, but it isn't a field selected by data[]? e.g, in the previous example, MyProfile2.name is a required unique field; but if it isn't instantiated with useful data, the profile instances can't exist. == Commentary == For me, the previous three edge cases essentially point to the fact that there can ultimately only be 1 profile for "core" user information. There will almost certainly be uses for multiple profiles (e.g., to store OpenID credentials) -- but then, this is already the case (I've got more than one project in the wild with multiple "UserProfile" objects, without using AUTH_USER_PROFILE). However, as soon as there are overlaps between profiles, you're going to end up with partially populated profiles, and eventually someone will write a 'unified' profile model for each project that doesn't have the overlap ... ... at which point, we've essentially arrived at a swappable User model, just with a required join to get profile data, and a bunch of magic attributes and ORM properties to hide the fact that the join exists. I understand Django's history has examples where swappable models caused problems that we don't want to revisit. However, as I understand it, these problems were largely caused by the fact that it was *easy* to accidentally swap out a model, and that change wouldn't be effectively communicated to other developers on the same codebase until the magic smoke escaped. It's also worth pointing out that the problems with swappable models aren't really avoided by the profile-based approach. If I've got a project with N user profile models, grabbing user.data['name'] from one of them, another developer can very easily modify the order of AUTH_PROFILES, or modify the apps in INSTALLED_APPS to add to or change the user profiles that are currently in effect, or alter the precedence in AUTH_PROFILES to override the attribute we need. If the objection to swappable user models is that it's easy to swap out models, then it's just as easy to change the expectations for User models via a profile -- possibly easier, because you aren't modifying a single "AUTH_USER_MODEL" setting; you're changing one of several settings whose consequences *may* include changing the operation of the User model. AUTH_USER_MODEL at least has the consequences written on the box. (User models are hard. Lets go shopping. :-) One possible way to avoid this problem would be to make it harder to make these sorts of changes -- i.e., add project/install-level tracking for certain key settings. So, when you do a syncdb, we set a key-value pair in the database for the current value of AUTH_USER_MODEL (or whatever mechanism needs to be persisted). Django checks this value against the current settings.AUTH_USER_MODEL value on startup; if the setting value doesn't match the installation value, we can throw an error on startup. Would this approach temper your objection to swappable models? At the end of the day, I'm happy to go with a BDFL judgement on this. However, it's interesting to note that almost every time this problem arises, a "swappable" model of some sort is usually the initially proposed solution. That's because it's an obvious solution -- an obvious solution with problems, but an obvious solution, nonetheless. The alternative being proposed here isn't *clearly* better -- it's really just a matter of making a strategic choice to pick a different set of pain points. To make matters works, it seems to me that it will require a lot more work to execute -- far from being "minimalist", it's invasive on auth code, ORM code, and (in the long term) the internals of every app that uses User. For example, any code written against the fields on the existing auth.User will need to be updated to use the new data[] access mechanism. Yes, this will be introduced gradually over releases, but it doesn't change the fact that every app that currently accesses user.is_staff will need to update to user.data['is_staff'] at some point in the next few releases. By comparison, the swappable user approach generally means no changes to app code (or just modifying ForeignKey(User) to LazyForeignKey(User), or ForeignKey(settings.USER_MODEL)), and documenting that is_staff is a required attribute on User for using the app (a property that can be validated as a project integration requirement by a test suite). There are also some aspects about this proposal that make my teeth itch -- filter(data__...), for example, strikes me as code that will be hard to write, hard to debug, and a PITA to maintain. And if you can filter(data__...), then you need to be able to order_by('data__...') as well. But all this complexity is required if you want to maintain decoupling of profiles. It also requires adding a feature to the ORM to support a specific use case in User, rather than a generic problem (although I suppose we could implement it in such a way as to make "profiles" an option for any other model that needs them). If we're going to make a choice to avoid the "obvious" solution, I think we need to be very clear (both now, and for posterity) why we made the choices we have made, rather than trying to address or ameliorate the problems with the "obvious" solution. Yours Russ Magee %-) -- You received this message because you are subscribed to the Google Groups "Django developers" group. To post to this group, send email to django-developers@googlegroups.com. To unsubscribe from this group, send email to django-developers+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.