3c4509ddae74df2902f844c7fe2c1463362db5fb
[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.contrib.auth.models import User
6 from django.db import models
7 from django.db.models import permalink
8 from django.utils.translation import ugettext_lazy as _
9 from newtagging.models import TagBase
10
11
12 # Those are hard-coded here so that makemessages sees them.
13 TAG_CATEGORIES = (
14     ('author', _('author')),
15     ('epoch', _('epoch')),
16     ('kind', _('kind')),
17     ('genre', _('genre')),
18     ('theme', _('theme')),
19     ('set', _('set')),
20     ('book', _('book')),
21 )
22
23
24 class Tag(TagBase):
25     """A tag attachable to books and fragments (and possibly anything).
26     
27     Used to represent searchable metadata (authors, epochs, genres, kinds),
28     fragment themes (motifs) and some book hierarchy related kludges."""
29     name = models.CharField(_('name'), max_length=50, db_index=True)
30     slug = models.SlugField(_('slug'), max_length=120, db_index=True)
31     sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
32     category = models.CharField(_('category'), max_length=50, blank=False, null=False,
33         db_index=True, choices=TAG_CATEGORIES)
34     description = models.TextField(_('description'), blank=True)
35
36     user = models.ForeignKey(User, blank=True, null=True)
37     book_count = models.IntegerField(_('book count'), blank=True, null=True)
38     gazeta_link = models.CharField(blank=True, max_length=240)
39     wiki_link = models.CharField(blank=True, max_length=240)
40
41     created_at    = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
42     changed_at    = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
43
44     class UrlDeprecationWarning(DeprecationWarning):
45         pass
46
47     categories_rev = {
48         'autor': 'author',
49         'epoka': 'epoch',
50         'rodzaj': 'kind',
51         'gatunek': 'genre',
52         'motyw': 'theme',
53         'polka': 'set',
54     }
55     categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
56
57     class Meta:
58         ordering = ('sort_key',)
59         verbose_name = _('tag')
60         verbose_name_plural = _('tags')
61         unique_together = (("slug", "category"),)
62         app_label = 'catalogue'
63
64     def __unicode__(self):
65         return self.name
66
67     def __repr__(self):
68         return "Tag(slug=%r)" % self.slug
69
70     @permalink
71     def get_absolute_url(self):
72         return ('catalogue.views.tagged_object_list', [self.url_chunk])
73
74     @classmethod
75     @permalink
76     def create_url(cls, category, slug):
77         return ('catalogue.views.tagged_object_list', [
78                 '/'.join((cls.categories_dict[category], slug))
79             ])
80
81     def has_description(self):
82         return len(self.description) > 0
83     has_description.short_description = _('description')
84     has_description.boolean = True
85
86     def get_count(self):
87         """Returns global book count for book tags, fragment count for themes."""
88         from catalogue.models import Book, Fragment
89
90         if self.category == 'book':
91             # never used
92             objects = Book.objects.none()
93         elif self.category == 'theme':
94             objects = Fragment.tagged.with_all((self,))
95         else:
96             objects = Book.tagged.with_all((self,)).order_by()
97             if self.category != 'set':
98                 # eliminate descendants
99                 l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects.iterator()])
100                 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags).iterator()]
101                 if descendants_keys:
102                     objects = objects.exclude(pk__in=descendants_keys)
103         return objects.count()
104
105     @staticmethod
106     def get_tag_list(tags):
107         if isinstance(tags, basestring):
108             real_tags = []
109             ambiguous_slugs = []
110             category = None
111             deprecated = False
112             tags_splitted = tags.split('/')
113             for name in tags_splitted:
114                 if category:
115                     real_tags.append(Tag.objects.get(slug=name, category=category))
116                     category = None
117                 elif name in Tag.categories_rev:
118                     category = Tag.categories_rev[name]
119                 else:
120                     try:
121                         real_tags.append(Tag.objects.exclude(category='book').get(slug=name))
122                         deprecated = True 
123                     except Tag.MultipleObjectsReturned, e:
124                         ambiguous_slugs.append(name)
125
126             if category:
127                 # something strange left off
128                 raise Tag.DoesNotExist()
129             if ambiguous_slugs:
130                 # some tags should be qualified
131                 e = Tag.MultipleObjectsReturned()
132                 e.tags = real_tags
133                 e.ambiguous_slugs = ambiguous_slugs
134                 raise e
135             if deprecated:
136                 e = Tag.UrlDeprecationWarning()
137                 e.tags = real_tags
138                 raise e
139             return real_tags
140         else:
141             return TagBase.get_tag_list(tags)
142
143     @property
144     def url_chunk(self):
145         return '/'.join((Tag.categories_dict[self.category], self.slug))
146
147     @staticmethod
148     def tags_from_info(info):
149         from slughifi import slughifi
150         from sortify import sortify
151         meta_tags = []
152         categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
153         for field_name, category in categories:
154             try:
155                 tag_names = getattr(info, field_name)
156             except:
157                 try:
158                     tag_names = [getattr(info, category)]
159                 except:
160                     # For instance, Pictures do not have 'genre' field.
161                     continue
162             for tag_name in tag_names:
163                 tag_sort_key = tag_name
164                 if category == 'author':
165                     tag_sort_key = tag_name.last_name
166                     tag_name = tag_name.readable()
167                 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
168                 if created:
169                     tag.name = tag_name
170                     tag.sort_key = sortify(tag_sort_key.lower())
171                     tag.save()
172                 meta_tags.append(tag)
173         return meta_tags