Skip to content

Commit

Permalink
Merge pull request #663 from fumuumuf/fix-490
Browse files Browse the repository at this point in the history
Fixed prefetch_related when using UUIDTaggedItem
  • Loading branch information
Asif Saif Uddin committed Feb 26, 2020
2 parents 20461d2 + e041568 commit 23ac104
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 5 deletions.
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

0 comments on commit 23ac104

Please sign in to comment.