Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Related polymorphic query support inc select_related and prefetch_related #545

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,34 @@ About Queryset Methods
* ``distinct()`` works as expected. It only regards the fields of
the base class, but this should never make a difference.

* ``select_related()`` works just as usual, but it can not (yet) be used
to select relations in inherited models
(like ``ModelA.objects.select_related('ModelC___fieldxy')`` )
* ``select_related()`` works just as usual with the exception that the
query set must be derived from a PolymorphicRelatedQuerySetMixin
or PolymorphicRelatedQuerySet.

This can be achieved by using a custom manager

class NonPolyModel(models.Model):
relation = models.ForeignKey(BasePolyModel, on_delete=models.CASCADE)
objects = models.Manager.from_queryset(PolymorphicRelatedQuerySet)()

or by converting a models queryset using

class NonPolyModel(models.Model):
relation = models.ForeignKey(BasePolyModel, on_delete=models.CASCADE)
objects = models.Manager.from_queryset(QuerySet)()

``convert_to_polymorphic_queryset(NonPolyModel.objects).filter(...)``

To select related fields the model name comes after the field name and set the
field.
``ModelA.objects.filter(....).select_related('field___TargetModel__subfield')``.
or using the polymorphic added related fieldname which is normally the lowercase
version of the model name.
``ModelA.objects.filter(....).select_related('field__targetmodel__subfield')``

This automatically manages the via models between the model specified in the related
field and the target model.
``ModelA.objects.filter(....).select_related('field__targetparentmodel__targetmodel__subfield')``

* ``extra()`` works as expected (it returns polymorphic results) but
currently has one restriction: The resulting objects are required to have
Expand Down
2 changes: 1 addition & 1 deletion polymorphic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __new__(self, model_name, bases, attrs, **kwargs):
# determine the name of the primary key field and store it into the class variable
# polymorphic_primary_key_name (it is needed by query.py)
for f in new_class._meta.fields:
if f.primary_key and type(f) != models.OneToOneField:
if f.primary_key and not isinstance(f, models.OneToOneField):
new_class.polymorphic_primary_key_name = f.name
break

Expand Down
1 change: 1 addition & 0 deletions polymorphic/formsets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""



def add_media(dest, media):
"""
Optimized version of django.forms.Media.__add__() that doesn't create new objects.
Expand Down
18 changes: 13 additions & 5 deletions polymorphic/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,15 @@ def __init__(self, *args, **kwargs):
return
self.__class__.polymorphic_super_sub_accessors_replaced = True

def create_accessor_function_for_model(model, accessor_name):
def create_accessor_function_for_model(model, field):
def accessor_function(self):
objects = getattr(model, "_base_objects", model.objects)
attr = objects.get(pk=self.pk)
return attr
try:
rel_obj = field.get_cached_value(self)
except KeyError:
objects = getattr(model, "_base_objects", model.objects)
rel_obj = objects.get(pk=self.pk)
field.set_cached_value(self, rel_obj)
return rel_obj

return accessor_function

Expand All @@ -209,10 +213,14 @@ def accessor_function(self):
type(orig_accessor),
(ReverseOneToOneDescriptor, ForwardManyToOneDescriptor),
):

field = orig_accessor.related \
if isinstance(orig_accessor, ReverseOneToOneDescriptor) else orig_accessor.field

setattr(
self.__class__,
name,
property(create_accessor_function_for_model(model, name)),
property(create_accessor_function_for_model(model, field)),
)

def _get_inheritance_relation_fields_and_models(self):
Expand Down
Loading