ba219fc7ce6eb99b6a3dde64552209449d238440
[wolnelektury.git] / apps / catalogue / models / tag.py
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.
4 #
5 from django.conf import settings
6 from django.contrib.auth.models import User
7 from django.core.exceptions import ValidationError
8 from django.db import models
9 from django.db.models import permalink
10 from django.utils.translation import ugettext, ugettext_lazy as _
11 from newtagging.models import TagBase
12
13
14 # Those are hard-coded here so that makemessages sees them.
15 TAG_CATEGORIES = (
16     ('author', _('author')),
17     ('epoch', _('epoch')),
18     ('kind', _('kind')),
19     ('genre', _('genre')),
20     ('theme', _('theme')),
21     ('set', _('set')),
22     ('book', _('book')),
23     ('thing', _('thing')), # things shown on pictures
24 )
25
26
27 class Tag(TagBase):
28     """A tag attachable to books and fragments (and possibly anything).
29
30     Used to represent searchable metadata (authors, epochs, genres, kinds),
31     fragment themes (motifs) and some book hierarchy related kludges."""
32     name = models.CharField(_('name'), max_length=50, db_index=True)
33     slug = models.SlugField(_('slug'), max_length=120, db_index=True)
34     sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
35     category = models.CharField(_('category'), max_length=50, blank=False, null=False,
36         db_index=True, choices=TAG_CATEGORIES)
37     description = models.TextField(_('description'), blank=True)
38
39     user = models.ForeignKey(User, blank=True, null=True)
40     book_count = models.IntegerField(_('book count'), blank=True, null=True)
41     picture_count = models.IntegerField(_('picture count'), blank=True, null=True)
42     gazeta_link = models.CharField(blank=True, max_length=240)
43     culturepl_link = models.CharField(blank=True, max_length=240)
44     wiki_link = models.CharField(blank=True, max_length=240)
45
46     created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
47     changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
48
49     class UrlDeprecationWarning(DeprecationWarning):
50         pass
51
52     categories_rev = {
53         'autor': 'author',
54         'epoka': 'epoch',
55         'rodzaj': 'kind',
56         'gatunek': 'genre',
57         'motyw': 'theme',
58         'polka': 'set',
59         'obiekt': 'thing',
60     }
61     categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
62
63     class Meta:
64         ordering = ('sort_key',)
65         verbose_name = _('tag')
66         verbose_name_plural = _('tags')
67         unique_together = (("slug", "category"),)
68         app_label = 'catalogue'
69
70     def __unicode__(self):
71         return self.name
72
73     def __repr__(self):
74         return "Tag(slug=%r)" % self.slug
75
76     @permalink
77     def get_absolute_url(self):
78         return ('catalogue.views.tagged_object_list', [self.url_chunk])
79
80     def clean(self):
81         if self.category == 'book' and (self.gazeta_link or self.wiki_link):
82             raise ValidationError(ugettext(
83                 u"Book tags can't have attached links. Set them directly on the book instead of it's tag."))
84
85     @classmethod
86     @permalink
87     def create_url(cls, category, slug):
88         return ('catalogue.views.tagged_object_list', [
89                 '/'.join((cls.categories_dict[category], slug))
90             ])
91
92     def has_description(self):
93         return len(self.description) > 0
94     has_description.short_description = _('description')
95     has_description.boolean = True
96
97     def get_count(self):
98         """Returns global book count for book tags, fragment count for themes."""
99         from catalogue.models import Book, Fragment
100
101         if self.category == 'book':
102             # never used
103             objects = Book.objects.none()
104         elif self.category == 'theme':
105             objects = Fragment.tagged.with_all((self,))
106         else:
107             objects = Book.tagged.with_all((self,)).order_by()
108             if self.category != 'set':
109                 # eliminate descendants
110                 l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects.iterator()])
111                 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags).iterator()]
112                 if descendants_keys:
113                     objects = objects.exclude(pk__in=descendants_keys)
114         return objects.count()
115
116     # I shouldn't break the get_count() api
117     # just to include pictures.
118     def get_picture_count(self):
119         from picture.models import Picture, PictureArea
120
121         if self.category == 'book':
122             # never used
123             objects = Picture.objects.none()
124         elif self.category == 'theme':
125             objects = PictureArea.tagged.with_all((self,))
126         elif self.category == 'thing':
127             objects = Picture.tagged.with_all((self,))
128         else:
129             objects = Picture.tagged.with_all((self,)).order_by()
130         return objects.count()
131
132     @staticmethod
133     def get_tag_list(tags):
134         if isinstance(tags, basestring):
135             real_tags = []
136             ambiguous_slugs = []
137             category = None
138             deprecated = False
139             tags_splitted = tags.split('/')
140             for name in tags_splitted:
141                 if category:
142                     real_tags.append(Tag.objects.get(slug=name, category=category))
143                     category = None
144                 elif name in Tag.categories_rev:
145                     category = Tag.categories_rev[name]
146                 else:
147                     try:
148                         real_tags.append(Tag.objects.exclude(category='book').get(slug=name))
149                         deprecated = True
150                     except Tag.MultipleObjectsReturned, e:
151                         ambiguous_slugs.append(name)
152
153             if category:
154                 # something strange left off
155                 raise Tag.DoesNotExist()
156             if ambiguous_slugs:
157                 # some tags should be qualified
158                 e = Tag.MultipleObjectsReturned()
159                 e.tags = real_tags
160                 e.ambiguous_slugs = ambiguous_slugs
161                 raise e
162             if deprecated:
163                 e = Tag.UrlDeprecationWarning()
164                 e.tags = real_tags
165                 raise e
166             return real_tags
167         else:
168             return TagBase.get_tag_list(tags)
169
170     @property
171     def url_chunk(self):
172         return '/'.join((Tag.categories_dict[self.category], self.slug))
173
174     @staticmethod
175     def tags_from_info(info):
176         from fnpdjango.utils.text.slughifi import slughifi
177         from sortify import sortify
178         meta_tags = []
179         categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
180         for field_name, category in categories:
181             try:
182                 tag_names = getattr(info, field_name)
183             except:
184                 try:
185                     tag_names = [getattr(info, category)]
186                 except:
187                     # For instance, Pictures do not have 'genre' field.
188                     continue
189             for tag_name in tag_names:
190                 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
191                 tag_sort_key = tag_name
192                 if category == 'author':
193                     tag_sort_key = tag_name.last_name
194                     tag_name = tag_name.readable()
195                 if lang == settings.LANGUAGE_CODE:
196                     # Allow creating new tag, if it's in default language.
197                     tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
198                     if created:
199                         tag_name = unicode(tag_name)
200                         tag.name = tag_name
201                         setattr(tag, "name_%s" % lang, tag_name)
202                         tag.sort_key = sortify(tag_sort_key.lower())
203                         tag.save()
204
205                     meta_tags.append(tag)
206                 else:
207                     # Ignore unknown tags in non-default languages.
208                     try:
209                         tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
210                     except Tag.DoesNotExist:
211                         pass
212                     else:
213                         meta_tags.append(tag)
214         return meta_tags