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 gettext_lazy as _
16 from newtagging.models import TagManager, TaggedItemManager
20 ('author', _('autor')),
21 ('epoch', _('epoka')),
22 ('kind', _('rodzaj')),
23 ('genre', _('gatunek')),
24 ('theme', _('motyw')),
26 ('thing', _('obiekt')), # things shown on pictures
30 class TagRelation(models.Model):
31 tag = models.ForeignKey('Tag', models.CASCADE, verbose_name='tag', related_name='items')
32 content_type = models.ForeignKey(ContentType, models.CASCADE, verbose_name='typ obiektu')
33 object_id = models.PositiveIntegerField('id obiektu', db_index=True)
34 content_object = GenericForeignKey('content_type', 'object_id')
36 objects = TaggedItemManager()
39 db_table = 'catalogue_tag_relation'
40 unique_together = (('tag', 'content_type', 'object_id'),)
44 return '%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
45 except ObjectDoesNotExist:
46 return '<deleted> [%s]' % self.tag
49 class Tag(models.Model):
50 """A tag attachable to books and fragments (and possibly anything).
52 Used to represent searchable metadata (authors, epochs, genres, kinds),
53 fragment themes (motifs) and some book hierarchy related kludges."""
54 name = models.CharField('nazwa', max_length=120, db_index=True)
55 slug = models.SlugField('slug', max_length=120, db_index=True)
56 sort_key = models.CharField('klucz sortowania', max_length=120, db_index=True)
57 category = models.CharField(
58 'kategoria', max_length=50, blank=False, null=False, db_index=True, choices=TAG_CATEGORIES)
59 description = models.TextField('opis', blank=True)
61 for_books = models.BooleanField(default=False)
62 for_pictures = models.BooleanField(default=False)
64 user = models.ForeignKey(User, models.CASCADE, blank=True, null=True)
65 gazeta_link = models.CharField(blank=True, max_length=240)
66 culturepl_link = models.CharField(blank=True, max_length=240)
67 wiki_link = models.CharField(blank=True, max_length=240)
68 photo = models.FileField(blank=True, null=True, upload_to='catalogue/tag/')
69 photo_attribution = models.CharField(max_length=255, blank=True)
71 created_at = models.DateTimeField('data utworzenia', auto_now_add=True, db_index=True)
72 changed_at = models.DateTimeField('data modyfikacji', auto_now=True, db_index=True)
74 plural = models.CharField(
75 'liczba mnoga', max_length=255, blank=True,
76 help_text='dotyczy gatunków'
78 genre_epoch_specific = models.BooleanField(
80 help_text='Po wskazaniu tego gatunku, dodanie epoki byłoby nadmiarowe, np. „dramat romantyczny”'
82 adjective_feminine_singular = models.CharField(
83 'przymiotnik pojedynczy żeński', max_length=255, blank=True,
84 help_text='twórczość … Adama Mickiewicza; dotyczy epok'
86 adjective_nonmasculine_plural = models.CharField(
87 'przymiotnik mnogi niemęskoosobowy', max_length=255, blank=True,
88 help_text='utwory … Adama Mickiewicza; dotyczy epok'
90 genitive = models.CharField(
91 'dopełniacz', max_length=255, blank=True,
92 help_text='utwory … (czyje?); dotyczy autorów'
94 collective_noun = models.CharField(
95 'określenie zbiorowe', max_length=255, blank=True,
96 help_text='np. „Liryka” albo „Twórczość dramatyczna”; dotyczy rodzajów'
99 after_change = Signal()
101 intermediary_table_model = TagRelation
102 objects = TagManager()
104 class UrlDeprecationWarning(DeprecationWarning):
105 def __init__(self, tags=None):
106 super(Tag.UrlDeprecationWarning, self).__init__()
118 categories_dict = dict((item[::-1] for item in categories_rev.items()))
121 ordering = ('sort_key',)
123 verbose_name_plural = 'tagi'
124 unique_together = (("slug", "category"),)
125 app_label = 'catalogue'
127 def save(self, *args, quick=False, **kwargs):
128 existing = self.pk and self.category != 'set'
129 ret = super(Tag, self).save(*args, **kwargs)
130 if existing and not quick:
131 self.after_change.send(sender=type(self), instance=self)
138 return "Tag(slug=%r)" % self.slug
140 def get_initial(self):
141 if self.category == 'author':
142 return self.sort_key[0]
149 def category_plural(self):
150 return self.category + 's'
152 def get_absolute_url(self):
153 return reverse('tagged_object_list', args=[self.url_chunk])
155 def get_absolute_gallery_url(self):
156 return reverse('tagged_object_list_gallery', args=[self.url_chunk])
158 def get_absolute_catalogue_url(self):
159 # TODO: remove magic.
160 if self.category == 'set':
161 return reverse('social_my_shelf')
162 elif self.category == 'thing':
165 return reverse(f'{self.category}_catalogue')
167 def has_description(self):
168 return len(self.description) > 0
169 has_description.short_description = 'opis'
170 has_description.boolean = True
173 def get_tag_list(tag_str):
180 tags_splitted = tag_str.split('/')
181 for name in tags_splitted:
183 tags.append(Tag.objects.get(slug=name, category=category))
185 elif name in Tag.categories_rev:
186 category = Tag.categories_rev[name]
189 tags.append(Tag.objects.get(slug=name))
191 except Tag.MultipleObjectsReturned:
192 ambiguous_slugs.append(name)
195 # something strange left off
196 raise Tag.DoesNotExist()
198 # some tags should be qualified
199 e = Tag.MultipleObjectsReturned()
201 e.ambiguous_slugs = ambiguous_slugs
204 raise Tag.UrlDeprecationWarning(tags=tags)
209 return '/'.join((Tag.categories_dict[self.category], self.slug))
212 def tags_from_info(info):
213 from slugify import slugify
214 from sortify import sortify
216 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
217 for field_name, category in categories:
219 tag_names = getattr(info, field_name)
220 except (AttributeError, KeyError): # TODO: shouldn't be KeyError here at all.
222 tag_names = [getattr(info, category)]
224 # For instance, Pictures do not have 'genre' field.
226 for tag_name in tag_names:
227 lang = getattr(tag_name, 'lang', None) or settings.LANGUAGE_CODE
228 tag_sort_key = tag_name
229 if category == 'author':
230 tag_sort_key = ' '.join((tag_name.last_name,) + tag_name.first_names)
231 tag_name = tag_name.readable()
234 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
235 except Tag.DoesNotExist:
236 if lang == settings.LANGUAGE_CODE:
237 # Allow creating new tag, if it's in default language.
238 tag, created = Tag.objects.get_or_create(slug=slugify(tag_name), category=category)
240 tag_name = str(tag_name)
242 setattr(tag, "name_%s" % lang, tag_name)
243 tag.sort_key = sortify(tag_sort_key.lower())
246 meta_tags.append(tag)
248 meta_tags.append(tag)
252 TagRelation.tag_model = Tag
255 def prefetch_relations(objects, category, only_name=True):
256 queryset = TagRelation.objects.filter(tag__category=category).select_related('tag')
258 queryset = queryset.only('tag__name_pl', 'object_id')
259 return objects.prefetch_related(
260 Prefetch('tag_relations', queryset=queryset, to_attr='%s_relations' % category))
263 def prefetched_relations(obj, category):
264 if hasattr(obj, '%s_relations' % category):
265 return getattr(obj, '%s_relations' % category)