1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 from django.conf import settings
5 from django.contrib.contenttypes.fields import GenericForeignKey
6 from django.contrib.contenttypes.models import ContentType
7 from django.core.cache import caches
8 from django.contrib.auth.models import User
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.db import models
11 from django.db.models.query import Prefetch
12 from django.dispatch import Signal
13 from django.urls import reverse
14 from django.utils.translation import ugettext_lazy as _
16 from newtagging.models import TagManager, TaggedItemManager
19 # Those are hard-coded here so that makemessages sees them.
21 ('author', _('author')),
22 ('epoch', _('epoch')),
24 ('genre', _('genre')),
25 ('theme', _('theme')),
27 ('thing', _('thing')), # things shown on pictures
31 class TagRelation(models.Model):
33 tag = models.ForeignKey('Tag', models.CASCADE, verbose_name=_('tag'), related_name='items')
34 content_type = models.ForeignKey(ContentType, models.CASCADE, verbose_name=_('content type'))
35 object_id = models.PositiveIntegerField(_('object id'), db_index=True)
36 content_object = GenericForeignKey('content_type', 'object_id')
38 objects = TaggedItemManager()
41 db_table = 'catalogue_tag_relation'
42 unique_together = (('tag', 'content_type', 'object_id'),)
46 return '%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
47 except ObjectDoesNotExist:
48 return '<deleted> [%s]' % self.tag
51 class Tag(models.Model):
52 """A tag attachable to books and fragments (and possibly anything).
54 Used to represent searchable metadata (authors, epochs, genres, kinds),
55 fragment themes (motifs) and some book hierarchy related kludges."""
56 name = models.CharField(_('name'), max_length=120, db_index=True)
57 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
58 sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
59 category = models.CharField(
60 _('category'), max_length=50, blank=False, null=False, db_index=True, choices=TAG_CATEGORIES)
61 description = models.TextField(_('description'), blank=True)
63 for_books = models.BooleanField(default=False)
64 for_pictures = models.BooleanField(default=False)
66 user = models.ForeignKey(User, models.CASCADE, blank=True, null=True)
67 gazeta_link = models.CharField(blank=True, max_length=240)
68 culturepl_link = models.CharField(blank=True, max_length=240)
69 wiki_link = models.CharField(blank=True, max_length=240)
71 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
72 changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
74 after_change = Signal(providing_args=['instance'])
76 intermediary_table_model = TagRelation
77 objects = TagManager()
79 class UrlDeprecationWarning(DeprecationWarning):
80 def __init__(self, tags=None):
81 super(Tag.UrlDeprecationWarning, self).__init__()
93 categories_dict = dict((item[::-1] for item in categories_rev.items()))
96 ordering = ('sort_key',)
97 verbose_name = _('tag')
98 verbose_name_plural = _('tags')
99 unique_together = (("slug", "category"),)
100 app_label = 'catalogue'
102 def save(self, *args, **kwargs):
103 existing = self.pk and self.category != 'set'
104 ret = super(Tag, self).save(*args, **kwargs)
106 self.after_change.send(sender=type(self), instance=self)
113 return "Tag(slug=%r)" % self.slug
115 def get_initial(self):
116 if self.category == 'author':
117 return self.sort_key[0]
124 def category_plural(self):
125 return self.category + 's'
127 def get_absolute_url(self):
128 return reverse('tagged_object_list', args=[self.url_chunk])
130 def get_absolute_gallery_url(self):
131 return reverse('tagged_object_list_gallery', args=[self.url_chunk])
133 def has_description(self):
134 return len(self.description) > 0
135 has_description.short_description = _('description')
136 has_description.boolean = True
139 def get_tag_list(tag_str):
146 tags_splitted = tag_str.split('/')
147 for name in tags_splitted:
149 tags.append(Tag.objects.get(slug=name, category=category))
151 elif name in Tag.categories_rev:
152 category = Tag.categories_rev[name]
155 tags.append(Tag.objects.get(slug=name))
157 except Tag.MultipleObjectsReturned:
158 ambiguous_slugs.append(name)
161 # something strange left off
162 raise Tag.DoesNotExist()
164 # some tags should be qualified
165 e = Tag.MultipleObjectsReturned()
167 e.ambiguous_slugs = ambiguous_slugs
170 raise Tag.UrlDeprecationWarning(tags=tags)
175 return '/'.join((Tag.categories_dict[self.category], self.slug))
178 def tags_from_info(info):
179 from slugify import slugify
180 from sortify import sortify
182 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
183 for field_name, category in categories:
185 tag_names = getattr(info, field_name)
186 except (AttributeError, KeyError): # TODO: shouldn't be KeyError here at all.
188 tag_names = [getattr(info, category)]
190 # For instance, Pictures do not have 'genre' field.
192 for tag_name in tag_names:
193 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
194 tag_sort_key = tag_name
195 if category == 'author':
196 tag_sort_key = ' '.join((tag_name.last_name,) + tag_name.first_names)
197 tag_name = tag_name.readable()
198 if lang == settings.LANGUAGE_CODE:
199 # Allow creating new tag, if it's in default language.
200 tag, created = Tag.objects.get_or_create(slug=slugify(tag_name), category=category)
202 tag_name = str(tag_name)
204 setattr(tag, "name_%s" % lang, tag_name)
205 tag.sort_key = sortify(tag_sort_key.lower())
208 meta_tags.append(tag)
210 # Ignore unknown tags in non-default languages.
212 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
213 except Tag.DoesNotExist:
216 meta_tags.append(tag)
220 TagRelation.tag_model = Tag
223 def prefetch_relations(objects, category, only_name=True):
224 queryset = TagRelation.objects.filter(tag__category=category).select_related('tag')
226 queryset = queryset.only('tag__name_pl', 'object_id')
227 return objects.prefetch_related(
228 Prefetch('tag_relations', queryset=queryset, to_attr='%s_relations' % category))
231 def prefetched_relations(obj, category):
232 if hasattr(obj, '%s_relations' % category):
233 return getattr(obj, '%s_relations' % category)