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.
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
14 # Those are hard-coded here so that makemessages sees them.
16 ('author', _('author')),
17 ('epoch', _('epoch')),
19 ('genre', _('genre')),
20 ('theme', _('theme')),
23 ('thing', _('thing')), # things shown on pictures
28 """A tag attachable to books and fragments (and possibly anything).
30 Used to represent searchable metadata (authors, epochs, genres, kinds),
31 fragment themes (motifs) and some book hierarchy related kludges."""
32 name = models.CharField(_('name'), max_length=50, db_index=True)
33 slug = models.SlugField(_('slug'), max_length=120, db_index=True)
34 sort_key = models.CharField(_('sort key'), max_length=120, db_index=True)
35 category = models.CharField(_('category'), max_length=50, blank=False, null=False,
36 db_index=True, choices=TAG_CATEGORIES)
37 description = models.TextField(_('description'), blank=True)
39 user = models.ForeignKey(User, blank=True, null=True)
40 book_count = models.IntegerField(_('book count'), blank=True, null=True)
41 picture_count = models.IntegerField(_('picture count'), blank=True, null=True)
42 gazeta_link = models.CharField(blank=True, max_length=240)
43 wiki_link = models.CharField(blank=True, max_length=240)
45 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
46 changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
48 class UrlDeprecationWarning(DeprecationWarning):
60 categories_dict = dict((item[::-1] for item in categories_rev.iteritems()))
63 ordering = ('sort_key',)
64 verbose_name = _('tag')
65 verbose_name_plural = _('tags')
66 unique_together = (("slug", "category"),)
67 app_label = 'catalogue'
69 def __unicode__(self):
73 return "Tag(slug=%r)" % self.slug
76 def get_absolute_url(self):
77 return ('catalogue.views.tagged_object_list', [self.url_chunk])
80 if self.category == 'book' and (self.gazeta_link or self.wiki_link):
81 raise ValidationError(ugettext(
82 u"Book tags can't have attached links. Set them directly on the book instead of it's tag."))
86 def create_url(cls, category, slug):
87 return ('catalogue.views.tagged_object_list', [
88 '/'.join((cls.categories_dict[category], slug))
91 def has_description(self):
92 return len(self.description) > 0
93 has_description.short_description = _('description')
94 has_description.boolean = True
97 """Returns global book count for book tags, fragment count for themes."""
98 from catalogue.models import Book, Fragment
100 if self.category == 'book':
102 objects = Book.objects.none()
103 elif self.category == 'theme':
104 objects = Fragment.tagged.with_all((self,))
106 objects = Book.tagged.with_all((self,)).order_by()
107 if self.category != 'set':
108 # eliminate descendants
109 l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects.iterator()])
110 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags).iterator()]
112 objects = objects.exclude(pk__in=descendants_keys)
113 return objects.count()
115 # I shouldn't break the get_count() api
116 # just to include pictures.
117 def get_picture_count(self):
118 from picture.models import Picture
120 if self.category == 'book':
122 objects = Book.objects.none()
123 elif self.category == 'theme':
124 objects = Picture.tagged.with_all((self,))
125 elif self.category == 'thing':
126 objects = Picture.tagged.with_all((self,))
128 objects = Picture.tagged.with_all((self,)).order_by()
129 return objects.count()
132 def get_tag_list(tags):
133 if isinstance(tags, basestring):
138 tags_splitted = tags.split('/')
139 for name in tags_splitted:
141 real_tags.append(Tag.objects.get(slug=name, category=category))
143 elif name in Tag.categories_rev:
144 category = Tag.categories_rev[name]
147 real_tags.append(Tag.objects.exclude(category='book').get(slug=name))
149 except Tag.MultipleObjectsReturned, e:
150 ambiguous_slugs.append(name)
153 # something strange left off
154 raise Tag.DoesNotExist()
156 # some tags should be qualified
157 e = Tag.MultipleObjectsReturned()
159 e.ambiguous_slugs = ambiguous_slugs
162 e = Tag.UrlDeprecationWarning()
167 return TagBase.get_tag_list(tags)
171 return '/'.join((Tag.categories_dict[self.category], self.slug))
174 def tags_from_info(info):
175 from fnpdjango.utils.text.slughifi import slughifi
176 from sortify import sortify
178 categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
179 for field_name, category in categories:
181 tag_names = getattr(info, field_name)
184 tag_names = [getattr(info, category)]
186 # For instance, Pictures do not have 'genre' field.
188 for tag_name in tag_names:
189 lang = getattr(tag_name, 'lang', settings.LANGUAGE_CODE)
190 tag_sort_key = tag_name
191 if category == 'author':
192 tag_sort_key = tag_name.last_name
193 tag_name = tag_name.readable()
194 if lang == settings.LANGUAGE_CODE:
195 # Allow creating new tag, if it's in default language.
196 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
199 setattr(tag, "name_%s" % lang, tag_name)
200 tag.sort_key = sortify(tag_sort_key.lower())
202 meta_tags.append(tag)
204 # Ignore unknown tags in non-default languages.
206 tag = Tag.objects.get(category=category, **{"name_%s" % lang: tag_name})
207 except Tag.DoesNotExist:
210 meta_tags.append(tag)