viewer basically implemented. going to wire information/links on the site
[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     gazeta_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     }
57     categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
58
59     class Meta:
60         ordering = ('sort_key',)
61         verbose_name = _('tag')
62         verbose_name_plural = _('tags')
63         unique_together = (("slug", "category"),)
64         app_label = 'catalogue'
65
66     def __unicode__(self):
67         return self.name
68
69     def __repr__(self):
70         return "Tag(slug=%r)" % self.slug
71
72     @permalink
73     def get_absolute_url(self):
74         return ('catalogue.views.tagged_object_list', [self.url_chunk])
75
76     def clean(self):
77         if self.category == 'book' and (self.gazeta_link or self.wiki_link):
78             raise ValidationError(ugettext(
79                 u"Book tags can't have attached links. Set them directly on the book instead of it's tag."))
80
81     @classmethod
82     @permalink
83     def create_url(cls, category, slug):
84         return ('catalogue.views.tagged_object_list', [
85                 '/'.join((cls.categories_dict[category], slug))
86             ])
87
88     def has_description(self):
89         return len(self.description) > 0
90     has_description.short_description = _('description')
91     has_description.boolean = True
92
93     def get_count(self):
94         """Returns global book count for book tags, fragment count for themes."""
95         from catalogue.models import Book, Fragment
96
97         if self.category == 'book':
98             # never used
99             objects = Book.objects.none()
100         elif self.category == 'theme':
101             objects = Fragment.tagged.with_all((self,))
102         else:
103             objects = Book.tagged.with_all((self,)).order_by()
104             if self.category != 'set':
105                 # eliminate descendants
106                 l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects.iterator()])
107                 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags).iterator()]
108                 if descendants_keys:
109                     objects = objects.exclude(pk__in=descendants_keys)
110         return objects.count()
111
112     @staticmethod
113     def get_tag_list(tags):
114         if isinstance(tags, basestring):
115             real_tags = []
116             ambiguous_slugs = []
117             category = None
118             deprecated = False
119             tags_splitted = tags.split('/')
120             for name in tags_splitted:
121                 if category:
122                     real_tags.append(Tag.objects.get(slug=name, category=category))
123                     category = None
124                 elif name in Tag.categories_rev:
125                     category = Tag.categories_rev[name]
126                 else:
127                     try:
128                         real_tags.append(Tag.objects.exclude(category='book').get(slug=name))
129                         deprecated = True 
130                     except Tag.MultipleObjectsReturned, e:
131                         ambiguous_slugs.append(name)
132
133             if category:
134                 # something strange left off
135                 raise Tag.DoesNotExist()
136             if ambiguous_slugs:
137                 # some tags should be qualified
138                 e = Tag.MultipleObjectsReturned()
139                 e.tags = real_tags
140                 e.ambiguous_slugs = ambiguous_slugs
141                 raise e
142             if deprecated:
143                 e = Tag.UrlDeprecationWarning()
144                 e.tags = real_tags
145                 raise e
146             return real_tags
147         else:
148             return TagBase.get_tag_list(tags)
149
150     @property
151     def url_chunk(self):
152         return '/'.join((Tag.categories_dict[self.category], self.slug))
153
154     @staticmethod
155     def tags_from_info(info):
156         from fnpdjango.utils.text.slughifi import slughifi
157         from sortify import sortify
158         meta_tags = []
159         categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
160         for field_name, category in categories:
161             try:
162                 tag_names = getattr(info, field_name)
163             except:
164                 try:
165                     tag_names = [getattr(info, category)]
166                 except:
167                     # For instance, Pictures do not have 'genre' field.
168                     continue
169             for tag_name in tag_names:
170                 tag_sort_key = tag_name
171                 if category == 'author':
172                     tag_sort_key = tag_name.last_name
173                     tag_name = tag_name.readable()
174                 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
175                 if created:
176                     tag.name = tag_name
177                     tag.sort_key = sortify(tag_sort_key.lower())
178                     tag.save()
179                 meta_tags.append(tag)
180         return meta_tags