Unfortunately this would break in subtle in confusing ways in inheritance
scenarios.
Say you've defined the following Manager subclass prior to Django 1.6 and
we're only raising a warning on Manager.get_query_set calls.:
class ActiveManager(models.Manager):
def get_query_set(self, *args, **kwargs):
return super(ActiveManager, self).get_query_set(*args,
**kwargs).filter(is_active=True)
You would get a Manager.get_query_set() is deprecated, use
`get_queryset()`instead.warning upon calling
ActiveManager.get_query_set, so far so good.
>From here you would go ahead and rename your super call and/or rename
ActiveManager.get_query_set to get_queryset:
class ActiveManager(models.Manager):
def get_query_set(self, *args, **kwargs):
return super(ActiveManager, self).get_queryset(*args,
**kwargs).filter(is_active=True)
Would be broken since internal code calls get_queryset. And renaming both:
class ActiveManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super(ActiveManager, self).get_queryset(*args,
**kwargs).filter(is_active=True)
Would not be backward compatible with code not yet migrated (third-party
apps for example) that still calls get_query_set. The correct way of
handling this *by hand* would be to define both methods when you override a
renamed one:
class ActiveManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super(ActiveManager, self).get_queryset(*args,
**kwargs).filter(is_active=True)
def get_query_set(self, *args, **kwargs):
return ActiveManager.get_queryset(self, *args, **kwargs)
Again we could raise a more verbose warning telling the user to do exactly
that but that would break if you're subclassing a Manager subclass from a
third party app that didn't migrate yet:
class ThirdPartyManager(models.Manager):
"""You cannot do the rename here since it's from a third party
(site-packages,...) module"""
def get_query_set(self, *args, **kwargs):
qs = super(ThirdPartyManager, self).get_query_set(*args, **kwargs)
# To something with qs...
return qs
And you would be left to yourself figuring out you have to do the following:
class MyManager(ThirdPartyManager):
def get_queryset(self, *args, **kwargs):
# Call ThirdPartyManager.get_query_set because it didn't migrate yet.
qs = super(MyManager, self).get_query_set(*args, **kwargs)
# To something with qs...
return qs
def get_query_set(self, *args, **kwargs):
# Compatiblity shim
return MyManager.get_queryset(self, *args, **kwargs)
Maintaining backward compatiblity with third party apps is the main reaosn
a class decorator wouldn't work here. @python_2_unicode_compatible works
well because __str__ and __unicode__ are not often involved in supertrickery
while you almost always override
get_queryset when you subclass Manager.
The use of the metaclass make sure you're not shooting yourself in the foot
all and all the metaclass subclassing trouble should only concern a
minority of users who most probably know what they're doing.
Simon
Le mercredi 6 mars 2013 10:30:35 UTC-5, Jacob Kaplan-Moss a écrit :
>
> Hm.
>
> I'm +1 on cleaning up the names.... but do we *really* have to use a
> metaclass for this? Seems to me this is "gratuitous use of a
> metaclass" territory, especially given the shenanigans you have to go
> through with subclassing the metaclass.
>
> I'd be a lot happier with this patch if it just did this in a simple
> manner: have the old methods raise the warnings and call the new ones.
> Unless there's a actual need for the metaclassing beyond saving a few
> lines of code can you drop it, please?
>
> Jacob
>
> On Tue, Mar 5, 2013 at 2:20 AM, charettes <[email protected]<javascript:>>
> wrote:
> > Since the consensus as shifted toward not making a break-everything 2.0
> I'm
> > planning to normalize the queryset methods name early in the 1.6
> development
> > cycle to spot any breakages.
> >
> > The affected classes are the following:
> >
> > django.contrib.admin.options.BaseModelAdmin (queryset -> get_queryset).
> > django.contrib.admin.views.main.ChangeList (get_query_set ->
> get_queryset).
> > django.contrib.comments.templatetags.comments.BaseCommentNode
> (get_query_set
> > -> get_queryset).
> > django.contrib.contenttypes.generic.GenericForeignKey
> > (get_prefetch_query_set -> get_prefetch_queryset).
> > django.db.models.fields.related.SingleRelatedObjectDescriptor
> (get_query_set
> > -> get_queryset, get_prefetch_query_set -> get_prefetch_queryset).
> > django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor
> > (get_query_set -> get_queryset, get_prefetch_query_set ->
> > get_prefetch_queryset).
> > django.db.models.manager.Manager (get_query_set -> get_queryset,
> > get_prefetch_query_set -> get_prefetch_queryset).
> >
> > And subclasses (ModelAdmin, ...) where bold items are part of the
> documented
> > API and "->" stands for rename.
> >
> > The patch does the following:
> >
> > Define the new method if missing and complain about it.
> > Define the old method if missing to assure backwards compatibility.
> > Complain whenever an old method is called.
> >
> > As discussed in the ticket the whole MRO is handled by the patch so
> mixins
> > should also work correctly. The only issue you might encounter is if you
> > defined a custom metaclass for one of the classed affected by the
> rename.
> > The simple fix is to make sure your metaclass subclasses the class of
> the
> > class you're planning to attach it to:
> >
> > class MyManagerBase(Manager.__class__):
> > pass
> >
> > class MyManager(Manager):
> > __metaclass__ = MyManagerBase
> >
> >
> > If your code base or library must support both Django > 1.6 and <= 1.6
> you
> > should define both methods until you drop support for < 1.6:
> >
> > class MyManager(Manager):
> > def get_queryset(self, *args, **kwargs):
> > # Do something funky
> > # ...
> >
> > def get_query_set(self, *args, **kwargs):
> > # TODO: Remove when support for Django < 1.6 is dropped.
> > return MyManager.get_queryset(self, *args, **kwargs)
> >
> >
> > The pull request can be reviewed here .
> >
> > I'm planning to commit the proposed fix this week if no major issues are
> > raised here or on the ticket.
> >
> > --
> > 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 [email protected] <javascript:>.
> > To post to this group, send email to
> > [email protected]<javascript:>.
>
> > Visit this group at
> http://groups.google.com/group/django-developers?hl=en.
> > For more options, visit https://groups.google.com/groups/opt_out.
> >
> >
>
--
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 [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/django-developers?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.