On Fri, Jun 8, 2012 at 8:53 AM, Anssi Kääriäinen
<anssi.kaariai...@thl.fi> wrote:
> On 8 kesä, 02:43, Russell Keith-Magee <russ...@keith-magee.com> wrote:
>> >  - For documentation: It should be suggested that the example MyUser
>> > should define class Meta: swappable = 'AUTH_USER_MODEL'. Otherwise it
>> > will be synced to the database even if it is not the chosen user model
>> > for the project, which is likely not wanted. Maybe AbstractBaseUser
>> > should define swappable = 'AUTH_USER_MODEL' so that anybody who
>> > inherits from that gets the setting automatically?
>>
>> Ah - I think I see the problem now. The swappable Meta option isn't
>> visible to to end user -- ever -- unless they go spelunking in the
>> source code for auth.models.
>>
>> If I define MyUser, I *dont'* need to define it as swappable. I just
>> need to define AUTH_USER_MODEL in my settings file to point at my
>> replacement model definition. The only place where swappable is
>> defined is on the base model that is being swapped out -- in this
>> case, auth.User.
>>
>> I'm expecting that there will be *no* documentation of the swappable
>> option (unless, at some point in the future, we decide to make
>> swappable models an official feature). It exists purely to make the
>> internals clean, and avoid making auth.User a special case in code.
>
> The real problem comes when you have multiple swappable models defined
> for the same swappable "slot". This could happen in a CMS for example
> - they might define a couple of classes to use, you can choose which
> one to use by using a setting. Or, maybe you have some library
> installed which defines some reusable User models. Problem is, every
> one of these models will be installed unless they provided the
> swappable = 'AUTH_USER_MODEL' meta option. If you don't provide that
> option, nothing ties the models to the same slot.

That's certainly an interesting use case. However, I can think of at
least 2 ways it could be mitigated.

One way would be to treat this as part of the contract of a pluggble
app. We've always said that an app should do one thing, and one thing
only; providing a *single* swappable model candidate, and any
associated support (e.g., forms, admin declarations) would seem to fit
that definition fairly well. In this context, a "library" of reusable
User models is really just a collection of apps, one of which will be
installed in your project. The CMS with a range of User model options
would provide them as a series of apps, one (and only one) of which
needs to be installed.

Alternatively, I suppose we could introduce a separate Meta option
(lets call it "swapper" for the moment -- I don't particularly like
the name, but it will do for discussion purposes) which has the
opposite behaviour of swappable - i.e., a model with swappable=XXX is
only synchronised if settings.XXX is undefined, or XXX points to the
default model; a model with swapper=True is only synchronised if XXX
points to it. Otherwise, it's treated as an abstract model.

Personally, my preference would be the former approach.

>> >  - Assume a 3rd party developer was going to use the swappable Meta
>> > attribute. The developer provides a couple of different swappable
>> > implementations to use, each defining swappable = 'MY_SETTING'.
>> > However, by default settings.MY_SETTING is None, and thus each of
>> > these models will be synced into the DB. How does the 3rd party
>> > developer designate the default implementation? For auth.user this is
>> > easy, as global_settings can contain the default setting. A 3rd party
>> > developer doesn't have this option. (This one isn't important for the
>> > auth.User case, only for making this feature someday publicly
>> > available).
>>
>> If MY_SETTING isn't defined, all the code I've written should be
>> assuming that the model isn't swapped out (i.e., it's effectively
>> getattr(settings, 'MY_SETTING', model_label_of_default_model) ). As
>> proof of concept, it certainly wouldn't hurt to drop out the
>> AUTH_USER_MODEL setting from globals.
>
> The model_label_for_default_model can't be defined anywhere in the
> current implementation. The Model._meta.swapped uses:
>    return self.swappable and getattr(settings, self.swappable, None)
> not in (None, model_label)

Sure - but model_label_of_default_model doesn't *need* to be defined
anywhere - it's the model that has swappable=XXX defined on it.

>> >> > About the ORM capabilities of the interface: It seems there can not be
>> >> > any assumptions about the swapped in model here: for example
>> >> > SomeModel.filter(user__is_staff=True) or
>> >> > SomeModel.order_by('user__lastname', 'user__firstname') works
>> >> > currently, but there are no such lookups available for the interface.
>> >> > Maybe there should be some way to order by "full_name" and
>> >> > "short_name" at least. But, this gets us quickly to virtual fields or
>> >> > virtual lookups territory...
>>
>> >> Imo it's fine if we require some fields on a Usermodel to be compatible
>> >> with admin and friends. (eg a first/last_name field which is nullable [or
>> >> not required in the form validation sense] shouldn't hurt anyone, the 
>> >> admin
>> >> could check if the field is filled and if not display something else like
>> >> the username).
>>
>> > Admin is actually somewhat easy to make work re the ordering, because
>> > in there you can use get_full_name.admin_order_field =
>> > 'somemodelfield'. This doesn't work outside Admin. This isn't a major
>> > issue, it prevents things like ordering comments on the commenter's
>> > "get_short_name" output. Even then, this is only an issue for reusable
>> > code - in your project you know the concrete user model you have in
>> > use, and can thus use the specific lookups your model has.
>>
>> I can see two approaches here.
>>
>> The first is the full audit of admin that I've mentioned previously.
>> That will reveal everything that admin is depending on, and will allow
>> us to define a full "admin user interface" that any swappable user
>> must define.
>>
>> The second is to abstract away the ordering field in the same was as
>> we've abstracted get_short_name -- i.e., don't specify what the field
>> needs to be called, just require that the user provide an option that
>> defines how it can be retrieved. This is similar to what we've done
>> with get_short_name(), but at a model level.
>
> I am not so much afraid of how the Admin works, but more generally
> about the lookups available using the ORM. I would want to do:
>    Document.objects.all().order_by('creator__full_name')
> But I can't do that.
>
> Similar problem is there if you want to do:
>    Document.objects.filter(creator__full_name__ilike=q)
> In my own projects I know I can do something like:
>    Q(creator__last_name__ilike=q)|Q(creator__first_name__ilike=q)
> because I control the User model. But on the User interface level
> there isn't anything like this available.

Again - for me this is one of those things where it's all about the interface.

In this example, your end problem isn't "I need to issue a query with
Q(creator__last_name__ilike=q)|Q(creator__first_name__ilike=q)" --
it's "I need to search for document creator by name", and  *that* is
what your interface should be defining. If your interface spec said
you had to provide a "find_document_by_author()" method, and one
particular implementation happens to use that query, then you've just
solved the problem. The specific document attributes *should* be
irrelevant -- and if they aren't, you haven't defined your document
interface well enough.

It's impossible to claim that every model will be usable in every
circumstance. At some point, you have to lock down an interface
specification, which will define what it is possible to do in a
'generic' sense with your swappable model. This isn't some new concept
we're inventing here - interfaces have been a major part of more than
one language for decades. Luckily, with Python, we get the benefits of
interfaces without needing to be strict about it -- we can rely on
duck typing, rather than rigorous and inflexible compile-time checks.

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.

Reply via email to