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 get_absolute_url(self):
171 return 'tagged_object_list', [self.url_chunk]
174 def get_absolute_gallery_url(self):
175 return 'tagged_object_list_gallery', [self.url_chunk]
179 def create_url(cls, category, slug):
180 return ('catalogue.views.tagged_object_list', [
181 '/'.join((cls.categories_dict[category], slug))
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):
191 if isinstance(tag_str, basestring):
198 tags_splitted = tag_str.split('/')
199 for name in tags_splitted:
201 tags.append(Tag.objects.get(slug=name, category=category))
203 elif name in Tag.categories_rev:
204 category = Tag.categories_rev[name]
207 tags.append(Tag.objects.get(slug=name))
209 except Tag.MultipleObjectsReturned:
210 ambiguous_slugs.append(name)
213 # something strange left off
214 raise Tag.DoesNotExist()
216 # some tags should be qualified
217 e = Tag.MultipleObjectsReturned()
219 e.ambiguous_slugs = ambiguous_slugs
222 raise Tag.UrlDeprecationWarning(tags=tags)
225 return TagBase.get_tag_list(tag_str)
229 return '/'.join((Tag.categories_dict[self.category], self.slug))
232 def tags_from_info(info):
233 from fnpdjango.utils.text.slughifi import slughifi
234 from sortify import sortify
236 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
237 for field_name, category in categories:
239 tag_names = getattr(info, field_name)
242 tag_names = [getattr(info, category)]
244 # For instance, Pictures do not have 'genre' field.
246 for tag_name in tag_names:
247 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
248 tag_sort_key = tag_name
249 if category == 'author':
250 tag_sort_key = ' '.join((tag_name.last_name,) + tag_name.first_names)
251 tag_name = tag_name.readable()
252 if lang == settings.LANGUAGE_CODE:
253 # Allow creating new tag, if it's in default language.
254 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
256 tag_name = unicode(tag_name)
258 setattr(tag, "name_%s" % lang, tag_name)
259 tag.sort_key = sortify(tag_sort_key.lower())
262 meta_tags.append(tag)
264 # Ignore unknown tags in non-default languages.
266 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
267 except Tag.DoesNotExist:
270 meta_tags.append(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)