Hi Aaron.

On Fri, Sep 20, 2013 at 8:00 AM, Aaron Merriam <aaronmerr...@gmail.com>wrote:

> Russell, my reply there isn't meant to be confrontational and yet
> re-reading it I can see it being easily interpreted as such.
>

No worries - I didn't read your response as confrontational, but I'll make
sure I continue to not do so :-)


> My intention is to ask whether there is something about swappable that I
> am missing.  It seems to work the way I stated above but your statement
> seems to imply that their is some missing piece that needs extra mechanisms
> surrounding model loading.  So, am I missing something?
>

I think you are.

Lets try via example. django.contrib.auth contains 2 User models -- User,
and EmailUser. The (massively truncated) definitions for these models are:

class User(Model):
   username = models.CharField()

   USERNAME_FIELD = 'username'

   class Meta:
       swappable = 'AUTH_USER_MODEL'

class EmailUser(Model):
    email = models.CharField()

   USERNAME_FIELD = 'email'

Note that EmailUser *doesn't* have a Meta: swappable definition. There is
nothing on this model that says "I am swappable", or "I am an appropriate
substitute for User".

So - Lets look at two cases.

Case 1: You set AUTH_USER_MODEL = 'auth.EmailModel'. You run syncdb.
EmailUser is synchronised because it's a model in an app. User *isn't*
synchronised because it's explicitly marked as being swappable, and the
swappable attribute doesn't point at itself. Problem solved, right? We've
got the right models syncronized!

Well, no - we still have a problem -- We can't just say
"contrib.auth.forms.AuthenticationForm", because this form needs to change
depending on the model that is in use. We have similar problems with admin,
and potentially with views and URLs.

Case 2: The default case: I just want the default User model. I install
django.contrib.auth in INSTALLED_APPS. I run syncdb. User is synchronised,
as we expect.

Question: How does Django identify that EmailUser *shouldn't* be
synchronised to the database? EmailUser isn't mentioned in any setting
(because AUTH_USER_MODEL == 'auth.User'). There's nothing on EmailUser that
flags it as being a "User" model. It's in a models file with other models
(Group, Permission) which *will* be synchronized. Absent of any additional
information, EmailUser will be synchronised, because it looks like a normal
model.

Answer: We have 2 options. We need to modify contrib/auth/models.py to read:

class User(Model):
   …

if settings.AUTH_USER_MODEL == 'auth.EmailUser':
    class EmailUser(Model):
        ….

and make the same changes in forms, admin, views, URLs etc.

Alternatively, we need to add something to the definition of EmailUser that
says "I'm a User Model. Don't sync me unless I'm active":

class EmailUser(Model):
    email = models.CharField()

    class Meta:
        swappable_using = 'AUTH_USER_MODEL'

And then we *still* need to do the "if settings.AUTH_USER_MODEL ==
auth.EmailUser" dance with views, forms, admin, etc.

However, this also means:

 1) We need to a bunch of internal plumbing in the app loader, Meta classes
and so on to determine whether or not a model should be synchronised, and
"hide" it in some way if it shouldn't be there. If you think this logic is
simple, I've got a bridge to sell you :-)

 2) We have to introduce a backwards incompatible change to pluggable
models. At present you don't have to pre-declare "my model is a User
model". All existing swappable User models in the wild need to be updated
to be "1.7 compliant".

 3) We introduce an implicit requirement for predeclaration. I need to know
ahead of time that my model is going to be a User model. In the case of
User, this probably isn't a huge impediment -- after all, I'm gonna know if
I'm a User model. But looking longer term: the infrastructure for swappable
Users isn't User specific. By design, swappable is a generic mechanism. You
*could* use it as a limited substitute for generic foreign keys (e.g., a
comments model pointing at a specific content type). But now you have to
pre-declare that my model "can be swapped for X" -- which means that as a
model designer, you need to know all the models that you *might* be swapped
out as. Consider the comments example -- I want people to be able to
comment on Users.  But I can't, because I haven't pre-declared User to be
"swappable as" a content object for comments. Predeclaration imposes a
restriction on one side of the swappable agreement that isn't always under
your control.

The alternative -- we put EmailUser in a separate app. That app contains
it's own forms, admin, views and URLs. No extra Meta attributes are
required. No special "if" logic is required internal to models, views,
forms, admin, etc. It's completely backwards compatible. It doesn't impose
any pre-declaration restrictions. And it requires no modifications to the
app loading internals. The only overhead - you have to declare an extra app
in INSTALLED_APPS -- which is hardly surprising to me, given that it's
being *distributed* as an app.

Does that clarify the problem for you?

Yours,
Russ Magee %-)

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to