On Fri, Mar 9, 2012 at 10:36 AM, Luke Plant <l.plant...@cantab.net> wrote:

What you are really saying is this: being pragmatic means that we
> prioritise *your* immediate need above the need to keep the code and the
> docs maintainable, and above the need to maintain compatibility with
> existing installations.
>

Of course not. Firstly, this is not *my* need; it's a feature that many
have requested. Take a survey of the web applications that you use on a
weekly basis. How many of them allow you to sign in with an email address?
Compared to that, how many required you to invent a username? Of those, I
would wager that a good many are Django apps.

Secondly, I'm advocating for a backwards-compatible solution that won't
break existing installations. That point keeps getting missed, as you and
others keep assuming that we need schema migrations for this change—we
don't. People who are happy with the existing user model can keep using it,
as-is. But people who aren't happy with it should be able to provide their
own.


> There are a million-and-one hacks we would add if we took that approach,
> and Django would have a million-and-one more bugs, or possibly much
> worse - if you add just 10 boolean switches like the one you suggested
> in an earlier email, you've got over 1000 combinations to test and
> debug.


I was giving an example of a pragmatic solution: sometimes an `if`
statement is better than a far-reaching architectural change. I don't
advocate boolean flags for solving the auth.User problem, I'm merely trying
to encourage a shift in thinking from lofty aspiration to humble
accomplishment.


> I believe that all the problems you have can be solved already:
>
>  - you can write your own authentication backend to authenticate
>   by email
>

True.


>  - you can write your own login and account creation views
>

True.


>  - you can even override admin views by inserting items into
>   urls.py
>

True.

But these things aren't the problem. It's the auth.User model that poses
the challenge. If you haven't implemented email-based authentication in
your own apps, let me suggest that you try it before you tell me how easy
it is. At some point, you will run into these limitations posed by the User
model:

 - The username field can't be used to store emails (it's too short and
restrictive). No problem, let's use the email field;
 - But the username field is required. OK, we can just fill it with a
random string;
 - The email field does not have a unique constraint. But we can do a
reasonably good job applying the constraint in python;
 - The email field is not indexed. We don't want full table scans whenever
someone logs in, so let's write a custom (and possibly non-portable) SQL
migration to add an index.

All this, and the email field is still too short to hold all valid email
addresses. On top of that, we've littered our database and admin interface
with random strings for usernames, and littered our application with the
code to generate them.

Yes, these are all more-or-less hacky. And they have problems. For
> instance, what if you have two accounts with the same email address? (I
> have that in some of my Django projects, BTW). The current code allows
> it, so we can't change anything that would make it impossible, at either
> the DB level or the Python level. This is the kind of issue that means
> you can't just use the existing models.
>

Of course, which is why we need to allow developers to supply their own
User model, while maintaining backwards compatibility for those who are
happy with the built-in model.

A full and correct solution that didn't introduce dozens of new bugs is
> quite hard - and I'm talking about just the code required to fix this
> one feature for you, without doing anything more generally useful.
>

Again, it's not just for me. And, yes, I appreciate that it's not a trivial
problem. It's hard, but all the problems worth solving are.

That is why we're not going to put hacks like this in core - we would
> have to support these hacks for at least 3 versions if we did. We are
> interested in *removing* existing hacks from our code base, and this is
> vital if Django is going to see increases in features and functionality.
> We are not interested in adding more hacks.
>

I can appreciate the appeal of a pristine, hack-free code base, but I've
never seen one. Eventually reality always trumps grand design. If my
example of the `if` statement above has led you to believe that I'm
proposing a mindless hack, then I apologize. I'm proposing something
between a mindless hack and grand design. Something pragmatic.

Some examples from a quick Google groups search for "auth User":
>
> http://goo.gl/swTpr


Some hand waving ("It's deceptively difficult"), with no examples, and no
mention of #3011.

http://goo.gl/fFlKh


This is someone with mediocre design skills trying to get paid to hack on
Django over the summer, but failing to convince anyone that he has a clue
about how to solve this problem. Save for some humorous jibes from Russell,
the thread is almost devoid of good content, except for Russell's example
of the COMMENTS_APP setting, which sounds a lot like "the idea of a
'pluggable' User model"—a proposal that the GSoC page indicates has already
been rejected, but without explanation.

> Ticket #3011 was rejected without much of a
> > reason. HM asked for an explanation both in the ticket itself and on
> > django-dev; no explanation was ever given.
>
> I don't know what thread you are talking about. Hanne Moa brings up the
> subject here and gets two replies from Russell:
>
> http://goo.gl/7p1JN


I was thinking of this thread:

http://goo.gl/nehrO

wherein Carl Meyer (I confused CM with HM) enquires about #3011 and
receives no reply. In the thread you mention, there isn't any detail about
#3011, other than that the patch it contains is "*completely* the wrong
approach."

> The GSoC page
> > (https://code.djangoproject.com/wiki/SummerOfCode2011#Enhancedauth.user)
> > is a frustrating read. It goes on and on about how hard the problem is
> > and how wrong your solution is, but doesn't provide any detail as to why
> > it's hard or why it's wrong.
>
> It does provide detail - it gives a list of issues you have to consider,
> and tells you where to search for the other issues.
>

It poses a list of questions (how can we represent a generic user? how
should we approach authentication vs. authorization? how do we handle
migrations?) that all presuppose a grand redesign of auth.User instead of
an incremental improvement, but it doesn't discuss at all why an
incremental improvement won't be considered. And then it refers would-be
implementers to read up on Lazy Foreign Keys (LFKs), which I contend are
neither sufficient nor necessary for an auth.User refactor.

To illustrate this last point, consider some of the compatibility problems
we're liable to face when replacing django.contrib.auth.models.User:

1. Parts of Django, numerous third-party apps, and countless bespoke
software installations have models with FKs to User. LFKs could help here,
in that we could delay binding to the precise model until that binding is
known (through configuration, or whatever). But if, to use an LFK, any of
the third-party apps or bespoke software has to be changed (say, from
`ForeignKey(User)` to `LazyForeignKey('User')` or
`ForeignKey(LazyModel('User'))`), then LFKs are not backwards compatible.
They may be the place we want to end up, but they're not the place to
start, as they require too much change in software we don't control.

2. Parts of Django, numerous third-party apps, and countless bespoke
software installations have ModelForms built around the User model. The
model is specified in the ModelForm's Meta class, and must be available at
class definition time. LFKs don't help here. We could extend ModelForm to
support delayed binding, but if that involves changing any existing
software (say, `model = LazyModel('User')`), then, again, we've broken
backwards compatibility.

3. Parts of Django, numerous third-party apps, and countless bespoke
software installations import User from django.contrib.auth.models. But
we're talking about allowing developers to specify their own User model, so
code that imports d.c.a.m.User would no longer be valid. LFKs don't help
here, either.

What's needed is a way to maintain the interface defined by the existing
django.contrib.auth application (a User model in django.contrib.auth.models,
an AuthenticationForm in django.contrib.auth.forms, a ModelBackend in
django.contrib.auth.backends, etc) while allowing the developer to specify
the implementation—whether the stock User app, a reusable third-party app,
or their own.

I've written such a pluggable User app. By default, the User model works as
before. But I've also provided a User model (and forms, admin, backends,
etc) implementation that removes the limitations of the User model noted
above and allows for email authentication. It's still a bit rough around
the edges, but it works, insofar that `manage.py createsuperuser` doesn't
ask for a username, `manage.py changepassword` takes an email address, and
I can login to the admin interface with my email address.

It works for me, and that's well and good since I have a client that needs
this functionality, but I don't want to maintain the fork forever. I would
like to provide a patch for consideration for trunk, but before I waste my
time and yours, I'd like to understand why the notion of pluggable user
models was rejected (as indicated on the GSoC page), when Russell seems to
be advocating for a design of that sort with his reference to the
COMMENTS_APP setting.

And to be clear, I don't consider the pluggable user model approach that
I'm taking and the Lazy Foreign Key approach to be mutually exclusive.
Rather, I see my approach as something of a compatibility shim until
sufficient time has elapsed to pull the plug on imports of
django.contrib.auth.models.User. I propose that we continue to support the
existing auth interface (defined more completely above, but for brevity, a
User model at django.contrib.auth.models) but start raising a deprecation
warning in the 1.4.1+ timeframe. Meanwhile, we introduce LFKs (which I have
ideas about, but which, again, are not required for an incremental
improvement to auth.User), so that maintainers of reusable apps can start
to take advantage of delayed binding. At some point, say, 1.5, or 2.0, we
drop support for the existing auth interface (which is to say, apps should
not expect to find a User model at django.contrib.auth.models) and the LFK
becomes the "one—and preferably only one—obvious way to do it."

Cheers,

Clay

-- 
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.

Reply via email to