django-modelcluster icon indicating copy to clipboard operation
django-modelcluster copied to clipboard

Unable to `prefetch_related` on `ClusterTaggableManager`

Open RealOrangeOne opened this issue 4 years ago • 1 comments

Model set up:

@register_snippet
class MyTag(TagBase):
    free_tagging = False

    class Meta:
        ordering = ['name']


class MyRelation(ItemBase):
    tag = models.ForeignKey(MyTag, on_delete=models.CASCADE)
    content_object = ParentalKey(to='app.MyPage', on_delete=models.CASCADE)


class MyPage(Page):
    my_field = ClusterTaggableManager(through=MyRelation, verbose_name="My Relation")

Running MyPage.objects.prefetch_related('my_field') should work, but instead yields an error message which looks a lot like a bug rather than simple misuse:

(Paths redacted)

Traceback (most recent call last):
  File "blah/python3.8/site-packages/taggit/managers.py", line 71, in get_queryset
    return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
AttributeError: 'NoneType' object has no attribute '_prefetched_objects_cache'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "blah/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "blah/python3.8/site-packages/django/core/handlers/base.py", line 145, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "blah/python3.8/site-packages/django/core/handlers/base.py", line 143, in _get_response
    response = response.render()
  File "blah/python3.8/site-packages/django/template/response.py", line 106, in render
    self.content = self.rendered_content
  File "blah/python3.8/site-packages/django/template/response.py", line 83, in rendered_content
    content = template.render(context, self._request)
  File "blah/python3.8/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 171, in render
    return self._render(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 163, in _render
    return self.nodelist.render(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 937, in render
    bit = node.render_annotated(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 904, in render_annotated
    return self.render(context)
  File "blah/python3.8/site-packages/django/template/loader_tags.py", line 150, in render
    return compiled_parent._render(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 163, in _render
    return self.nodelist.render(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 937, in render
    bit = node.render_annotated(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 904, in render_annotated
    return self.render(context)
  File "blah/python3.8/site-packages/django/template/loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 937, in render
    bit = node.render_annotated(context)
  File "blah/python3.8/site-packages/django/template/base.py", line 904, in render_annotated
    return self.render(context)
  File "blah/python3.8/site-packages/django/template/defaulttags.py", line 308, in render
    if match:
  File "blah/python3.8/site-packages/django/core/paginator.py", line 150, in __len__
    return len(self.object_list)
  File "blah/python3.8/site-packages/django/db/models/query.py", line 256, in __len__
    self._fetch_all()
  File "blah/python3.8/site-packages/django/db/models/query.py", line 1244, in _fetch_all
    self._prefetch_related_objects()
  File "blah/python3.8/site-packages/django/db/models/query.py", line 771, in _prefetch_related_objects
    prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
  File "blah/python3.8/site-packages/django/db/models/query.py", line 1625, in prefetch_related_objects
    obj_list, additional_lookups = prefetch_one_level(obj_list, prefetcher, lookup, level)
  File "blah/python3.8/site-packages/django/db/models/query.py", line 1738, in prefetch_one_level
    prefetcher.get_prefetch_queryset(instances, lookup.get_current_queryset(level)))
  File "blah/python3.8/site-packages/taggit/managers.py", line 100, in get_prefetch_queryset
    self.get_queryset(query)
  File "blah/python3.8/site-packages/modelcluster/contrib/taggit.py", line 32, in get_queryset
    return super(_ClusterTaggableManager, self).get_queryset(extra_filters)
  File "blah/python3.8/site-packages/taggit/managers.py", line 74, in get_queryset
    return self.through.tags_for(self.model, self.instance, **kwargs)
  File "blah/python3.8/site-packages/taggit/models.py", line 112, in tags_for
    return cls.tag_model().objects.filter(**kwargs).distinct()
  File "blah/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "blah/python3.8/site-packages/django/db/models/query.py", line 892, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "blah/python3.8/site-packages/django/db/models/query.py", line 910, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "blah/python3.8/site-packages/django/db/models/sql/query.py", line 1290, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "blah/python3.8/site-packages/django/db/models/sql/query.py", line 1315, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "blah/python3.8/site-packages/django/db/models/sql/query.py", line 1190, in build_filter
    lookups, parts, reffed_expression = self.solve_lookup_type(arg)
  File "blah/python3.8/site-packages/django/db/models/sql/query.py", line 1049, in solve_lookup_type
    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
  File "blah/python3.8/site-packages/django/db/models/sql/query.py", line 1419, in names_to_path
    raise FieldError("Cannot resolve keyword '%s' into field. "
django.core.exceptions.FieldError: Cannot resolve keyword 'None' into field. Choices are: myrelation, mypage, id, name, slug

Prefetching by my_field__tag has the same result.

Given the stack, this might be an issue with taggit itself, but prefetch claims to be directly supported.

RealOrangeOne avatar Apr 14 '21 11:04 RealOrangeOne

This error might be because your tag ForeignKey doesn't define a related_name. See taggit documentation here.

chosak avatar Dec 22 '23 19:12 chosak