Add Book.ancestor m2m.
[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     ('thing', _('thing')), # things shown on pictures
23 )
24
25
26 class Tag(TagBase):
27     """A tag attachable to books and fragments (and possibly anything).
28
29     Used to represent searchable metadata (authors, epochs, genres, kinds),
30     fragment themes (motifs) and some book hierarchy related kludges."""
31     name = models.CharField(_('name'), max_length=50, db_index=True)
32     slug = models.SlugField(_('slug'), max_length=120, db_index=True)
33     sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
34     category = models.CharField(_('category'), max_length=50, blank=False, null=False,
35         db_index=True, choices=TAG_CATEGORIES)
36     description = models.TextField(_('description'), blank=True)
37
38     user = models.ForeignKey(User, blank=True, null=True)
39     gazeta_link = models.CharField(blank=True, max_length=240)
40     culturepl_link = models.CharField(blank=True, max_length=240)
41     wiki_link = models.CharField(blank=True, max_length=240)
42
43     created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
44     changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
45
46     class UrlDeprecationWarning(DeprecationWarning):
47         pass
48
49     categories_rev = {
50         'autor': 'author',
51         'epoka': 'epoch',
52         'rodzaj': 'kind',
53         'gatunek': 'genre',
54         'motyw': 'theme',
55         'polka': 'set',
56         'obiekt': 'thing',
57     }
58     categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
59
60     class Meta:
61         ordering = ('sort_key',)
62         verbose_name = _('tag')
63         verbose_name_plural = _('tags')
64         unique_together = (("slug", "category"),)
65         app_label = 'catalogue'
66
67     def __unicode__(self):
68         return self.name
69
70     def __repr__(self):
71         return "Tag(slug=%r)" % self.slug
72
73     @permalink
74     def get_absolute_url(self):
75         return ('catalogue.views.tagged_object_list', [self.url_chunk])
76
77     @classmethod
78     @permalink
79     def create_url(cls, category, slug):
80         return ('catalogue.views.tagged_object_list', [
81                 '/'.join((cls.categories_dict[category], slug))
82             ])
83
84     def has_description(self):
85         return len(self.description) > 0
86     has_description.short_description = _('description')
87     has_description.boolean = True
88
89     @staticmethod
90     def get_tag_list(tags):
91         if isinstance(tags, basestring):
92             real_tags = []
93             ambiguous_slugs = []
94             category = None
95             deprecated = False
96             tags_splitted = tags.split('/')
97             for name in tags_splitted:
98                 if category:
99                     real_tags.append(Tag.objects.get(slug=name, category=category))
100                     category = None
101                 elif name in Tag.categories_rev:
102                     category = Tag.categories_rev[name]
103                 else:
104                     try:
105                         real_tags.append(Tag.objects.get(slug=name))
106                         deprecated = True
107                     except Tag.MultipleObjectsReturned, e:
108                         ambiguous_slugs.append(name)
109
110             if category:
111                 # something strange left off
112                 raise Tag.DoesNotExist()
113             if ambiguous_slugs:
114                 # some tags should be qualified
115                 e = Tag.MultipleObjectsReturned()
116                 e.tags = real_tags
117                 e.ambiguous_slugs = ambiguous_slugs
118                 raise e
119             if deprecated:
120                 e = Tag.UrlDeprecationWarning()
121                 e.tags = real_tags
122                 raise e
123             return real_tags
124         else:
125             return TagBase.get_tag_list(tags)
126
127     @property
128     def url_chunk(self):
129         return '/'.join((Tag.categories_dict[self.category], self.slug))
130
131     @staticmethod
132     def tags_from_info(info):
133         from fnpdjango.utils.text.slughifi import slughifi
134         from sortify import sortify
135         meta_tags = []
136         categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
137         for field_name, category in categories:
138             try:
139                 tag_names = getattr(info, field_name)
140             except:
141                 try:
142                     tag_names = [getattr(info, category)]
143                 except:
144                     # For instance, Pictures do not have 'genre' field.
145                     continue
146             for tag_name in tag_names:
147                 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
148                 tag_sort_key = tag_name
149                 if category == 'author':
150                     tag_sort_key = tag_name.last_name
151                     tag_name = tag_name.readable()
152                 if lang == settings.LANGUAGE_CODE:
153                     # Allow creating new tag, if it's in default language.
154                     tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
155                     if created:
156                         tag_name = unicode(tag_name)
157                         tag.name = tag_name
158                         setattr(tag, "name_%s" % lang, tag_name)
159                         tag.sort_key = sortify(tag_sort_key.lower())
160                         tag.save()
161
162                     meta_tags.append(tag)
163                 else:
164                     # Ignore unknown tags in non-default languages.
165                     try:
166                         tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
167                     except Tag.DoesNotExist:
168                         pass
169                     else:
170                         meta_tags.append(tag)
171         return meta_tags
172
173
174 # Pickle complains about not having this.
175 TagRelation = Tag.intermediary_table_model