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 TagManager, TaggedItemManager
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')
40 objects = TaggedItemManager()
43 db_table = 'catalogue_tag_relation'
44 unique_together = (('tag', 'content_type', 'object_id'),)
46 def __unicode__(self):
48 return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
49 except ObjectDoesNotExist:
50 return u'<deleted> [%s]' % self.tag
53 class Tag(models.Model):
54 """A tag attachable to books and fragments (and possibly anything).
56 Used to represent searchable metadata (authors, epochs, genres, kinds),
57 fragment themes (motifs) and some book hierarchy related kludges."""
58 name = models.CharField(_('name'), max_length=120, db_index=True)
59 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
60 sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
61 category = models.CharField(
62 _('category'), max_length=50, blank=False, null=False, db_index=True, choices=TAG_CATEGORIES)
63 description = models.TextField(_('description'), blank=True)
65 for_books = models.BooleanField(default=False)
66 for_pictures = models.BooleanField(default=False)
68 user = models.ForeignKey(User, blank=True, null=True)
69 gazeta_link = models.CharField(blank=True, max_length=240)
70 culturepl_link = models.CharField(blank=True, max_length=240)
71 wiki_link = models.CharField(blank=True, max_length=240)
73 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
74 changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
76 after_change = Signal(providing_args=['instance', 'languages'])
78 intermediary_table_model = TagRelation
79 objects = TagManager()
81 class UrlDeprecationWarning(DeprecationWarning):
82 def __init__(self, tags=None):
83 super(Tag.UrlDeprecationWarning, self).__init__()
95 categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
98 ordering = ('sort_key',)
99 verbose_name = _('tag')
100 verbose_name_plural = _('tags')
101 unique_together = (("slug", "category"),)
102 app_label = 'catalogue'
104 def save(self, *args, **kwargs):
105 flush_cache = flush_all_includes = False
106 if self.pk and self.category != 'set':
107 # Flush the whole views cache.
108 # Seem a little harsh, but changed tag names, descriptions
109 # and links come up at any number of places.
112 # Find in which languages we need to flush related includes.
113 old_self = type(self).objects.get(pk=self.pk)
114 # Category shouldn't normally be changed, but just in case.
115 if self.category != old_self.category:
116 flush_all_includes = True
117 languages_changed = self.languages_changed(old_self)
119 ret = super(Tag, self).save(*args, **kwargs)
122 caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
123 if flush_all_includes:
126 self.flush_includes()
127 self.after_change.send(sender=type(self), instance=self, languages=languages_changed)
131 def languages_changed(self, old):
132 all_langs = [lc for (lc, _ln) in settings.LANGUAGES]
133 if (old.category, old.slug) != (self.category, self.slug):
136 for lang in all_langs:
137 name_field = 'name_%s' % lang
138 if getattr(old, name_field) != getattr(self, name_field):
142 def flush_includes(self, languages=True):
145 if languages is True:
146 languages = [lc for (lc, _ln) in settings.LANGUAGES]
148 template % (self.pk, lang)
150 '/api/include/tag/%d.%s.json',
151 '/api/include/tag/%d.%s.xml',
153 for lang in languages
156 '/katalog/%s.json' % lang for lang in languages])
158 def __unicode__(self):
162 return "Tag(slug=%r)" % self.slug
164 def get_initial(self):
165 if self.category == 'author':
166 return self.sort_key[0]
173 def category_plural(self):
174 return self.category + 's'
177 def get_absolute_url(self):
178 return 'tagged_object_list', [self.url_chunk]
181 def get_absolute_gallery_url(self):
182 return 'tagged_object_list_gallery', [self.url_chunk]
184 def has_description(self):
185 return len(self.description) > 0
186 has_description.short_description = _('description')
187 has_description.boolean = True
190 def get_tag_list(tag_str):
197 tags_splitted = tag_str.split('/')
198 for name in tags_splitted:
200 tags.append(Tag.objects.get(slug=name, category=category))
202 elif name in Tag.categories_rev:
203 category = Tag.categories_rev[name]
206 tags.append(Tag.objects.get(slug=name))
208 except Tag.MultipleObjectsReturned:
209 ambiguous_slugs.append(name)
212 # something strange left off
213 raise Tag.DoesNotExist()
215 # some tags should be qualified
216 e = Tag.MultipleObjectsReturned()
218 e.ambiguous_slugs = ambiguous_slugs
221 raise Tag.UrlDeprecationWarning(tags=tags)
226 return '/'.join((Tag.categories_dict[self.category], self.slug))
229 def tags_from_info(info):
230 from slugify import slugify
231 from sortify import sortify
233 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
234 for field_name, category in categories:
236 tag_names = getattr(info, field_name)
239 tag_names = [getattr(info, category)]
241 # For instance, Pictures do not have 'genre' field.
243 for tag_name in tag_names:
244 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
245 tag_sort_key = tag_name
246 if category == 'author':
247 tag_sort_key = ' '.join((tag_name.last_name,) + tag_name.first_names)
248 tag_name = tag_name.readable()
249 if lang == settings.LANGUAGE_CODE:
250 # Allow creating new tag, if it's in default language.
251 tag, created = Tag.objects.get_or_create(slug=slugify(tag_name), category=category)
253 tag_name = unicode(tag_name)
255 setattr(tag, "name_%s" % lang, tag_name)
256 tag.sort_key = sortify(tag_sort_key.lower())
259 meta_tags.append(tag)
261 # Ignore unknown tags in non-default languages.
263 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
264 except Tag.DoesNotExist:
267 meta_tags.append(tag)
271 TagRelation.tag_model = Tag
274 def prefetch_relations(objects, category, only_name=True):
275 queryset = TagRelation.objects.filter(tag__category=category).select_related('tag')
277 queryset = queryset.only('tag__name_pl', 'object_id')
278 return objects.prefetch_related(
279 Prefetch('tag_relations', queryset=queryset, to_attr='%s_relations' % category))
282 def prefetched_relations(obj, category):
283 if hasattr(obj, '%s_relations' % category):
284 return getattr(obj, '%s_relations' % category)