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.core.cache import caches
7 from django.contrib.auth.models import User
8 from django.db import models
9 from django.db.models import permalink
10 from django.db.models.query import Prefetch
11 from django.dispatch import Signal
12 from django.utils.translation import ugettext_lazy as _
14 from newtagging.models import TagBase
15 from ssify import flush_ssi_includes
18 # Those are hard-coded here so that makemessages sees them.
20 ('author', _('author')),
21 ('epoch', _('epoch')),
23 ('genre', _('genre')),
24 ('theme', _('theme')),
26 ('thing', _('thing')), # things shown on pictures
31 """A tag attachable to books and fragments (and possibly anything).
33 Used to represent searchable metadata (authors, epochs, genres, kinds),
34 fragment themes (motifs) and some book hierarchy related kludges."""
35 name = models.CharField(_('name'), max_length=120, db_index=True)
36 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
37 sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
38 category = models.CharField(
39 _('category'), max_length=50, blank=False, null=False, db_index=True, choices=TAG_CATEGORIES)
40 description = models.TextField(_('description'), blank=True)
42 user = models.ForeignKey(User, blank=True, null=True)
43 gazeta_link = models.CharField(blank=True, max_length=240)
44 culturepl_link = models.CharField(blank=True, max_length=240)
45 wiki_link = models.CharField(blank=True, max_length=240)
47 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
48 changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
50 after_change = Signal(providing_args=['instance', 'languages'])
52 class UrlDeprecationWarning(DeprecationWarning):
53 def __init__(self, tags=None):
54 super(Tag.UrlDeprecationWarning, self).__init__()
66 categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
69 ordering = ('sort_key',)
70 verbose_name = _('tag')
71 verbose_name_plural = _('tags')
72 unique_together = (("slug", "category"),)
73 app_label = 'catalogue'
75 def save(self, *args, **kwargs):
76 flush_cache = flush_all_includes = False
77 if self.pk and self.category != 'set':
78 # Flush the whole views cache.
79 # Seem a little harsh, but changed tag names, descriptions
80 # and links come up at any number of places.
83 # Find in which languages we need to flush related includes.
84 old_self = type(self).objects.get(pk=self.pk)
85 # Category shouldn't normally be changed, but just in case.
86 if self.category != old_self.category:
87 flush_all_includes = True
88 languages_changed = self.languages_changed(old_self)
90 ret = super(Tag, self).save(*args, **kwargs)
93 caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
94 if flush_all_includes:
98 self.after_change.send(sender=type(self), instance=self, languages=languages_changed)
102 def languages_changed(self, old):
103 all_langs = [lc for (lc, _ln) in settings.LANGUAGES]
104 if (old.category, old.slug) != (self.category, self.slug):
107 for lang in all_langs:
108 name_field = 'name_%s' % lang
109 if getattr(old, name_field) != getattr(self, name_field):
113 def flush_includes(self, languages=True):
116 if languages is True:
117 languages = [lc for (lc, _ln) in settings.LANGUAGES]
119 template % (self.pk, lang)
121 '/api/include/tag/%d.%s.json',
122 '/api/include/tag/%d.%s.xml',
124 for lang in languages
127 '/katalog/%s.json' % lang for lang in languages])
129 def __unicode__(self):
133 return "Tag(slug=%r)" % self.slug
135 def get_initial(self):
136 if self.category == 'author':
137 return self.sort_key[0]
144 def get_absolute_url(self):
145 return 'tagged_object_list', [self.url_chunk]
148 def get_absolute_gallery_url(self):
149 return 'tagged_object_list_gallery', [self.url_chunk]
153 def create_url(cls, category, slug):
154 return ('catalogue.views.tagged_object_list', [
155 '/'.join((cls.categories_dict[category], slug))
158 def has_description(self):
159 return len(self.description) > 0
160 has_description.short_description = _('description')
161 has_description.boolean = True
164 def get_tag_list(tag_str):
165 if isinstance(tag_str, basestring):
172 tags_splitted = tag_str.split('/')
173 for name in tags_splitted:
175 tags.append(Tag.objects.get(slug=name, category=category))
177 elif name in Tag.categories_rev:
178 category = Tag.categories_rev[name]
181 tags.append(Tag.objects.get(slug=name))
183 except Tag.MultipleObjectsReturned:
184 ambiguous_slugs.append(name)
187 # something strange left off
188 raise Tag.DoesNotExist()
190 # some tags should be qualified
191 e = Tag.MultipleObjectsReturned()
193 e.ambiguous_slugs = ambiguous_slugs
196 raise Tag.UrlDeprecationWarning(tags=tags)
199 return TagBase.get_tag_list(tag_str)
203 return '/'.join((Tag.categories_dict[self.category], self.slug))
206 def tags_from_info(info):
207 from fnpdjango.utils.text.slughifi import slughifi
208 from sortify import sortify
210 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
211 for field_name, category in categories:
213 tag_names = getattr(info, field_name)
216 tag_names = [getattr(info, category)]
218 # For instance, Pictures do not have 'genre' field.
220 for tag_name in tag_names:
221 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
222 tag_sort_key = tag_name
223 if category == 'author':
224 tag_sort_key = ' '.join((tag_name.last_name,) + tag_name.first_names)
225 tag_name = tag_name.readable()
226 if lang == settings.LANGUAGE_CODE:
227 # Allow creating new tag, if it's in default language.
228 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
230 tag_name = unicode(tag_name)
232 setattr(tag, "name_%s" % lang, tag_name)
233 tag.sort_key = sortify(tag_sort_key.lower())
236 meta_tags.append(tag)
238 # Ignore unknown tags in non-default languages.
240 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
241 except Tag.DoesNotExist:
244 meta_tags.append(tag)
248 # Pickle complains about not having this.
249 TagRelation = Tag.intermediary_table_model
252 def prefetch_relations(objects, category, only_name=True):
253 queryset = TagRelation.objects.filter(tag__category=category).select_related('tag')
255 queryset = queryset.only('tag__name_pl', 'object_id')
256 return objects.prefetch_related(
257 Prefetch('tag_relations', queryset=queryset, to_attr='%s_relations' % category))
260 def prefetched_relations(obj, category):
261 if hasattr(obj, '%s_relations' % category):
262 return getattr(obj, '%s_relations' % category)