1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 from django.conf import settings
5 from django.contrib.contenttypes.fields import GenericForeignKey
6 from django.contrib.contenttypes.models import ContentType
7 from django.core.cache import caches
8 from django.contrib.auth.models import User
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.db import models
11 from django.db.models.query import Prefetch
12 from django.dispatch import Signal
13 from django.urls import reverse
14 from django.utils.translation import ugettext_lazy as _
16 from newtagging.models import TagManager, TaggedItemManager
19 # Those are hard-coded here so that makemessages sees them.
21 ('author', _('author')),
22 ('epoch', _('epoch')),
24 ('genre', _('genre')),
25 ('theme', _('theme')),
27 ('thing', _('thing')), # things shown on pictures
31 class TagRelation(models.Model):
33 tag = models.ForeignKey('Tag', models.CASCADE, verbose_name=_('tag'), related_name='items')
34 content_type = models.ForeignKey(ContentType, models.CASCADE, verbose_name=_('content type'))
35 object_id = models.PositiveIntegerField(_('object id'), db_index=True)
36 content_object = GenericForeignKey('content_type', 'object_id')
38 objects = TaggedItemManager()
41 db_table = 'catalogue_tag_relation'
42 unique_together = (('tag', 'content_type', 'object_id'),)
46 return '%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
47 except ObjectDoesNotExist:
48 return '<deleted> [%s]' % self.tag
51 class Tag(models.Model):
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, models.CASCADE, 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)
70 photo = models.FileField(blank=True, null=True, upload_to='catalogue/tag/')
71 photo_attribution = models.CharField(max_length=255, blank=True)
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'])
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.items()))
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 existing = self.pk and self.category != 'set'
106 ret = super(Tag, self).save(*args, **kwargs)
108 self.after_change.send(sender=type(self), instance=self)
115 return "Tag(slug=%r)" % self.slug
117 def get_initial(self):
118 if self.category == 'author':
119 return self.sort_key[0]
126 def category_plural(self):
127 return self.category + 's'
129 def get_absolute_url(self):
130 return reverse('tagged_object_list', args=[self.url_chunk])
132 def get_absolute_gallery_url(self):
133 return reverse('tagged_object_list_gallery', args=[self.url_chunk])
135 def has_description(self):
136 return len(self.description) > 0
137 has_description.short_description = _('description')
138 has_description.boolean = True
141 def get_tag_list(tag_str):
148 tags_splitted = tag_str.split('/')
149 for name in tags_splitted:
151 tags.append(Tag.objects.get(slug=name, category=category))
153 elif name in Tag.categories_rev:
154 category = Tag.categories_rev[name]
157 tags.append(Tag.objects.get(slug=name))
159 except Tag.MultipleObjectsReturned:
160 ambiguous_slugs.append(name)
163 # something strange left off
164 raise Tag.DoesNotExist()
166 # some tags should be qualified
167 e = Tag.MultipleObjectsReturned()
169 e.ambiguous_slugs = ambiguous_slugs
172 raise Tag.UrlDeprecationWarning(tags=tags)
177 return '/'.join((Tag.categories_dict[self.category], self.slug))
180 def tags_from_info(info):
181 from slugify import slugify
182 from sortify import sortify
184 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
185 for field_name, category in categories:
187 tag_names = getattr(info, field_name)
188 except (AttributeError, KeyError): # TODO: shouldn't be KeyError here at all.
190 tag_names = [getattr(info, category)]
192 # For instance, Pictures do not have 'genre' field.
194 for tag_name in tag_names:
195 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
196 tag_sort_key = tag_name
197 if category == 'author':
198 tag_sort_key = ' '.join((tag_name.last_name,) + tag_name.first_names)
199 tag_name = tag_name.readable()
200 if lang == settings.LANGUAGE_CODE:
201 # Allow creating new tag, if it's in default language.
202 tag, created = Tag.objects.get_or_create(slug=slugify(tag_name), category=category)
204 tag_name = str(tag_name)
206 setattr(tag, "name_%s" % lang, tag_name)
207 tag.sort_key = sortify(tag_sort_key.lower())
210 meta_tags.append(tag)
212 # Ignore unknown tags in non-default languages.
214 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
215 except Tag.DoesNotExist:
218 meta_tags.append(tag)
222 TagRelation.tag_model = Tag
225 def prefetch_relations(objects, category, only_name=True):
226 queryset = TagRelation.objects.filter(tag__category=category).select_related('tag')
228 queryset = queryset.only('tag__name_pl', 'object_id')
229 return objects.prefetch_related(
230 Prefetch('tag_relations', queryset=queryset, to_attr='%s_relations' % category))
233 def prefetched_relations(obj, category):
234 if hasattr(obj, '%s_relations' % category):
235 return getattr(obj, '%s_relations' % category)