split catalogue.models
[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     def has_description(self):
75         return len(self.description) > 0
76     has_description.short_description = _('description')
77     has_description.boolean = True
78
79     def get_count(self):
80         """Returns global book count for book tags, fragment count for themes."""
81         from catalogue.models import Book, Fragment
82
83         if self.category == 'book':
84             # never used
85             objects = Book.objects.none()
86         elif self.category == 'theme':
87             objects = Fragment.tagged.with_all((self,))
88         else:
89             objects = Book.tagged.with_all((self,)).order_by()
90             if self.category != 'set':
91                 # eliminate descendants
92                 l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects.iterator()])
93                 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags).iterator()]
94                 if descendants_keys:
95                     objects = objects.exclude(pk__in=descendants_keys)
96         return objects.count()
97
98     @staticmethod
99     def get_tag_list(tags):
100         if isinstance(tags, basestring):
101             real_tags = []
102             ambiguous_slugs = []
103             category = None
104             deprecated = False
105             tags_splitted = tags.split('/')
106             for name in tags_splitted:
107                 if category:
108                     real_tags.append(Tag.objects.get(slug=name, category=category))
109                     category = None
110                 elif name in Tag.categories_rev:
111                     category = Tag.categories_rev[name]
112                 else:
113                     try:
114                         real_tags.append(Tag.objects.exclude(category='book').get(slug=name))
115                         deprecated = True 
116                     except Tag.MultipleObjectsReturned, e:
117                         ambiguous_slugs.append(name)
118
119             if category:
120                 # something strange left off
121                 raise Tag.DoesNotExist()
122             if ambiguous_slugs:
123                 # some tags should be qualified
124                 e = Tag.MultipleObjectsReturned()
125                 e.tags = real_tags
126                 e.ambiguous_slugs = ambiguous_slugs
127                 raise e
128             if deprecated:
129                 e = Tag.UrlDeprecationWarning()
130                 e.tags = real_tags
131                 raise e
132             return real_tags
133         else:
134             return TagBase.get_tag_list(tags)
135
136     @property
137     def url_chunk(self):
138         return '/'.join((Tag.categories_dict[self.category], self.slug))
139
140     @staticmethod
141     def tags_from_info(info):
142         from slughifi import slughifi
143         from sortify import sortify
144         meta_tags = []
145         categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
146         for field_name, category in categories:
147             try:
148                 tag_names = getattr(info, field_name)
149             except:
150                 try:
151                     tag_names = [getattr(info, category)]
152                 except:
153                     # For instance, Pictures do not have 'genre' field.
154                     continue
155             for tag_name in tag_names:
156                 tag_sort_key = tag_name
157                 if category == 'author':
158                     tag_sort_key = tag_name.last_name
159                     tag_name = tag_name.readable()
160                 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
161                 if created:
162                     tag.name = tag_name
163                     tag.sort_key = sortify(tag_sort_key.lower())
164                     tag.save()
165                 meta_tags.append(tag)
166         return meta_tags