add index on popularity count
[wolnelektury.git] / src / catalogue / models / book.py
index 5ac999e..8b85ec7 100644 (file)
@@ -13,7 +13,8 @@ from django.db.models import permalink
 import django.dispatch
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.urlresolvers import reverse
 import django.dispatch
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext_lazy as _, get_language
+from django.utils.deconstruct import deconstructible
 import jsonfield
 from fnpdjango.storage import BofhFileSystemStorage
 from ssify import flush_ssi_includes
 import jsonfield
 from fnpdjango.storage import BofhFileSystemStorage
 from ssify import flush_ssi_includes
@@ -21,7 +22,7 @@ from newtagging import managers
 from catalogue import constants
 from catalogue.fields import EbookField
 from catalogue.models import Tag, Fragment, BookMedia
 from catalogue import constants
 from catalogue.fields import EbookField
 from catalogue.models import Tag, Fragment, BookMedia
-from catalogue.utils import create_zip, gallery_url, gallery_path
+from catalogue.utils import create_zip, gallery_url, gallery_path, split_tags
 from catalogue.models.tag import prefetched_relations
 from catalogue import app_settings
 from catalogue import tasks
 from catalogue.models.tag import prefetched_relations
 from catalogue import app_settings
 from catalogue import tasks
@@ -30,18 +31,23 @@ from wolnelektury.utils import makedirs
 bofh_storage = BofhFileSystemStorage()
 
 
 bofh_storage = BofhFileSystemStorage()
 
 
-def _make_upload_to(path):
-    def _upload_to(i, n):
-        return path % i.slug
-    return _upload_to
+@deconstructible
+class UploadToPath(object):
+    def __init__(self, path):
+        self.path = path
 
 
+    def __call__(self, instance, filename):
+        return self.path % instance.slug
 
 
-_cover_upload_to = _make_upload_to('book/cover/%s.jpg')
-_cover_thumb_upload_to = _make_upload_to('book/cover_thumb/%s.jpg')
+
+_cover_upload_to = UploadToPath('book/cover/%s.jpg')
+_cover_thumb_upload_to = UploadToPath('book/cover_thumb/%s.jpg')
+_cover_api_thumb_upload_to = UploadToPath('book/cover_api_thumb/%s.jpg')
+_simple_cover_upload_to = UploadToPath('book/cover_simple/%s.jpg')
 
 
 def _ebook_upload_to(upload_path):
 
 
 def _ebook_upload_to(upload_path):
-    return _make_upload_to(upload_path)
+    return UploadToPath(upload_path)
 
 
 class Book(models.Model):
 
 
 class Book(models.Model):
@@ -55,11 +61,13 @@ class Book(models.Model):
     language = models.CharField(_('language code'), max_length=3, db_index=True, default=app_settings.DEFAULT_LANGUAGE)
     description = models.TextField(_('description'), blank=True)
     created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
     language = models.CharField(_('language code'), max_length=3, db_index=True, default=app_settings.DEFAULT_LANGUAGE)
     description = models.TextField(_('description'), blank=True)
     created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
-    changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
+    changed_at = models.DateTimeField(_('change date'), auto_now=True, db_index=True)
     parent_number = models.IntegerField(_('parent number'), default=0)
     extra_info = jsonfield.JSONField(_('extra information'), default={})
     gazeta_link = models.CharField(blank=True, max_length=240)
     wiki_link = models.CharField(blank=True, max_length=240)
     parent_number = models.IntegerField(_('parent number'), default=0)
     extra_info = jsonfield.JSONField(_('extra information'), default={})
     gazeta_link = models.CharField(blank=True, max_length=240)
     wiki_link = models.CharField(blank=True, max_length=240)
+    print_on_demand = models.BooleanField(_('print on demand'), default=False)
+    recommended = models.BooleanField(_('recommended'), default=False)
 
     # files generated during publication
     cover = EbookField(
 
     # files generated during publication
     cover = EbookField(
@@ -73,12 +81,25 @@ class Book(models.Model):
         null=True, blank=True,
         upload_to=_cover_thumb_upload_to,
         max_length=255)
         null=True, blank=True,
         upload_to=_cover_thumb_upload_to,
         max_length=255)
+    cover_api_thumb = EbookField(
+        'cover_api_thumb', _('cover thumbnail for mobile app'),
+        null=True, blank=True,
+        upload_to=_cover_api_thumb_upload_to,
+        max_length=255)
+    simple_cover = EbookField(
+        'simple_cover', _('cover for mobile app'),
+        null=True, blank=True,
+        upload_to=_simple_cover_upload_to,
+        max_length=255)
     ebook_formats = constants.EBOOK_FORMATS
     formats = ebook_formats + ['html', 'xml']
 
     parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
     ancestor = models.ManyToManyField('self', blank=True, editable=False, related_name='descendant', symmetrical=False)
 
     ebook_formats = constants.EBOOK_FORMATS
     formats = ebook_formats + ['html', 'xml']
 
     parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
     ancestor = models.ManyToManyField('self', blank=True, editable=False, related_name='descendant', symmetrical=False)
 
+    cached_author = models.CharField(blank=True, max_length=240, db_index=True)
+    has_audience = models.BooleanField(default=False)
+
     objects = models.Manager()
     tagged = managers.ModelTaggedItemManager(Tag)
     tags = managers.TagDescriptor(Tag)
     objects = models.Manager()
     tagged = managers.ModelTaggedItemManager(Tag)
     tags = managers.TagDescriptor(Tag)
@@ -117,8 +138,25 @@ class Book(models.Model):
         else:
             return ', '.join(self.tags.filter(category=category).values_list('name', flat=True))
 
         else:
             return ', '.join(self.tags.filter(category=category).values_list('name', flat=True))
 
+    def tags_by_category(self):
+        return split_tags(self.tags.exclude(category__in=('set', 'theme')))
+
     def author_unicode(self):
     def author_unicode(self):
-        return self.tag_unicode('author')
+        return self.cached_author
+
+    def translator(self):
+        translators = self.extra_info.get('translators')
+        if not translators:
+            return None
+        if len(translators) > 3:
+            translators = translators[:2]
+            others = ' i inni'
+        else:
+            others = ''
+        return ', '.join(u'\xa0'.join(reversed(translator.split(', ', 1))) for translator in translators) + others
+
+    def cover_source(self):
+        return self.extra_info.get('cover_source', self.parent.cover_source() if self.parent else '')
 
     def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
 
     def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
@@ -132,6 +170,9 @@ class Book(models.Model):
             author = u''
         self.sort_key_author = author
 
             author = u''
         self.sort_key_author = author
 
+        self.cached_author = self.tag_unicode('author')
+        self.has_audience = 'audience' in self.extra_info
+
         ret = super(Book, self).save(force_insert, force_update, **kwargs)
 
         return ret
         ret = super(Book, self).save(force_insert, force_update, **kwargs)
 
         return ret
@@ -170,6 +211,9 @@ class Book(models.Model):
         else:
             return self.media.filter(type=type_).exists()
 
         else:
             return self.media.filter(type=type_).exists()
 
+    def has_audio(self):
+        return self.has_media('mp3')
+
     def get_media(self, type_):
         if self.has_media(type_):
             if type_ in Book.formats:
     def get_media(self, type_):
         if self.has_media(type_):
             if type_ in Book.formats:
@@ -212,6 +256,33 @@ class Book(models.Model):
     has_daisy_file.short_description = 'DAISY'
     has_daisy_file.boolean = True
 
     has_daisy_file.short_description = 'DAISY'
     has_daisy_file.boolean = True
 
+    def get_audiobooks(self):
+        ogg_files = {}
+        for m in self.media.filter(type='ogg').order_by().iterator():
+            ogg_files[m.name] = m
+
+        audiobooks = []
+        projects = set()
+        for mp3 in self.media.filter(type='mp3').iterator():
+            # ogg files are always from the same project
+            meta = mp3.extra_info
+            project = meta.get('project')
+            if not project:
+                # temporary fallback
+                project = u'CzytamySłuchając'
+
+            projects.add((project, meta.get('funded_by', '')))
+
+            media = {'mp3': mp3}
+
+            ogg = ogg_files.get(mp3.name)
+            if ogg:
+                media['ogg'] = ogg
+            audiobooks.append(media)
+
+        projects = sorted(projects)
+        return audiobooks, projects
+
     def wldocument(self, parse_dublincore=True, inherit=True):
         from catalogue.import_utils import ORMDocProvider
         from librarian.parser import WLDocument
     def wldocument(self, parse_dublincore=True, inherit=True):
         from catalogue.import_utils import ORMDocProvider
         from librarian.parser import WLDocument
@@ -336,6 +407,11 @@ class Book(models.Model):
 
         meta_tags = Tag.tags_from_info(book_info)
 
 
         meta_tags = Tag.tags_from_info(book_info)
 
+        for tag in meta_tags:
+            if not tag.for_books:
+                tag.for_books = True
+                tag.save()
+
         book.tags = set(meta_tags + book_shelves)
 
         cover_changed = old_cover != book.cover_info()
         book.tags = set(meta_tags + book_shelves)
 
         cover_changed = old_cover != book.cover_info()
@@ -369,6 +445,8 @@ class Book(models.Model):
         if 'cover' not in dont_build:
             book.cover.build_delay()
             book.cover_thumb.build_delay()
         if 'cover' not in dont_build:
             book.cover.build_delay()
             book.cover_thumb.build_delay()
+            book.cover_api_thumb.build_delay()
+            book.simple_cover.build_delay()
 
         # Build HTML and ebooks.
         book.html_file.build_delay()
 
         # Build HTML and ebooks.
         book.html_file.build_delay()
@@ -386,6 +464,7 @@ class Book(models.Model):
         for child in notify_cover_changed:
             child.parent_cover_changed()
 
         for child in notify_cover_changed:
             child.parent_cover_changed()
 
+        book.save()  # update sort_key_author
         cls.published.send(sender=cls, instance=book)
         return book
 
         cls.published.send(sender=cls, instance=book)
         return book
 
@@ -470,6 +549,8 @@ class Book(models.Model):
             if 'cover' not in app_settings.DONT_BUILD:
                 self.cover.build_delay()
                 self.cover_thumb.build_delay()
             if 'cover' not in app_settings.DONT_BUILD:
                 self.cover.build_delay()
                 self.cover_thumb.build_delay()
+                self.cover_api_thumb.build_delay()
+                self.simple_cover.build_delay()
             for format_ in constants.EBOOK_FORMATS_WITH_COVERS:
                 if format_ not in app_settings.DONT_BUILD:
                     getattr(self, '%s_file' % format_).build_delay()
             for format_ in constants.EBOOK_FORMATS_WITH_COVERS:
                 if format_ not in app_settings.DONT_BUILD:
                     getattr(self, '%s_file' % format_).build_delay()
@@ -499,6 +580,13 @@ class Book(models.Model):
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
+    def publisher(self):
+        publisher = self.extra_info['publisher']
+        if isinstance(publisher, basestring):
+            return publisher
+        elif isinstance(publisher, list):
+            return ', '.join(publisher)
+
     @classmethod
     def tagged_top_level(cls, tags):
         """ Returns top-level books tagged with `tags`.
     @classmethod
     def tagged_top_level(cls, tags):
         """ Returns top-level books tagged with `tags`.
@@ -552,6 +640,7 @@ class Book(models.Model):
         "SP": (1, u"szkoła podstawowa"),
         "SP1": (1, u"szkoła podstawowa"),
         "SP2": (1, u"szkoła podstawowa"),
         "SP": (1, u"szkoła podstawowa"),
         "SP1": (1, u"szkoła podstawowa"),
         "SP2": (1, u"szkoła podstawowa"),
+        "SP3": (1, u"szkoła podstawowa"),
         "P": (1, u"szkoła podstawowa"),
         "G": (2, u"gimnazjum"),
         "L": (3, u"liceum"),
         "P": (1, u"szkoła podstawowa"),
         "G": (2, u"gimnazjum"),
         "L": (3, u"liceum"),
@@ -584,6 +673,13 @@ class Book(models.Model):
         else:
             return None
 
         else:
             return None
 
+    def fragment_data(self):
+        fragment = self.choose_fragment()
+        if fragment:
+            return {'title': fragment.book.pretty_title(), 'html': fragment.get_short_text()}
+        else:
+            return None
+
     def update_popularity(self):
         count = self.tags.filter(category='set').values('user').order_by('user').distinct().count()
         try:
     def update_popularity(self):
         count = self.tags.filter(category='set').values('user').order_by('user').distinct().count()
         try:
@@ -593,6 +689,9 @@ class Book(models.Model):
         except BookPopularity.DoesNotExist:
             BookPopularity.objects.create(book=self, count=count)
 
         except BookPopularity.DoesNotExist:
             BookPopularity.objects.create(book=self, count=count)
 
+    def ridero_link(self):
+        return 'https://ridero.eu/%s/books/wl_%s/' % (get_language(), self.slug.replace('-', '_'))
+
 
 def add_file_fields():
     for format_ in Book.formats:
 
 def add_file_fields():
     for format_ in Book.formats:
@@ -616,4 +715,4 @@ add_file_fields()
 
 class BookPopularity(models.Model):
     book = models.OneToOneField(Book, related_name='popularity')
 
 class BookPopularity(models.Model):
     book = models.OneToOneField(Book, related_name='popularity')
-    count = models.IntegerField(default=0)
+    count = models.IntegerField(default=0, db_index=True)