jazzband / django-taggit

Simple tagging for django
https://django-taggit.readthedocs.io
BSD 3-Clause "New" or "Revised" License
3.34k stars 622 forks source link

TaggableManager.most_common() generates an error with extra_filters in Django v4.1a1 #806

Closed philgyford closed 2 years ago

philgyford commented 2 years ago

After trying an existing project that uses django-taggit v2.1.0 with the main branch of Django, and getting an error (#805), I've create a minimal example to demonstrate it.

I've made temp-django-taggit-test to make it easier to try this out, with tests. The error occurs when using Django v4.1a1 or the latest main code, but does not occur when using Django v4.0. I'm not sure if this is a problem with Django or django-taggit.

With this in models.py:

from django.db import models
from taggit.managers import TaggableManager
from taggit.models import GenericTaggedItemBase, TagBase
from taggit.managers import _TaggableManager

class ItemTag(TagBase):
    pass

class TaggedItem(GenericTaggedItemBase):
    tag = models.ForeignKey(
        ItemTag, on_delete=models.CASCADE, related_name="tagged_items",
    )

class Item(models.Model):
    is_private = models.BooleanField(default=False)

    tags = TaggableManager(through=TaggedItem)

And then in manage.py shell I do this:

from myproject.myapp.models import Item
extra_filters = {"item__is_private": False}
Item.tags.most_common(extra_filters=extra_filters)

I get an error (traceback shown below).

If I leave the extra_filters off:

from myproject.myapp.models import Item
Item.tags.most_common()

then there is no error.

The traceback:

Traceback (most recent call last):
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/taggit/managers.py", line 77, 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 "<console>", line 1, in <module>
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/taggit/managers.py", line 357, in most_common
    self.get_queryset(extra_filters)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/taggit/managers.py", line 80, in get_queryset
    return self.through.tags_for(self.model, self.instance, **kwargs).order_by(
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/taggit/models.py", line 161, in tags_for
    return cls.tag_model().objects.filter(**kwargs).distinct()
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/query.py", line 1395, in filter
    return self._filter_or_exclude(False, args, kwargs)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/query.py", line 1413, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/query.py", line 1420, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1532, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1562, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1470, in build_filter
    col = self._get_col(targets[0], join_info.final_field, alias)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/sql/query.py", line 387, in _get_col
    return target.get_col(alias, field)
AttributeError: 'ManyToManyRel' object has no attribute 'get_col'
philgyford commented 2 years ago

I don't know if this is a separate problem, but I've found another thing that fails in Django 4.1a1 but works with 4.0. I've added a test to the repo mentioned above, but in summary, using the above models, doing this:

from myproject.myapp.models import Item
items = Item.objects.filter(tags__slug__in=["red"])

results in this error with 4.1a1, but no error with 4.0:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/query.py", line 1395, in filter
    return self._filter_or_exclude(False, args, kwargs)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/query.py", line 1413, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/query.py", line 1420, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1532, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1562, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/Users/phil/.local/share/virtualenvs/temp-django-taggit-test-bvQO61Tk/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1466, in build_filter
    raise FieldError(
django.core.exceptions.FieldError: Related Field got invalid lookup: slug

I'm baffled. It's entirely possible I'm doing something stupid and wrong in both these cases, and it's only being revealed by some change in the latest Django. If so, I'd appreciate someone correcting my assumptions about how things work.

philgyford commented 2 years ago

This now appears to be fixed - something in django-taggit v3.0.0 obviously did the trick. Thank you!