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

Fixed prefetch_related when using UUIDTaggedItem #663

Merged
merged 3 commits into from Feb 26, 2020
Merged
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
25 changes: 22 additions & 3 deletions taggit/managers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid
from operator import attrgetter

from django import VERSION
Expand All @@ -17,7 +18,11 @@
from django.utils.translation import gettext_lazy as _

from taggit.forms import TagField
from taggit.models import CommonGenericTaggedItemBase, TaggedItem
from taggit.models import (
CommonGenericTaggedItemBase,
GenericUUIDTaggedItemBase,
TaggedItem,
)
from taggit.utils import require_instance_manager


Expand Down Expand Up @@ -102,18 +107,32 @@ def get_prefetch_queryset(self, instances, queryset=None):
}
)
)

if issubclass(self.through, GenericUUIDTaggedItemBase):

def uuid_rel_obj_attr(v):
value = attrgetter("_prefetch_related_val")(v)
if value is not None and not isinstance(value, uuid.UUID):
input_form = "int" if isinstance(value, int) else "hex"
value = uuid.UUID(**{input_form: value})
return value

rel_obj_attr = uuid_rel_obj_attr
else:
rel_obj_attr = attrgetter("_prefetch_related_val")

if VERSION < (2, 0):
return (
qs,
attrgetter("_prefetch_related_val"),
rel_obj_attr,
lambda obj: obj._get_pk_val(),
False,
self.prefetch_cache_name,
)
else:
return (
qs,
attrgetter("_prefetch_related_val"),
rel_obj_attr,
lambda obj: obj._get_pk_val(),
False,
self.prefetch_cache_name,
Expand Down
109 changes: 109 additions & 0 deletions tests/migrations/0002_auto_20200214_1129.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Generated by Django 3.0.3 on 2020-02-14 11:29

import uuid

import django.utils.timezone
from django.db import migrations, models

import taggit.managers


class Migration(migrations.Migration):

dependencies = [
("taggit", "0003_taggeditem_add_unique_index"),
("contenttypes", "0002_remove_content_type_name"),
("tests", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="UUIDPet",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.CharField(max_length=50)),
("created_at", models.DateTimeField(auto_now_add=True)),
],
options={"ordering": ["created_at"]},
),
migrations.AlterModelOptions(
name="uuidfood", options={"ordering": ["created_at"]},
),
migrations.AddField(
model_name="blanktagmodel",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="taggit.TaggedItem",
to="taggit.Tag",
verbose_name="Tags",
),
),
migrations.AddField(
model_name="uuidfood",
name="created_at",
field=models.DateTimeField(
auto_now_add=True, default=django.utils.timezone.now
),
preserve_default=False,
),
migrations.AlterField(
model_name="taggedtrackedfood",
name="tag",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="taggedtrackedfood_items",
to="tests.TrackedTag",
),
),
migrations.AlterField(
model_name="taggedtrackedpet",
name="tag",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="taggedtrackedpet_items",
to="tests.TrackedTag",
),
),
migrations.AlterUniqueTogether(
name="uuidtaggeditem",
unique_together={("content_type", "object_id", "tag")},
),
migrations.CreateModel(
name="UUIDHousePet",
fields=[
(
"uuidpet_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="tests.UUIDPet",
),
),
("trained", models.BooleanField(default=False)),
],
bases=("tests.uuidpet",),
),
migrations.AddField(
model_name="uuidpet",
name="tags",
field=taggit.managers.TaggableManager(
help_text="A comma-separated list of tags.",
through="tests.UUIDTaggedItem",
to="tests.UUIDTag",
verbose_name="Tags",
),
),
]
28 changes: 28 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,34 @@ class UUIDFood(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager(through="UUIDTaggedItem")

created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.name

class Meta:
# With a UUIDField pk, objects are not always ordered by creation time. So explicitly set ordering.
ordering = ["created_at"]


class UUIDPet(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=50)

tags = TaggableManager(through="UUIDTaggedItem")
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.name

class Meta:
# With a UUIDField pk, objects are not always ordered by creation time. So explicitly set ordering.
ordering = ["created_at"]


class UUIDHousePet(UUIDPet):
trained = models.BooleanField(default=False)


class UUIDTag(TagBase):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Expand All @@ -345,6 +370,9 @@ class UUIDTaggedItem(GenericUUIDTaggedItemBase):
UUIDTag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE
)

class Meta:
unique_together = [["content_type", "object_id", "tag"]]


# Exists to verify system check failure.
# tests.Name.tags: (fields.E303) Reverse query name for 'Name.tags' clashes with field name 'Tag.name'.
Expand Down
20 changes: 18 additions & 2 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
TaggedTrackedFood,
TrackedTag,
UUIDFood,
UUIDHousePet,
UUIDPet,
UUIDTag,
UUIDTaggedItem,
)

from taggit.managers import TaggableManager, _TaggableManager
Expand Down Expand Up @@ -677,13 +680,13 @@ def test_names_method(self):
apple = self.food_model.objects.create(name="apple")
apple.tags.add("green")
apple.tags.add("red")
self.assertEqual(list(apple.tags.names()), ["green", "red"])
self.assertEqual(sorted(list(apple.tags.names())), ["green", "red"])

def test_slugs_method(self):
apple = self.food_model.objects.create(name="apple")
apple.tags.add("green and juicy")
apple.tags.add("red")
self.assertEqual(list(apple.tags.slugs()), ["green-and-juicy", "red"])
self.assertEqual(sorted(list(apple.tags.slugs())), ["green-and-juicy", "red"])

def test_serializes(self):
apple = self.food_model.objects.create(name="apple")
Expand Down Expand Up @@ -794,6 +797,19 @@ def test_require_pk(self):
pass


class TaggableManagerUUIDTestCase(TaggableManagerTestCase):
food_model = UUIDFood
pet_model = UUIDPet
housepet_model = UUIDHousePet
taggeditem_model = UUIDTaggedItem
tag_model = UUIDTag

def test_require_pk(self):
# With a UUIDField pk, pk is never None. So taggit has no way to tell
# if the instance is saved or not.
pass


class TaggableManagerOfficialTestCase(TaggableManagerTestCase):
food_model = OfficialFood
pet_model = OfficialPet
Expand Down