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