Skip to content
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
4 changes: 2 additions & 2 deletions taggit/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TaggedItemInline(admin.StackedInline):
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
inlines = [TaggedItemInline]
list_display = ["name", "slug"]
ordering = ["name", "slug"]
list_display = ["name", "slug", "language_code"]
ordering = ["name", "slug", "language_code"]
search_fields = ["name"]
prepopulated_fields = {"slug": ["name"]}
10 changes: 8 additions & 2 deletions taggit/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json

from django import forms
from django.utils.translation import gettext as _

from taggit.utils import edit_string_for_tags, parse_tags


Expand All @@ -24,8 +25,13 @@ class TagField(forms.CharField):

def clean(self, value):
value = super().clean(value)
if not value:
return {'language_code': '', 'tags': []}
value_obj = json.loads(value)
tags_str = parse_tags(value_obj['tags'])
value_obj['tags'] = tags_str
try:
return parse_tags(value)
return value_obj
except ValueError:
raise forms.ValidationError(
_("Please provide a comma-separated list of tags.")
Expand Down
64 changes: 24 additions & 40 deletions taggit/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ def _lookup_kwargs(self):
return self.through.lookup_kwargs(self.instance)

@require_instance_manager
def add(self, *tags):
def add(self, lang, tags):
db = router.db_for_write(self.through, instance=self.instance)

tag_objs = self._to_tag_model_instances(tags)
tag_objs = self._to_tag_model_instances(lang, tags)
new_ids = {t.pk for t in tag_objs}

# NOTE: can we hardcode 'tag_id' here or should the column name be got
Expand Down Expand Up @@ -165,7 +165,7 @@ def add(self, *tags):
using=db,
)

def _to_tag_model_instances(self, tags):
def _to_tag_model_instances(self, lang, tags):
"""
Takes an iterable containing either strings, tag objects, or a mixture
of both and returns set of tag objects.
Expand All @@ -189,36 +189,19 @@ def _to_tag_model_instances(self, tags):

case_insensitive = getattr(settings, "TAGGIT_CASE_INSENSITIVE", False)
manager = self.through.tag_model()._default_manager.using(db)
existing = []

if case_insensitive:
# Some databases can do case-insensitive comparison with IN, which
# would be faster, but we can't rely on it or easily detect it.
existing = []
tags_to_create = []

for name in str_tags:
try:
tag = manager.get(name__iexact=name)
existing.append(tag)
except self.through.tag_model().DoesNotExist:
tags_to_create.append(name)
else:
# If str_tags has 0 elements Django actually optimizes that to not
# do a query. Malcolm is very smart.
existing = manager.filter(name__in=str_tags)
tags_to_create = str_tags - {t.name for t in existing}

tag_objs.update(existing)

for new_tag in tags_to_create:
# Some databases can do case-insensitive comparison with IN, which
# would be faster, but we can't rely on it or easily detect it.
for tag in str_tags:
if case_insensitive:
tag, created = manager.get_or_create(
name__iexact=new_tag, defaults={"name": new_tag}
)
_tag = manager.filter(name__iexact=tag, language_code=lang).first()
else:
tag, created = manager.get_or_create(name=new_tag)
_tag = manager.filter(name=tag, language_code=lang).first()
if _tag:
existing.append(_tag)

tag_objs.add(tag)
tag_objs.update(existing)

return tag_objs

Expand All @@ -231,7 +214,7 @@ def slugs(self):
return self.get_queryset().values_list("slug", flat=True)

@require_instance_manager
def set(self, *tags, **kwargs):
def set(self, tags_obj, **kwargs):
"""
Set the object's tags to the given n tags. If the clear kwarg is True
then all existing tags are removed (using `.clear()`) and the new tags
Expand All @@ -240,30 +223,31 @@ def set(self, *tags, **kwargs):
"""
db = router.db_for_write(self.through, instance=self.instance)
clear = kwargs.pop("clear", False)

tags = tags_obj['tags']
lang = tags_obj['language_code']
if clear:
self.clear()
self.add(*tags)
self.add(lang, tags)
else:
# make sure we're working with a collection of a uniform type
objs = self._to_tag_model_instances(tags)
objs = self._to_tag_model_instances(lang, tags)

# get the existing tag strings
old_tag_strs = set(
self.through._default_manager.using(db)
.filter(**self._lookup_kwargs())
.values_list("tag__name", flat=True)
.filter(**self._lookup_kwargs(), tag__language_code=lang)
.values_list("tag__id", flat=True)
)

new_objs = []
for obj in objs:
if obj.name in old_tag_strs:
old_tag_strs.remove(obj.name)
if obj.id in old_tag_strs:
old_tag_strs.remove(obj.id)
else:
new_objs.append(obj)

self.remove(*old_tag_strs)
self.add(*new_objs)
self.add(lang, new_objs)

@require_instance_manager
def remove(self, *tags):
Expand All @@ -275,7 +259,7 @@ def remove(self, *tags):
qs = (
self.through._default_manager.using(db)
.filter(**self._lookup_kwargs())
.filter(tag__name__in=tags)
.filter(tag__id__in=tags)
)

old_ids = set(qs.values_list("tag_id", flat=True))
Expand Down Expand Up @@ -514,7 +498,7 @@ def post_through_setup(self, cls):
)

def save_form_data(self, instance, value):
getattr(instance, self.name).set(*value)
getattr(instance, self.name).set(value)

def formfield(self, form_class=TagField, **kwargs):
defaults = {
Expand Down
30 changes: 30 additions & 0 deletions taggit/migrations/0004_auto_20200204_1036.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.18 on 2020-02-04 09:36
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('taggit', '0003_taggeditem_add_unique_index'),
]

operations = [
migrations.AddField(
model_name='tag',
name='language_code',
field=models.CharField(default='sk', max_length=255, verbose_name='Language code'),
),
migrations.AlterField(
model_name='tag',
name='name',
field=models.CharField(max_length=100, verbose_name='Name'),
),
migrations.AlterField(
model_name='tag',
name='slug',
field=models.SlugField(max_length=100, verbose_name='Slug'),
),
]
19 changes: 19 additions & 0 deletions taggit/migrations/0005_auto_20200204_1049.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.18 on 2020-02-04 09:49
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('taggit', '0004_auto_20200204_1036'),
]

operations = [
migrations.AlterUniqueTogether(
name='tag',
unique_together=set([('slug', 'language_code'), ('name', 'language_code')]),
),
]
29 changes: 29 additions & 0 deletions taggit/migrations/0006_alter_tag_language_code_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('taggit', '0005_auto_20200204_1049'),
]

operations = [
migrations.AlterField(
model_name='tag',
name='language_code',
field=models.CharField(choices=list(map(lambda cou: (cou['code'], cou['code']), settings.PARLER_LANGUAGES[1])), default=settings.LANGUAGE_CODE, max_length=255, verbose_name='Language code'),
),
migrations.AlterField(
model_name='taggeditem',
name='content_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_tagged_items', to='contenttypes.contenttype', verbose_name='Content type'),
),
migrations.AlterField(
model_name='taggeditem',
name='tag',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_items', to='taggit.tag'),
),
]
7 changes: 5 additions & 2 deletions taggit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.db import IntegrityError, models, router, transaction
from django.utils.text import slugify
from django.utils.translation import gettext, gettext_lazy as _
from django.conf import settings

try:
from unidecode import unidecode
Expand All @@ -13,8 +14,9 @@ def unidecode(tag):


class TagBase(models.Model):
name = models.CharField(verbose_name=_("Name"), unique=True, max_length=100)
slug = models.SlugField(verbose_name=_("Slug"), unique=True, max_length=100)
name = models.CharField(verbose_name=_("Name"), max_length=100)
slug = models.SlugField(verbose_name=_("Slug"), max_length=100)
language_code = models.CharField(verbose_name=_('Language code'), max_length=255, default=settings.LANGUAGE_CODE, choices=list(map(lambda cou: (cou['code'], cou['code']), settings.PARLER_LANGUAGES[1])))

def __str__(self):
return self.name
Expand Down Expand Up @@ -76,6 +78,7 @@ class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
app_label = "taggit"
unique_together = (("name", "language_code",), ("slug", "language_code",),)


class ItemBase(models.Model):
Expand Down
10 changes: 10 additions & 0 deletions taggit/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.conf.urls import url

from .views import TagAutocomplete

urlpatterns = [
url(r'^tag-autocomplete/$', TagAutocomplete.as_view(), name='tag-autocomplete', ),
]



17 changes: 17 additions & 0 deletions taggit/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from dal import autocomplete
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.views.generic.list import ListView

Expand Down Expand Up @@ -44,3 +46,18 @@ def get_context_data(self, **kwargs):
context["extra_context"] = {}
context["extra_context"]["tag"] = self.tag
return context


class TagAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated() or (
not self.request.user.is_superuser and not self.request.user.has_perm('taggit.change_tag')):
return Tag.objects.none()

qs = Tag.objects.all()

if self.q:
qs = Tag.objects.filter(
Q(name__icontains=self.q) | Q(slug__icontains=self.q)).all()
return qs