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.dispatch import Signal
11 from django.utils.translation import ugettext_lazy as _
12 from newtagging.models import TagBase
13 from ssify import flush_ssi_includes
16 # Those are hard-coded here so that makemessages sees them.
18 ('author', _('author')),
19 ('epoch', _('epoch')),
21 ('genre', _('genre')),
22 ('theme', _('theme')),
24 ('thing', _('thing')), # things shown on pictures
29 """A tag attachable to books and fragments (and possibly anything).
31 Used to represent searchable metadata (authors, epochs, genres, kinds),
32 fragment themes (motifs) and some book hierarchy related kludges."""
33 name = models.CharField(_('name'), max_length=120, db_index=True)
34 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
35 sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
36 category = models.CharField(_('category'), max_length=50, blank=False, null=False,
37 db_index=True, choices=TAG_CATEGORIES)
38 description = models.TextField(_('description'), blank=True)
40 user = models.ForeignKey(User, blank=True, null=True)
41 gazeta_link = models.CharField(blank=True, max_length=240)
42 culturepl_link = models.CharField(blank=True, max_length=240)
43 wiki_link = models.CharField(blank=True, max_length=240)
45 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
46 changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
48 after_change = Signal(providing_args=['instance', 'languages'])
50 class UrlDeprecationWarning(DeprecationWarning):
62 categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
65 ordering = ('sort_key',)
66 verbose_name = _('tag')
67 verbose_name_plural = _('tags')
68 unique_together = (("slug", "category"),)
69 app_label = 'catalogue'
71 def save(self, *args, **kwargs):
72 flush_cache = flush_all_includes = False
73 if self.pk and self.category != 'set':
74 # Flush the whole views cache.
75 # Seem a little harsh, but changed tag names, descriptions
76 # and links come up at any number of places.
79 # Find in which languages we need to flush related includes.
80 old_self = type(self).objects.get(pk=self.pk)
81 # Category shouldn't normally be changed, but just in case.
82 if self.category != old_self.category:
83 flush_all_includes = True
84 languages_changed = self.languages_changed(old_self)
86 ret = super(Tag, self).save(*args, **kwargs)
89 caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
90 if flush_all_includes:
94 self.after_change.send(sender=type(self), instance=self, languages=languages_changed)
98 def languages_changed(self, old):
99 all_langs = [lc for (lc, _ln) in settings.LANGUAGES]
100 if (old.category, old.slug) != (self.category, self.slug):
103 for lang in all_langs:
104 name_field = 'name_%s' % lang
105 if getattr(old, name_field) != getattr(self, name_field):
109 def flush_includes(self, languages=True):
112 if languages is True:
113 languages = [lc for (lc, _ln) in settings.LANGUAGES]
115 template % (self.pk, lang)
117 '/api/include/tag/%d.%s.json',
118 '/api/include/tag/%d.%s.xml',
120 for lang in languages
123 '/katalog/%s.json' % lang for lang in languages])
125 def __unicode__(self):
129 return "Tag(slug=%r)" % self.slug
132 def get_absolute_url(self):
133 return ('catalogue.views.tagged_object_list', [self.url_chunk])
137 def create_url(cls, category, slug):
138 return ('catalogue.views.tagged_object_list', [
139 '/'.join((cls.categories_dict[category], slug))
142 def has_description(self):
143 return len(self.description) > 0
144 has_description.short_description = _('description')
145 has_description.boolean = True
148 def get_tag_list(tags):
149 if isinstance(tags, basestring):
154 tags_splitted = tags.split('/')
155 for name in tags_splitted:
157 real_tags.append(Tag.objects.get(slug=name, category=category))
159 elif name in Tag.categories_rev:
160 category = Tag.categories_rev[name]
163 real_tags.append(Tag.objects.get(slug=name))
165 except Tag.MultipleObjectsReturned, e:
166 ambiguous_slugs.append(name)
169 # something strange left off
170 raise Tag.DoesNotExist()
172 # some tags should be qualified
173 e = Tag.MultipleObjectsReturned()
175 e.ambiguous_slugs = ambiguous_slugs
178 e = Tag.UrlDeprecationWarning()
183 return TagBase.get_tag_list(tags)
187 return '/'.join((Tag.categories_dict[self.category], self.slug))
190 def tags_from_info(info):
191 from fnpdjango.utils.text.slughifi import slughifi
192 from sortify import sortify
194 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
195 for field_name, category in categories:
197 tag_names = getattr(info, field_name)
200 tag_names = [getattr(info, category)]
202 # For instance, Pictures do not have 'genre' field.
204 for tag_name in tag_names:
205 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
206 tag_sort_key = tag_name
207 if category == 'author':
208 tag_sort_key = tag_name.last_name
209 tag_name = tag_name.readable()
210 if lang == settings.LANGUAGE_CODE:
211 # Allow creating new tag, if it's in default language.
212 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
214 tag_name = unicode(tag_name)
216 setattr(tag, "name_%s" % lang, tag_name)
217 tag.sort_key = sortify(tag_sort_key.lower())
220 meta_tags.append(tag)
222 # Ignore unknown tags in non-default languages.
224 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
225 except Tag.DoesNotExist:
228 meta_tags.append(tag)
232 # Pickle complains about not having this.
233 TagRelation = Tag.intermediary_table_model