Merge branch 'picture' into pretty
[wolnelektury.git] / apps / catalogue / models.py
index 2ca78b0..2b33964 100644 (file)
@@ -2,6 +2,7 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
+from collections import namedtuple
 from datetime import datetime
 
 from django.db import models
@@ -40,13 +41,6 @@ TAG_CATEGORIES = (
     ('book', _('book')),
 )
 
-MEDIA_FORMATS = (
-    ('odt', _('ODT file')),
-    ('mp3', _('MP3 file')),
-    ('ogg', _('OGG file')),
-    ('daisy', _('DAISY file')), 
-)
-
 # not quite, but Django wants you to set a timeout
 CACHE_FOREVER = 2419200  # 28 days
 
@@ -174,16 +168,39 @@ class Tag(TagBase):
     def url_chunk(self):
         return '/'.join((Tag.categories_dict[self.category], self.slug))
 
+    @staticmethod
+    def tags_from_info(info):
+        from slughifi import slughifi
+        from sortify import sortify
+        meta_tags = []
+        categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
+        for field_name, category in categories:
+            try:
+                tag_names = getattr(info, field_name)
+            except:
+                tag_names = [getattr(info, category)]
+            for tag_name in tag_names:
+                tag_sort_key = tag_name
+                if category == 'author':
+                    tag_sort_key = tag_name.last_name
+                    tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
+                tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
+                if created:
+                    tag.name = tag_name
+                    tag.sort_key = sortify(tag_sort_key.lower())
+                    tag.save()
+                meta_tags.append(tag)
+        return meta_tags
+
+
 
 def get_dynamic_path(media, filename, ext=None, maxlen=100):
     from slughifi import slughifi
 
     # how to put related book's slug here?
     if not ext:
-        if media.type == 'daisy':
-            ext = 'daisy.zip'
-        else:
-            ext = media.type
+        # BookMedia case
+        ext = media.formats[media.type].ext
     if media is None or not media.name:
         name = slughifi(filename.split(".")[0])
     else:
@@ -202,8 +219,10 @@ def get_customized_pdf_path(book, customizations):
     """
     customizations.sort()
     h = hash(tuple(customizations))
+
     pdf_name = '%s-custom-%s' % (book.fileid(), h)
-    pdf_file = models.get_dynamic_path(None, pdf_name, ext='pdf')
+    pdf_file = get_dynamic_path(None, pdf_name, ext='pdf')
+
     return pdf_file
 
 
@@ -218,7 +237,16 @@ def get_existing_customized_pdf(book):
 
 
 class BookMedia(models.Model):
-    type        = models.CharField(_('type'), choices=MEDIA_FORMATS, max_length="100")
+    FileFormat = namedtuple("FileFormat", "name ext")
+    formats = SortedDict([
+        ('mp3', FileFormat(name='MP3', ext='mp3')),
+        ('ogg', FileFormat(name='Ogg Vorbis', ext='ogg')),
+        ('daisy', FileFormat(name='DAISY', ext='daisy.zip')),
+    ])
+    format_choices = [(k, _('%s file') % t.name)
+            for k, t in formats.items()]
+
+    type        = models.CharField(_('type'), choices=format_choices, max_length="100")
     name        = models.CharField(_('name'), max_length="100")
     file        = OverwritingFileField(_('file'), upload_to=book_upload_path())
     uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False)
@@ -331,8 +359,9 @@ class Book(models.Model):
     wiki_link     = models.CharField(blank=True, max_length=240)
     # files generated during publication
 
-    file_types = ['epub', 'html', 'mobi', 'pdf', 'txt', 'xml']
-    
+    ebook_formats = ['pdf', 'epub', 'mobi', 'txt']
+    formats = ebook_formats + ['html', 'xml']
+
     parent        = models.ForeignKey('self', blank=True, null=True, related_name='children')
     objects  = models.Manager()
     tagged   = managers.ModelTaggedItemManager(Tag)
@@ -429,14 +458,14 @@ class Book(models.Model):
         return book_tag
 
     def has_media(self, type):
-        if type in Book.file_types:
+        if type in Book.formats:
             return bool(getattr(self, "%s_file" % type))
         else:
             return self.media.filter(type=type).exists()
 
     def get_media(self, type):
         if self.has_media(type):
-            if type in Book.file_types:
+            if type in Book.formats:
                 return getattr(self, "%s_file" % type)
             else:                                             
                 return self.media.filter(type=type)
@@ -479,15 +508,13 @@ class Book(models.Model):
             formats = []
             # files generated during publication
             if self.has_media("html"):
-                formats.append(u'<a href="%s">%s</a>' % (reverse('book_text', [self.fileid()]), _('Read online')))
-            if self.has_media("pdf"):
-                formats.append(u'<a href="%s">PDF</a>' % self.get_media('pdf').url)
-            if self.has_media("mobi"):
-                formats.append(u'<a href="%s">MOBI</a>' % self.get_media('mobi').url)
-            if self.root_ancestor.has_media("epub"):
-                formats.append(u'<a href="%s">EPUB</a>' % self.root_ancestor.get_media('epub').url)
-            if self.has_media("txt"):
-                formats.append(u'<a href="%s">TXT</a>' % self.get_media('txt').url)
+                formats.append(u'<a href="%s">%s</a>' % (reverse('book_text', args=[self.fileid()]), _('Read online')))
+            for ebook_format in self.ebook_formats:
+                if self.has_media(ebook_format):
+                    formats.append(u'<a href="%s">%s</a>' % (
+                        self.get_media(ebook_format).url,
+                        ebook_format.upper()
+                    ))
             # other files
             for m in self.media.order_by('type'):
                 formats.append(u'<a href="%s">%s</a>' % (m.file.url, m.type.upper()))
@@ -501,17 +528,22 @@ class Book(models.Model):
                 cache.set(cache_key, short_html, CACHE_FOREVER)
             return mark_safe(short_html)
 
-    @property
-    def root_ancestor(self):
-        """ returns the oldest ancestor """
+    def mini_box(self):
+        if self.id:
+            cache_key = "Book.mini_boxs/%d" % (self.id, )
+            short_html = cache.get(cache_key)
+        else:
+            short_html = None
+
+        if short_html is None:
+            authors = self.tags.filter(category='author')
 
-        if not hasattr(self, '_root_ancestor'):
-            book = self
-            while book.parent:
-                book = book.parent
-            self._root_ancestor = book
-        return self._root_ancestor
+            short_html = unicode(render_to_string('catalogue/book_mini_box.html',
+                {'book': self, 'authors': authors, 'STATIC_URL': settings.STATIC_URL}))
 
+            if self.id:
+                cache.set(cache_key, short_html, CACHE_FOREVER)
+        return mark_safe(short_html)
 
     def has_description(self):
         return len(self.description) > 0
@@ -519,11 +551,6 @@ class Book(models.Model):
     has_description.boolean = True
 
     # ugly ugly ugly
-    def has_odt_file(self):
-        return bool(self.has_media("odt"))
-    has_odt_file.short_description = 'ODT'
-    has_odt_file.boolean = True
-
     def has_mp3_file(self):
         return bool(self.has_media("mp3"))
     has_mp3_file.short_description = 'MP3'
@@ -565,16 +592,17 @@ class Book(models.Model):
             current_self.pdf_file.save('%s.pdf' % self.fileid(),
                     File(open(pdf.get_filename())))
             self.pdf_file = current_self.pdf_file
+
+            # remove cached downloadables
+            remove_zip(settings.ALL_PDF_ZIP)
+
+            for customized_pdf in get_existing_customized_pdf(self):
+                unlink(customized_pdf)
         else:
             print "saving %s" % file_name
             print "to: %s" % DefaultStorage().path(file_name)
             DefaultStorage().save(file_name, File(open(pdf.get_filename())))
 
-        # remove cached downloadables
-        remove_zip(settings.ALL_PDF_ZIP)
-        for customized_pdf in get_existing_customized_pdf(self):
-            unlink(customized_pdf)
-
     def build_mobi(self):
         """ (Re)builds the MOBI file.
 
@@ -589,34 +617,15 @@ class Book(models.Model):
         # remove zip with all mobi files
         remove_zip(settings.ALL_MOBI_ZIP)
 
-    def build_epub(self, remove_descendants=True):
-        """ (Re)builds the epub file.
-            If book has a parent, does nothing.
-            Unless remove_descendants is False, descendants' epubs are removed.
-        """
+    def build_epub(self):
+        """(Re)builds the epub file."""
         from django.core.files import File
         from catalogue.utils import remove_zip
 
-        if self.parent:
-            # don't need an epub
-            return
-
         epub = self.wldocument().as_epub()
 
-        try:
-            epub.transform(ORMDocProvider(self), self.fileid(), output_file=epub_file)
-            self.epub_file.save('%s.epub' % self.fileid(), File(open(epub.get_filename())))
-        except NoDublinCore:
-            pass
-
-        book_descendants = list(self.children.all())
-        while len(book_descendants) > 0:
-            child_book = book_descendants.pop(0)
-            if remove_descendants and child_book.has_epub_file():
-                child_book.epub_file.delete()
-            # save anyway, to refresh short_html
-            child_book.save()
-            book_descendants += list(child_book.children.all())
+        self.epub_file.save('%s.epub' % self.fileid(),
+                File(open(epub.get_filename())))
 
         # remove zip package with all epub files
         remove_zip(settings.ALL_EPUB_ZIP)
@@ -728,7 +737,6 @@ class Book(models.Model):
     def from_text_and_meta(cls, raw_file, book_info, overwrite=False,
             build_epub=True, build_txt=True, build_pdf=True, build_mobi=True):
         import re
-        from slughifi import slughifi
         from sortify import sortify
 
         # check for parts before we do anything
@@ -763,24 +771,7 @@ class Book(models.Model):
         book.set_extra_info_value(book_info.to_dict())
         book.save()
 
-        meta_tags = []
-        categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch'))
-        for field_name, category in categories:
-            try:
-                tag_names = getattr(book_info, field_name)
-            except:
-                tag_names = [getattr(book_info, category)]
-            for tag_name in tag_names:
-                tag_sort_key = tag_name
-                if category == 'author':
-                    tag_sort_key = tag_name.last_name
-                    tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
-                tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category)
-                if created:
-                    tag.name = tag_name
-                    tag.sort_key = sortify(tag_sort_key.lower())
-                    tag.save()
-                meta_tags.append(tag)
+        meta_tags = Tag.tags_from_info(book_info)
 
         book.tags = set(meta_tags + book_shelves)
 
@@ -802,17 +793,16 @@ class Book(models.Model):
                 book.build_txt()
 
         if not settings.NO_BUILD_EPUB and build_epub:
-            book.root_ancestor.build_epub()
+            book.build_epub()
 
         if not settings.NO_BUILD_PDF and build_pdf:
-            book.root_ancestor.build_pdf()
+            book.build_pdf()
 
         if not settings.NO_BUILD_MOBI and build_mobi:
             book.build_mobi()
 
         book_descendants = list(book.children.all())
         # add l-tag to descendants and their fragments
-        # delete unnecessary EPUB files
         while len(book_descendants) > 0:
             child_book = book_descendants.pop(0)
             child_book.tags = list(child_book.tags) + [book_tag]
@@ -982,7 +972,7 @@ def _has_factory(ftype):
 
     
 # add the file fields
-for t in Book.file_types:
+for t in Book.formats:
     field_name = "%s_file" % t
     models.FileField(_("%s file" % t.upper()),
             upload_to=book_upload_path(t),