Beef with Django; admin and auth

4 views
Skip to first unread message

james...@gmail.com

unread,
Aug 14, 2008, 3:47:07 PM8/14/08
to Django developers
Me and my group here have been using Django for some time now with
great success on a pretty large project. We've even attended a sprint
in Lawrence and managed to total a rental car in the process.

But, I have to say that my biggest beef with Django since day one is
how much the Admin contrib relies on the Authentication contrib. For
people with unique authentication needs or want to do something
differently than Django does, the admin is impossible to use because
it just loves django_auth.

I know 1.0 is upon us and I am not trying to push any issues right
now. Is there a reason though that Django can't define an interface
for user authentication which can be overridden entirely? Right now
the alternate backends are decent but they certainly don't solve the
problem completely.

Within this vein the User object is the most annoying. It would be
nice if we could define our own with certain methods that conform to a
user spec. Groups and Permission are either completely overkill or
done completely differently in our case and user messages and
notification are handled with another service, not a DB table.

For very high load applications modifications like this are our life
blood. We can't just let the user model write messages to the DB or
lookup permissions the way it wants to, but using our own User models
would prevent us from also using the Django admin without a bunch of
hacky synchronization techniques and extra overhead.

If I am way off base here, I'm sure someone will let me know. But so
far this is my biggest disappointment with django.

Scott Moonen

unread,
Aug 14, 2008, 4:09:23 PM8/14/08
to django-d...@googlegroups.com
Hi James.  Just a thought, and perhaps this is not applicable in your case.  But in my own case, this is not an issue because admin users are quite distinct from site users.  What I do is issue private django.auth logins to my site administrators, but use my own auth models and mechanisms for public site users.  It's pretty easy to create middleware and decorators to mimic enough of django.auth for me to use transparently in my views and templates.  The only other overhead I have is that I need to create two projects / settings files, one for the admin and one for my site, since I only want to enable the appropriate auth middleware for each.

Again, this may not cut the knot in your case; perhaps for some reason all of your site users are also admin users, or there is some other way that admin users and site users are commingled and for which generic foreign keys doesn't solve your problems.  But it's something to think about, at least, and in the typical case where the admin users are pretty distinct from public users it might be a reasonable workaround.

  -- Scott

James Bennett

unread,
Aug 14, 2008, 4:09:38 PM8/14/08
to django-d...@googlegroups.com
On Thu, Aug 14, 2008 at 2:47 PM, james...@gmail.com
<james...@gmail.com> wrote:
> But, I have to say that my biggest beef with Django since day one is
> how much the Admin contrib relies on the Authentication contrib. For
> people with unique authentication needs or want to do something
> differently than Django does, the admin is impossible to use because
> it just loves django_auth.

Which has been brought up quite a lot (see archives of this list and
django-users). As of the newforms-admin merge, however, the entire
admin is built around classes which encapsulate this stuff so that --
if using an alternate auth backend simply isn't enough configurability
-- you can subclass and completely replace the way they do auth. I
tried this out back when NFA was still under development, and found it
to be quite a lot less painful than I'd expected it to be.

If there are specific hurdles preventing you from being able to do
that effectively, please do bring them up.


--
"Bureaucrat Conrad, you are technically correct -- the best kind of correct."

james...@gmail.com

unread,
Aug 14, 2008, 4:29:55 PM8/14/08
to Django developers
Sorry if I am covering ground already covered, my search skills are
weak. But before I get my hopes up are you suggesting that NFA is
modular enough to use my own user model?

Are there some archives you could link to or anything to point me in
the right direction? I have been digging through NFA a bit in the last
few minutes but I don't see anything obvious yet. I will keep looking
of course.

On Aug 14, 4:09 pm, "James Bennett" <ubernost...@gmail.com> wrote:
> On Thu, Aug 14, 2008 at 2:47 PM, jameslon...@gmail.com

James Bennett

unread,
Aug 14, 2008, 5:03:35 PM8/14/08
to django-d...@googlegroups.com
On Thu, Aug 14, 2008 at 3:29 PM, james...@gmail.com
<james...@gmail.com> wrote:
> Are there some archives you could link to or anything to point me in
> the right direction? I have been digging through NFA a bit in the last
> few minutes but I don't see anything obvious yet. I will keep looking
> of course.

It's not a case where there's going to be something that jumps up and
yells "change me and everything will work like you want it to"; it's
something that's going to require a bit of reading to get a feel for
how requests and responses flow through the admin. For example,
AdminSite.has_permission() (which checks whether a user has any access
at all to the admin) and the various
has_(add|change|delete)_permission() methods on ModelAdmin take an
HTTP request as argument, *not* a user, which means you can pull any
information you want out of the HTTP request to make decisions about
what to allow.

Jeff Anderson

unread,
Aug 14, 2008, 5:08:52 PM8/14/08
to django-d...@googlegroups.com
Hello!

james...@gmail.com wrote:
> Within this vein the User object is the most annoying. It would be
> nice if we could define our own with certain methods that conform to a
> user spec. Groups and Permission are either completely overkill or
> done completely differently in our case and user messages and
> notification are handled with another service, not a DB table.
>

This has come up where I work as well. We have an LDAP server with about
8700 user accounts. For tools that not too many people use, and ldapauth
backend has worked out fairly well. The ldapauth backend we've used just
creates a row in Django's auth_user table. This isn't the most ideal, as
changes in the ldap aren't reflected in our Django site until they try
to log in again.

The solution that I've come up with is to inherit from
django.contrib.auth.models.User and override anything that would cause a
write to the Database. We only want read only support from our Django
sites anyway, so it seems like a decent solution. I'm no guru when it
comes to the database handling stuff in Django, so I'm not even sure if
this approach is even the least bit sane.

The biggest problem that I've seen with creating user-like objects
instead of using contrib.auth is I can no longer store references to
them in other models' ForeignKey or ManyToManyFields. If user bob opens
up a trouble ticket, and alice wants to be included in the emails that
go out when the ticket is updated, the ManyToManyField throws an
exception, and I have to figure out a hack job way of storing the
references in the database and then creating instances when needed. If I
make my custom user-like object picklable, and store them in a list, and
store that in a PickledObjectField, it kind of works like a
ManyToManyField does. It just doesn't 'feel' like it's the right way to
do things. It just seems too messy.

I haven't looked into a lot of the NFA new stuff, but it would be nice
to be able to plug in my own custom auth stuff. I'll put that in my list
of Django stuff to play with.


Jeff Anderson

signature.asc

james...@gmail.com

unread,
Aug 14, 2008, 5:19:04 PM8/14/08
to Django developers
I see -- I see... this is indeed very new and improved. I shall play
with it for a while and see how far I go. Just from browsing the class
however I think this could solve most of my problems albeit with a
healthy dose of copied code.

While this is a huge improvement from last time, I still think it
would pay to draw some stronger lines in the sand with regards to the
admin and authentication modules.


On Aug 14, 5:03 pm, "James Bennett" <ubernost...@gmail.com> wrote:
> On Thu, Aug 14, 2008 at 3:29 PM, jameslon...@gmail.com

James Bennett

unread,
Aug 14, 2008, 5:30:09 PM8/14/08
to django-d...@googlegroups.com
On Thu, Aug 14, 2008 at 4:19 PM, james...@gmail.com
<james...@gmail.com> wrote:
> While this is a huge improvement from last time, I still think it
> would pay to draw some stronger lines in the sand with regards to the
> admin and authentication modules.

Well, it needs some sort of authentication available by default so
that people don't have to go write auth systems for it, so the
sensible default is to use the auth system that's bundled with Django.

In a lot of common cases this works pretty well; in situations where
you're integrating with legacy auth systems it requires a bit more
work but is still doable. Or, to borrow some other language's mantra,
it aims to make the easy things easy and the hard things possible ;)

james...@gmail.com

unread,
Aug 14, 2008, 6:06:26 PM8/14/08
to Django developers
Sorry if this is double posted.

On Aug 14, 5:30 pm, "James Bennett" <ubernost...@gmail.com> wrote:
> On Thu, Aug 14, 2008 at 4:19 PM, jameslon...@gmail.com
James, I completely agree 100%. However, the reliance on django auth
in the admin is a bit heavy handed you must admit. If there is a
mechanism to make authentication completely modular and abstract why
not rely only on that.

I'll give you with an example, just so you know I am not spouting off
without cause. In the main AdminSite class within the "login" function
(line 229) the first real line of code imports auth.models.User. This
is only used if the sanctioned channels for authentication (the
authenticate function 251:266) fail, but why?

Would it not make more sense to include that chunk of code as another
backend, or an alternate routine in the default authentication
backend? This was the first stumbling block; and while I can get
around this by copying most of the login funtion, it would be sweet
not to need to.

James Bennett

unread,
Aug 15, 2008, 3:32:59 AM8/15/08
to django-d...@googlegroups.com
On Thu, Aug 14, 2008 at 4:19 PM, jameslon...@gmail.com
> James, I completely agree 100%. However, the reliance on django auth
> in the admin is a bit heavy handed you must admit. If there is a
> mechanism to make authentication completely modular and abstract why
> not rely only on that.

Because simplicity wins.

Step back from your personal use case for a moment, and consider the
general situation. There are four primary ways people do auth in
Django:

1. Using django.contrib.auth and nothing else.
2. Using django.contrib.auth with a custom backend, to migrate from a
legacy auth system into using django.contrib.auth exclusively.
3. Using django.contrib.auth with a custom backend, to maintain
parallel auth in Django and in a legacy system.
4. Not using django.contrib.auth in any way.

Three of these four situations use django.contrib.auth in some
fashion, and in my experience they represent the overwhelming majority
of people who are using Django. So making contrib.auth as painless,
seamless and out-of-the-box simple as possible to use needs to be a
very high priority; one thing we don't want to get into is a situation
common in more "modular" systems where introductory documentation ends
with "Congratulations on getting the base system installed; here's a
list of five hundred components you can use, go pick the one you want
because we refuse to take a stance on it."

> I'll give you with an example, just so you know I am not spouting off
> without cause. In the main AdminSite class within the "login" function
> (line 229) the first real line of code imports auth.models.User. This
> is only used if the sanctioned channels for authentication (the
> authenticate function 251:266) fail, but why?

I'm kind of stumped here because that method strikes me as a very good
example of how flexible the admin is these days. The admin gets there
by the following process:

1. It calls the has_permission() method of the AdminSite class in use,
passing the HTTP request as an argument and looking for a boolean
result.
2. If the result of has_permission() was False, it calls and returns
the result of the login() method of the AdminSite class in use,
passing the HTTP request as an argument.

This means that, if you want to use something other than Django's
bundled auth system, all you have to do is override those methods on
your AdminSite clas so that they do whatever you want. The argument
signatures and the way in which they're called make absolutely no
assumptions whatsoever about the auth system. As far as AdminSite is
concerned, they're a black box: HTTP requests go in, information comes
out.

If this is what you call heavy-handed, I'd hate to see your idea of modular ;)

> Would it not make more sense to include that chunk of code as another
> backend, or an alternate routine in the default authentication
> backend? This was the first stumbling block; and while I can get
> around this by copying most of the login funtion, it would be sweet
> not to need to.

Again, I don't really see what the complaint is here; all you have to
do at this point is override a pair of methods to implement whatever
authentication you want. If you want to change the form that's used,
you override display_login_form() as well, which means you get the
ability to completely yank django.contrib.auth *and* its assumptions
about credentials out of there by overriding three methods whose
argument signatures are all generic enough to permit this decoupling.

Julien Phalip

unread,
Aug 15, 2008, 4:14:37 AM8/15/08
to Django developers
Hi,

So far I haven't had too much trouble customizing the authentication
system. However, I've stumbled one important problem: it doesn't
appear to be possible to authenticate with usernames longer than 30
characters, which is a serious issue if you want to login with your
email address for example.

This is caused by the login view being bound to use the standard
`AuthenticateForm`. Even if using a custom authentication backend,
this problem is a stopper. Unless you fork your own version of the
login view (yuk!), or do a nasty one-line monkey patch like:

AuthenticationForm.base_fields['username'].max_length = 75

I've posted a ticket about this with a simple patch:
http://code.djangoproject.com/ticket/8274

I really hope it gets in. If I've missed something here, please let me
know the right way to approach this.

Cheers,

Julien

Julien Phalip

unread,
Aug 15, 2008, 4:24:00 AM8/15/08
to Django developers
On a second thought, I may add that this is in fact not so much an
issue in the admin, since you can easily override the admin login
template. So my remark might be bit off-topic considering this thread,
sorry. However, this is a real problem that persists for the rest of
the site.

James Bennett

unread,
Aug 15, 2008, 5:02:34 AM8/15/08
to django-d...@googlegroups.com
On Fri, Aug 15, 2008 at 3:14 AM, Julien Phalip <jph...@gmail.com> wrote:
> So far I haven't had too much trouble customizing the authentication
> system. However, I've stumbled one important problem: it doesn't
> appear to be possible to authenticate with usernames longer than 30
> characters, which is a serious issue if you want to login with your
> email address for example.

So override a couple methods, and you're done.

Again: the fact that you have to subclass and write a little code to
do something other than the default isn't really an onerous
requirement.

Julien Phalip

unread,
Aug 15, 2008, 5:44:56 AM8/15/08
to Django developers
> So override a couple methods, and you're done.

Could you please specify what those methods are? Are we speaking in
the admin or in the front-end?

> Again: the fact that you have to subclass and write a little code to
> do something other than the default isn't really an onerous
> requirement.

I agree in principle, but in this case it seems like you have to
rewrite the whole of `auth.views.login()`. Please correct me if I'm
wrong.
Again, here I'm speaking about the front-end site. It's much easier in
the admin, since you can tweak the `admin/login.html` template. In
fact, now that I look closer, that template doesn't even enforce a
maxlength to the username and password inputs at all. Is that a bug?

Hmm, I've just done some more testing and found another issue... I've
got a custom auth backend allowing to login with your email. If login
fails in the admin (say, you mistype the email address), you get a
'Usernames cannot contain the '@' character.' message. Anyway, I'll
investigate more and possibly create a ticket about that.

James Bennett

unread,
Aug 15, 2008, 6:23:26 AM8/15/08
to django-d...@googlegroups.com
On Fri, Aug 15, 2008 at 4:44 AM, Julien Phalip <jph...@gmail.com> wrote:
> Could you please specify what those methods are? Are we speaking in
> the admin or in the front-end?

Please read my previous emails in this thread.

> I agree in principle, but in this case it seems like you have to
> rewrite the whole of `auth.views.login()`. Please correct me if I'm
> wrong.

Well, if what you want is to have something different from the way the
default login view works, yes.

> Again, here I'm speaking about the front-end site. It's much easier in
> the admin, since you can tweak the `admin/login.html` template. In
> fact, now that I look closer, that template doesn't even enforce a
> maxlength to the username and password inputs at all. Is that a bug?

No, because attributes of HTML elements are not and should not be
confused with validation routines.

> Hmm, I've just done some more testing and found another issue... I've
> got a custom auth backend allowing to login with your email. If login
> fails in the admin (say, you mistype the email address), you get a
> 'Usernames cannot contain the '@' character.' message. Anyway, I'll
> investigate more and possibly create a ticket about that.

You're not the first person to "discover" this. The reason for that
code is the fact that -- in the default setup -- a username is not and
cannot be an email address, and the form is designed to catch
erroneous use of email address in place of username (a common error).
Again, if you want custom behavior it is extremely easy to override.

Luke Plant

unread,
Aug 15, 2008, 6:32:46 AM8/15/08
to django-d...@googlegroups.com
On Friday 15 August 2008 10:44:56 Julien Phalip wrote:

> I agree in principle, but in this case it seems like you have to
> rewrite the whole of `auth.views.login()`. Please correct me if I'm
> wrong.

James: When your users are keeling over at the thought of writing < 30
lines of code, it's time to conclude that you are a victim of your
own success.

Luke

(apologies Julien, nothing personal :-)

--
"We may not return the affection of those who like us, but we always
respect their good judgement." -- Libbie Fudim

Luke Plant || http://lukeplant.me.uk/

Julien Phalip

unread,
Aug 15, 2008, 9:51:50 AM8/15/08
to Django developers
On Aug 15, 8:32 pm, Luke Plant <L.Plant...@cantab.net> wrote:
> > I agree in principle, but in this case it seems like you have to
> > rewrite the whole of `auth.views.login()`. Please correct me if I'm
> > wrong.
>
> James: When your users are keeling over at the thought of writing < 30
> lines of code, it's time to conclude that you are a victim of your
> own success.  

Hehe, you're right :) Django promotes DRY so well that as soon as you
need to duplicate a line of code, it feels nasty. But I guess it's a
sane reaction...

I guess it's a matter of balance. I'm happy with 99% of what the
default login view does. It's just a shame that I need to copy/paste
the whole code -- which I'll then have to maintain myself -- just
because I want to customize 1% of it. Yes, I'm keeling over at the
thought :)

In those instances, I prefer one-liner monkey patches. Just as ugly,
but much easier to maintain or to drop if one day I can find a better/
cleaner way.

Let us dream of a day where monkey-patching and duplicate code will be
history ;)

Jeff

unread,
Aug 26, 2008, 5:38:52 AM8/26/08
to Django developers
I'm quite new to django, so I'm not sure where to put that patch. I
tried it in settings.py and urls.py but it didn't affect anything.
Where's the correct place to put it?

Thanks
Reply all
Reply to author
Forward
0 new messages