1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 from django.conf import settings
6 from django.contrib.contenttypes.fields import GenericForeignKey
7 from django.contrib.contenttypes.models import ContentType
8 from django.core.cache import caches
9 from django.contrib.auth.models import User
10 from django.core.exceptions import ObjectDoesNotExist
11 from django.db import models
12 from django.db.models import permalink
13 from django.db.models.query import Prefetch
14 from django.dispatch import Signal
15 from django.utils.translation import ugettext_lazy as _
17 from newtagging.models import TagBase
18 from ssify import flush_ssi_includes
21 # Those are hard-coded here so that makemessages sees them.
23 ('author', _('author')),
24 ('epoch', _('epoch')),
26 ('genre', _('genre')),
27 ('theme', _('theme')),
29 ('thing', _('thing')), # things shown on pictures
33 class TagRelation(models.Model):
35 tag = models.ForeignKey('Tag', verbose_name=_('tag'), related_name='items')
36 content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
37 object_id = models.PositiveIntegerField(_('object id'), db_index=True)
38 content_object = GenericForeignKey('content_type', 'object_id')
41 db_table = 'catalogue_tag_relation'
42 unique_together = (('tag', 'content_type', 'object_id'),)
44 def __unicode__(self):
46 return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
47 except ObjectDoesNotExist:
48 return u'<deleted> [%s]' % self.tag
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, 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', 'languages'])
76 intermediary_table_model = TagRelation
78 class UrlDeprecationWarning(DeprecationWarning):
79 def __init__(self, tags=None):
80 super(Tag.UrlDeprecationWarning, self).__init__()
92 categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
95 ordering = ('sort_key',)
96 verbose_name = _('tag')
97 verbose_name_plural = _('tags')
98 unique_together = (("slug", "category"),)
99 app_label = 'catalogue'
101 def save(self, *args, **kwargs):
102 flush_cache = flush_all_includes = False
103 if self.pk and self.category != 'set':
104 # Flush the whole views cache.
105 # Seem a little harsh, but changed tag names, descriptions
106 # and links come up at any number of places.
109 # Find in which languages we need to flush related includes.
110 old_self = type(self).objects.get(pk=self.pk)
111 # Category shouldn't normally be changed, but just in case.
112 if self.category != old_self.category:
113 flush_all_includes = True
114 languages_changed = self.languages_changed(old_self)
116 ret = super(Tag, self).save(*args, **kwargs)
119 caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
120 if flush_all_includes:
123 self.flush_includes()
124 self.after_change.send(sender=type(self), instance=self, languages=languages_changed)
128 def languages_changed(self, old):
129 all_langs = [lc for (lc, _ln) in settings.LANGUAGES]
130 if (old.category, old.slug) != (self.category, self.slug):
133 for lang in all_langs:
134 name_field = 'name_%s' % lang
135 if getattr(old, name_field) != getattr(self, name_field):
139 def flush_includes(self, languages=True):
142 if languages is True:
143 languages = [lc for (lc, _ln) in settings.LANGUAGES]
145 template % (self.pk, lang)
147 '/api/include/tag/%d.%s.json',
148 '/api/include/tag/%d.%s.xml',
150 for lang in languages
153 '/katalog/%s.json' % lang for lang in languages])
155 def __unicode__(self):
159 return "Tag(slug=%r)" % self.slug
161 def get_initial(self):
162 if self.category == 'author':
163 return self.sort_key[0]
170 def category_plural(self):
171 return self.category + 's'
174 def get_absolute_url(self):
175 return 'tagged_object_list', [self.url_chunk]
178 def get_absolute_gallery_url(self):
179 return 'tagged_object_list_gallery', [self.url_chunk]
183 def create_url(cls, category, slug):
184 return ('catalogue.views.tagged_object_list', [
185 '/'.join((cls.categories_dict[category], slug))
188 def has_description(self):
189 return len(self.description) > 0
190 has_description.short_description = _('description')
191 has_description.boolean = True
194 def get_tag_list(tag_str):
195 if isinstance(tag_str, basestring):
202 tags_splitted = tag_str.split('/')
203 for name in tags_splitted:
205 tags.append(Tag.objects.get(slug=name, category=category))
207 elif name in Tag.categories_rev:
208 category = Tag.categories_rev[name]
211 tags.append(Tag.objects.get(slug=name))
213 except Tag.MultipleObjectsReturned:
214 ambiguous_slugs.append(name)
217 # something strange left off
218 raise Tag.DoesNotExist()
220 # some tags should be qualified
221 e = Tag.MultipleObjectsReturned()
223 e.ambiguous_slugs = ambiguous_slugs
226 raise Tag.UrlDeprecationWarning(tags=tags)
229 return TagBase.get_tag_list(tag_str)
233 return '/'.join((Tag.categories_dict[self.category], self.slug))
236 def tags_from_info(info):
237 from slugify import slugify
238 from sortify import sortify
240 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
241 for field_name, category in categories:
243 tag_names = getattr(info, field_name)
246 tag_names = [getattr(info, category)]
248 # For instance, Pictures do not have 'genre' field.
250 for tag_name in tag_names:
251 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
252 tag_sort_key = tag_name
253 if category == 'author':
254 tag_sort_key = ' '.join((tag_name.last_name,) + tag_name.first_names)
255 tag_name = tag_name.readable()
256 if lang == settings.LANGUAGE_CODE:
257 # Allow creating new tag, if it's in default language.
258 tag, created = Tag.objects.get_or_create(slug=slugify(tag_name), category=category)
260 tag_name = unicode(tag_name)
262 setattr(tag, "name_%s" % lang, tag_name)
263 tag.sort_key = sortify(tag_sort_key.lower())
266 meta_tags.append(tag)
268 # Ignore unknown tags in non-default languages.
270 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
271 except Tag.DoesNotExist:
274 meta_tags.append(tag)
278 def prefetch_relations(objects, category, only_name=True):
279 queryset = TagRelation.objects.filter(tag__category=category).select_related('tag')
281 queryset = queryset.only('tag__name_pl', 'object_id')
282 return objects.prefetch_related(
283 Prefetch('tag_relations', queryset=queryset, to_attr='%s_relations' % category))
286 def prefetched_relations(obj, category):
287 if hasattr(obj, '%s_relations' % category):
288 return getattr(obj, '%s_relations' % category)