--You received this message because you are subscribed to the Google Groups "Django developers" group.To post to this group, send email to django-d...@googlegroups.com.To unsubscribe from this group, send email to django-develop...@googlegroups.com.For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
I'm convinced that such an idea is ultimately a bad idea: it allows apps exert action at a distance over other apps. It would allow the idea of a user to completely change without any warning simply by modifying a setting.
+1 from me.
One minorish nit: I think that "in the face of ambiguity, refuse the
temptation to guess" should apply equally to reading or writing
user.data, and that there shouldn't be an awkward discrepancy between
reading and writing. Thus, if you've got multiple profiles with
overlapping attributes (the less-common case), you need to be explicit
with AUTH_PROFILES in order to either read or write the overlapping keys
via user.data (but writing is then allowed). In the common case of no
overlapping keys, of course, you don't have to worry about AUTH_PROFILES.
Carl
> 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-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
Identity doesn't have anything to do with automatically dispatching users. All it is is a unique identifier. That's all this proposal honestly enforces that your users have. Some single piece of identifiable data that can be used to differentiate. This could be a username, or an email address. It could be a random string. Anything you want.In your example you might have a TwitterProfile that provides 2 fields, "authenticated_by_twitter" and "twitter username". Then if you want to check how a person authenticated to your site, you'd merely check if user.data["authenticated_by_twitter"] was True. The identifier doesn't need to have that data codified in it, (but it could!) and I honestly do not think the statement "all users must have 1 single string of any length that uniquely identifies them" is that big of a burden.
I like this proposal because I am a big fan of a stripped down `User` model which is basically just an ID, whic provides a common hook into groups/permissions and other django and 3rd party profiles.
What I don't like is the magical `data` bag (accessing `User.data` and filter lookups), the new `AUTH_PROFILES` setting, and the idea of Django automagically or lazily creating profiles for me. I would rather see Django rely solely on its `OneToOneField` field to access user profile data.
Any pluggable app (app1) will know what fields it's own profile model has and how to access them, relative to the central `User` object.
If app1 relies on fields defined by another app (app2), that other app should be a requirement of app1 and app1 would know how to access app2's fields.
I am happy to use project-level signals for everything else (syncing common profile data from app1 and app2, or auto-creating user profiles), because project-level signals give me explicit control over what is going to happen and when. I don't need any more magic here.
Cheers.
Tai.
On Tuesday, 3 April 2012 10:35:41 UTC+10, 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'm not using BDFL fiat here, at least not yet. This is a proposal, and I very much want to hear feedback, objections, and alternatives. I'm particularly interested in hearing from people who've got complicated auth needs and think this absolutely won't work for them.I *have* reviewed all the other proposals and I'm between -0 and -1 on all of them. This means that if you don't like my proposal, you'll probably have to come up with a complete *new* idea to have any chance of getting my vote.Thanks!Jacob
--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-developers/-/PMpcPCKgTuoJ.
Thanks for taking the time to tackle this issue!
On Tue, Apr 3, 2012 at 2:35 AM, Jacob Kaplan-Moss <ja...@jacobian.org> 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.
I very much like the idea of stripping down the user fields. I am
currently using an approach that's very similar to your proposal in
all my projects, except that instead of creating several profiles, I
directly add fields to the User model using this snippet:
https://gist.github.com/1283969 (I am not the initial author)
With this, adding a field to the user model is as simple as:
class MyUser(models.Model):
newsletter = models.BooleanField()
class Meta:
abstract = True
contribute_to_model(MyUser, User)
I already use this to patch the email field in the User model or make
group names not unique for instance.
The only issue is that the contributed models need to be registered as
early as possible. Currently I import them in my urls.py, but we can
imagine adding hooks for such things that need to be done at startup
time (app-refactor? :) or use your AUTH_PROFILES setting for this (not
enthusiastic about this one).
I am fully aware that this can be abused a lot. However this makes a
couple of things simpler:
* No need to proxy fields on the User model, it stays a normal model
with no special treatment
* Conflicts are simply resolved: if two models contribute the same
field, the last one registered wins
* No need for a special-cased form for editing profile-related fields,
a simple ModelForm with includes = ['fields', 'to', 'include'] works.
This is the technique I will use anyway if your proposal is
implemented, and I don't see anything that would prevent this from
working with the new auth so I'm mainly posting this because I haven't
seen this approach in the different proposals for the auth refactor.
-Bruno
Jacob--
You received this message because you are subscribed to the Google Groups "Django developers" group.
Since I weighed in with a +1 on the linked-profiles approach, I should
say that I also find this proposal pretty compelling, and would be happy
to see us go either way. After reflecting on it a bit I think on balance
I prefer this proposal, for the following reasons:
1. Requires zero migration from people with existing projects who are
happy with the existing contrib.auth.User model. I think this is a big
plus: all the other proposals require every single installed Django
codebase to perform a migration.
2. Introduces fewer new-and-different one-off concepts into Django (the
data__ ORM special case, the automatic select_related special case, the
automatic collection of profile fields into user.data...). This also
translates into significantly less implementation work and less new
code, which is another big plus - we want something that'll actually get
done and not introduce a bunch of new code we need to maintain.
By the way, I took the liberty of removing from the wiki page the
references to models/settings circular dependencies, because AFAIK the
statements made about it on the wiki page were simply incorrect.
Importing settings does _not_ immediately loop through INSTALLED_APPS
and load every models file (that only happens if you run a management
command that performs model validation, and even then it happens only
after settings are fully loaded). And "from django.db import models"
itself imports settings, so if there were such a circular dependency
problem, every models.py in existence would already be suffering from
it. I frequently refer to settings in models.py and it does not cause a
problem. If anyone can provide sample code demonstrating that this is
actually a problem, feel free to correct me!
Carl
I just now got around to reading Jacob's solution and Alex's solution.
Thanks to everybody for the thoughts and impassioned debate so far.
Here's my take on it.
First, some background: I haven't used the built-in User module in
several years. I always write my own User model from scratch -- it's
so nice and clean. Want a twitter_username field? Just add it. No need
to add a convoluted foreign key or (oh god) one-to-one relationship to
some other table.
To me, this is the Right Way to do things. The framework should bend
to my needs, I shouldn't bend to the framework's needs.
Also, profile modules need to die. They needed to die circa 2006.
So, with that in mind, I've got to say I prefer Alex's solution. I
really think the right way to do it is:
1. Let you create your own User model, with whichever fields you want.
2. Provide a way to tell Django which model you're using for that.
3. In your own code, just deal with that model like you deal with any
other one. No need to jump through hoops.
4. Third-party models should be changed to use something like "user =
UserField()", which would automatically create a foreign key to the
registered User model. If you change your registered User model after
you've created those third-party tables, you're in for trouble. Don't
do that.
5. Django provides some generic APIs for getting at the registered
user. Example: a middleware that sets request.user based on the
current session.
6. Given that some third-party apps will likely want to get access to
common attributes of a User -- notably, email address -- there could
be some sort of standard interface that User models need to adhere to
(duck typing). So, a custom User model would say "for this User model,
the email address is stored in the database field called 'email'" --
or "this User model doesn't have email addresses."
I chatted about this with Jacob on IRC, and we reached consensus on
this approach. I'd like to get moving on this and would be happy to
take it on myself, starting next week.
Adrian
To unsubscribe from this group, send email to mailto:django-developers%2Bunsu...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
You would add whatever authorization fields you need to your single user
model. If you add new kinds of authorization that require new fields on
the user model, you'd do a schema migration (just like with any other
modification to any other model).
A third-party app that wants to provide, say, Facebook integration,
could either do its own model with a one-to-one to user (nothing is
going to suddenly prevent that approach, it just won't have any extra
sugar around it), or could provide an abstract base class that your user
model can inherit from that includes the needed fields, or could just
document "to use this app, your user model must have these fields: ..."
and let you do it yourself.
Carl
L
4. Third-party models should be changed to use something like "user =
UserField()", which would automatically create a foreign key to the
registered User model. If you change your registered User model after
you've created those third-party tables, you're in for trouble. Don't
do that.
5. Django provides some generic APIs for getting at the registered
user. Example: a middleware that sets request.user based on the
current session.
Those comments were my doing. I swear that at some point in Django's past, importing settings in models was a source of problems. However, the fact that django.db.models clearly imports settings indicates that regardless of whether this problem existed in the past, it certainly isn't the case any more.
Yours,
Russ Magee %-)
Extending on this -- User model requirements should be encoded as part of an integration test suite.
As an example of best practice, django.contrib.admin should implement a UserPropertiesTest that checks that the current USER_MODEL has an email field, and a get_permissions method, and so on. That way, if you swap in a user model that doesn't have the properties that the admin requires, it will get flagged as a issue during testing.
This should, of course, be backed up with a clear documentation of exactly what type of User-duck Django's admin requires.
Yours,
Russ Magee %-)
> On Tue, Apr 3, 2012 at 9:28 AM, Alex Ogier <alex....@gmail.com> wrote:
>> I have written up a little bit about the alternate proposal that I made a
>> while ago, Solution 2a
>> from https://code.djangoproject.com/wiki/ContribAuthImprovements
>
> ...
> 4. Third-party models should be changed to use something like "user =
> UserField()", which would automatically create a foreign key to the
> registered User model. If you change your registered User model after
> you've created those third-party tables, you're in for trouble. Don't
> do that.
>
My only concern about this approach is making a special case of swappable Users. There's at least one other example of a swappable model in Django's core -- the Comments model -- and I have no doubt that we could find other examples with a quick survey.
If we're going down the path of swappable models, I'd rather set up the infrastructure to solve the *general* problem of swappable models, rather than just swappable *user* models specifically.
If this required a massive increase in the effort required, I can see how practicality would determine that we just solve the immediate problem. However, in this case, it's really just a matter of avoiding User-specific naming on 4 features:
a) Specifying the model that can be swapped.
This is just the name of the setting. USER_MODEL is fine by itself, and has obvious analogs with other models. However, an alternate way of spelling the same thing would be to have a single setting for all swappable models:
SWAPPABLE_MODELS = {
'user': 'myauth.SuperDuperUser',
'comment': 'comments.Comment',
}
in which you can define any extensible model, and provide a convenient key (e.g., 'user') to identify the value for that model.
b) Preventing the "default" model from being added to the app cache, or synchronized to the database
contrib.auth is going to need ship with a User model, but we really don't want an auth_user table to be created, or get_model(auth,'User') to resolve, if the default User isn't being used. Same goes for any other swappable model.
Rather than make a special case of User inside syncdb, or nesting model definitions in if blocks, lets add a "swappable" attribute to Meta. If swappable is defined, syncdb checks the contents of SWAPPABLE_MODELS to see if this model has been substituted; if it has, then the table isn't created, and the model isn't added to the app cache. The value of pluggable can match the key in SWAPPABLE_MODELS (so auth.User would define swappable='user', following the previous example)
This can also act as a safety mechanism; if a developer has an app that contains a ForeignKey(User), and the User model has been swapped out, this can now raise a validation warning (notifying the developer that the User model could potentially be swapped out) or error (in the case where the model *has* been swapped out).
c) Providing a way to specify foreign keys to that model
Rather than introduce a User-specific UserField(), introduce a generic LazyForeignKey('user') - both of which are effectively just ForeignKey(settings.XXXX) anyway.
d) Provide a way to discover the current user model
Rather than have auth.get_user_model(), leverage the existing app cache. We already have get_model(); it wouldn't be too hard to add a get_swappable_model('user').
> 6. Given that some third-party apps will likely want to get access to
> common attributes of a User -- notably, email address -- there could
> be some sort of standard interface that User models need to adhere to
> (duck typing). So, a custom User model would say "for this User model,
> the email address is stored in the database field called 'email'" --
> or "this User model doesn't have email addresses."
>
As I've noted elsewhere, this can be both a documentation issue, and backed up by an integration test. As a longer term goal, if someone ever takes the bait and does the Validation refactor for the GSoC (hint hint), it could also be extracted as a validation condition for an app.
Yours,
Russ Magee %-)
Right now, we're trying to solve the swappable model *for User*, which is a bigger problem because it's not confined to a single app; it's defined in auth, but auth functions as a nexus for lots of other things.
Regards,
Luke
> I'm not so sure that it's necessary or even desirable to solve the "general" problem of swappable models. If anyone can swap any model by changing a setting, that sounds like a recipe for confusion to me.
Sure, but that's not what I've proposed. A model would only be swappable if the original app developer declared that model as swappable. An end user wouldn't be able to arbitrarily decide that they wanted to replace a model in an app developed by someone else.
And sure, any feature we add could ultimately end up being used (and overused) in bad ways. However, that's true of any language or library feature. Classes, metaclasses, decorators, or any other Python language feature can be both used and abused, as can Django features like ModelForms or the internals of the Meta class.
My point is that there is nothing about this problem that is unique to User. Django's own codebase contains another example of exactly the same pattern -- Comments. Therefore, we shouldn't pretend that the solution is User specific. At some point, we have to just provide enough documentation and guidance to shepherd people away from bad architectural decisions, and trust that the userbase will take that advice.
Yours,
Russ Magee %-)
My point is that there is nothing about this problem that is unique to User. Django's own codebase contains another example of exactly the same pattern -- Comments.
Totally agree with Jacob here, plus Tai's comment that "There is such
a thing as too generic." We've made the mistake of making things too
generic in the past, and it's kind of infuriating in retrospect, both
philosophically and in terms of code maintenance/understanding.
(django/utils/tree.py, anyone??)
I think our policy should be: make the simplest thing that can
possibly work for a narrowly-tailored use case, then make things more
generic *slowly* if there's a demand. No need to be an Architecture
Astronaut.
Adrian