initial mobi format support (with a really awful icon)
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 27 Oct 2011 13:54:29 +0000 (15:54 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 27 Oct 2011 14:27:53 +0000 (16:27 +0200)
some format generalizations
minor fixes

15 files changed:
apps/api/handlers.py
apps/catalogue/forms.py
apps/catalogue/management/commands/importbooks.py
apps/catalogue/management/commands/pack.py
apps/catalogue/migrations/0016_auto__add_field_book_mobi_file.py [new file with mode: 0644]
apps/catalogue/models.py
apps/catalogue/test_utils.py
apps/catalogue/urls.py
apps/catalogue/utils.py
apps/catalogue/views.py
lib/librarian
wolnelektury/settings.py
wolnelektury/static/img/mobi.png [new file with mode: 0644]
wolnelektury/templates/catalogue/book_detail.html
wolnelektury/templates/catalogue/tagged_object_list.html

index 41b5ac6..8f06dea 100644 (file)
@@ -93,8 +93,7 @@ class BookDetailHandler(BaseHandler):
 
     """
     allowed_methods = ['GET']
-    fields = ['title', 'parent',
-        'xml', 'html', 'pdf', 'epub', 'txt',
+    fields = ['title', 'parent'] + Book.file_types + [
         'media', 'url'] + category_singular.keys()
 
     @piwik_track
@@ -203,7 +202,7 @@ def _file_getter(format):
         else:
             return ''
     return get_file
-for format in ('xml', 'txt', 'html', 'epub', 'pdf'):
+for format in Book.file_types:
     setattr(BooksHandler, format, _file_getter(format))
 
 
@@ -353,16 +352,16 @@ class CatalogueHandler(BaseHandler):
 
     @staticmethod
     def book_dict(book, fields=None):
-        all_fields = ('url', 'title', 'description',
+        all_fields = ['url', 'title', 'description',
                       'gazeta_link', 'wiki_link',
-                      'xml', 'epub', 'txt', 'pdf', 'html',
+                      ] + Book.file_types + [
                       'mp3', 'ogg', 'daisy',
                       'parent', 'parent_number',
                       'tags',
                       'license', 'license_description', 'source_name',
                       'technical_editors', 'editors',
                       'author', 'sort_key',
-                     )
+                     ]
         if fields:
             fields = (f for f in fields if f in all_fields)
         else:
@@ -373,7 +372,7 @@ class CatalogueHandler(BaseHandler):
         obj = {}
         for field in fields:
 
-            if field in ('xml', 'epub', 'txt', 'pdf', 'html'):
+            if field in Book.file_types:
                 f = getattr(book, field+'_file')
                 if f:
                     obj[field] = {
index 2bf974d..04969c2 100644 (file)
@@ -85,6 +85,7 @@ FORMATS = (
     ('txt', 'TXT'),
     ('epub', 'EPUB'),
     ('daisy', 'DAISY'),
+    ('mobi', 'MOBI'),
 )
 
 
index f51214d..ecd3fcc 100644 (file)
@@ -22,6 +22,8 @@ class Command(BaseCommand):
             help='Print status messages to stdout'),
         make_option('-E', '--no-build-epub', action='store_false', dest='build_epub', default=True,
             help='Don\'t build EPUB file'),
+        make_option('-M', '--no-build-mobi', action='store_false', dest='build_mobi', default=True,
+            help='Don\'t build MOBI file'),
         make_option('-T', '--no-build-txt', action='store_false', dest='build_txt', default=True,
             help='Don\'t build TXT file'),
         make_option('-P', '--no-build-pdf', action='store_false', dest='build_pdf', default=True,
@@ -84,13 +86,18 @@ class Command(BaseCommand):
                         book = Book.from_xml_file(file_path, overwrite=force, 
                                                   build_epub=options.get('build_epub'),
                                                   build_txt=options.get('build_txt'),
-                                                  build_pdf=options.get('build_pdf'))
+                                                  build_pdf=options.get('build_pdf'),
+                                                  build_mobi=options.get('build_mobi'))
                         files_imported += 1
 
                         if os.path.isfile(file_base + '.pdf'):
                             book.pdf_file.save('%s.pdf' % book.slug, File(file(file_base + '.pdf')))
                             if verbose:
                                 print "Importing %s.pdf" % file_base
+                        if os.path.isfile(file_base + '.mobi'):
+                            book.mobi_file.save('%s.mobi' % book.slug, File(file(file_base + '.mobi')))
+                            if verbose:
+                                print "Importing %s.mobi" % file_base
                         if os.path.isfile(file_base + '.epub'):
                             book.epub_file.save('%s.epub' % book.slug, File(file(file_base + '.epub')))
                             if verbose:
index a300aff..c75f092 100755 (executable)
@@ -24,7 +24,6 @@ class Command(BaseCommand):
             help='Exclude specific books by slug')
     )
     help = 'Prepare data for Lesmianator.'
-    ftypes = ['xml', 'txt', 'html', 'epub', 'pdf']
     args = '[%s] output_path.zip' % '|'.join(ftypes)
 
     def handle(self, ftype, path, **options):
@@ -34,7 +33,7 @@ class Command(BaseCommand):
         include = options.get('include')
         exclude = options.get('exclude')
 
-        if ftype in self.ftypes:
+        if ftype in Book.file_types:
             field = "%s_file" % ftype
         else:
             print self.style.ERROR('Unknown file type.')
diff --git a/apps/catalogue/migrations/0016_auto__add_field_book_mobi_file.py b/apps/catalogue/migrations/0016_auto__add_field_book_mobi_file.py
new file mode 100644 (file)
index 0000000..87faf6b
--- /dev/null
@@ -0,0 +1,131 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'Book.mobi_file'
+        db.add_column('catalogue_book', 'mobi_file', self.gf('django.db.models.fields.files.FileField')(default='', max_length=100, blank=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'Book.mobi_file'
+        db.delete_column('catalogue_book', 'mobi_file')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.filerecord': {
+            'Meta': {'ordering': "('-time', '-slug', '-type')", 'object_name': 'FileRecord'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'sha1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '20', 'db_index': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
index e4d3595..565b759 100644 (file)
@@ -22,7 +22,7 @@ from django.conf import settings
 from newtagging.models import TagBase, tags_updated
 from newtagging import managers
 from catalogue.fields import JSONField, OverwritingFileField
-from catalogue.utils import ExistingFile, BookImportDocProvider, create_zip_task, remove_zip
+from catalogue.utils import ExistingFile, ORMDocProvider, create_zip_task, remove_zip
 
 from librarian import dcparser, html, epub, NoDublinCore
 import mutagen
@@ -296,11 +296,8 @@ class Book(models.Model):
     gazeta_link   = models.CharField(blank=True, max_length=240)
     wiki_link     = models.CharField(blank=True, max_length=240)
     # files generated during publication
-    xml_file      = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True)
-    html_file     = models.FileField(_('HTML file'), upload_to=book_upload_path('html'), blank=True)
-    pdf_file      = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
-    epub_file     = models.FileField(_('EPUB file'), upload_to=book_upload_path('epub'), blank=True)
-    txt_file      = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)
+
+    file_types = ['epub', 'html', 'mobi', 'pdf', 'txt', 'xml']
     
     parent        = models.ForeignKey('self', blank=True, null=True, related_name='children')
     objects  = models.Manager()
@@ -351,49 +348,15 @@ class Book(models.Model):
         return book_tag
 
     def has_media(self, type):
-        if   type == 'xml':
-            if self.xml_file:
-                return True
-            else:
-                return False
-        elif type == 'html':
-            if self.html_file:
-                return True
-            else:
-                return False        
-        elif type == 'txt':
-            if self.txt_file:
-                return True
-            else:
-                return False        
-        elif type == 'pdf':
-            if self.pdf_file:
-                return True
-            else:
-                return False  
-        elif type == 'epub':
-            if self.epub_file:
-                return True
-            else:
-                return False                          
+        if type in Book.file_types:
+            return bool(getattr(self, "%s_file" % type))
         else:
-            if self.media.filter(type=type).exists():
-                return True
-            else:
-                return False
+            return self.media.filter(type=type).exists()
 
     def get_media(self, type):
         if self.has_media(type):
-            if   type == "xml":
-                return self.xml_file
-            elif type == "html":
-                return self.html_file
-            elif type == "epub":
-                return self.epub_file
-            elif type == "txt":
-                return self.txt_file
-            elif type == "pdf":
-                return self.pdf_file
+            if type in Book.file_types:
+                return getattr(self, "%s_file" % type)
             else:                                             
                 return self.media.filter(type=type)
         else:
@@ -433,11 +396,13 @@ class Book(models.Model):
             tags = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)) for tag in tags]
 
             formats = []
-            # files generated during publication               
+            # files generated during publication
             if self.has_media("html"):
                 formats.append(u'<a href="%s">%s</a>' % (reverse('book_text', kwargs={'slug': self.slug}), _('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"):
@@ -473,26 +438,6 @@ class Book(models.Model):
     has_description.boolean = True
 
     # ugly ugly ugly
-    def has_pdf_file(self):
-        return bool(self.pdf_file)
-    has_pdf_file.short_description = 'PDF'
-    has_pdf_file.boolean = True
-
-    def has_epub_file(self):
-        return bool(self.epub_file)
-    has_epub_file.short_description = 'EPUB'
-    has_epub_file.boolean = True
-
-    def has_txt_file(self):
-        return bool(self.txt_file)
-    has_txt_file.short_description = 'HTML'
-    has_txt_file.boolean = True
-
-    def has_html_file(self):
-        return bool(self.html_file)
-    has_html_file.short_description = 'HTML'
-    has_html_file.boolean = True
-
     def has_odt_file(self):
         return bool(self.has_media("odt"))
     has_odt_file.short_description = 'ODT'
@@ -524,10 +469,9 @@ class Book(models.Model):
         # remove zip with all pdf files
         remove_zip(settings.ALL_PDF_ZIP)
 
-        path, fname = os.path.realpath(self.xml_file.path).rsplit('/', 1)
         try:
             pdf_file = NamedTemporaryFile(delete=False)
-            pdf.transform(BookImportDocProvider(self),
+            pdf.transform(ORMDocProvider(self),
                       file_path=str(self.xml_file.path),
                       output_file=pdf_file,
                       )
@@ -536,6 +480,28 @@ class Book(models.Model):
         finally:
             unlink(pdf_file.name)
 
+    def build_mobi(self):
+        """ (Re)builds the MOBI file.
+
+        """
+        from librarian import mobi
+        from tempfile import NamedTemporaryFile
+        import os
+
+        # remove zip with all pdf files
+        remove_zip(settings.ALL_MOBI_ZIP)
+
+        try:
+            mobi_file = NamedTemporaryFile(suffix='.mobi', delete=False)
+            mobi.transform(ORMDocProvider(self), verbose=1,
+                      file_path=str(self.xml_file.path),
+                      output_file=mobi_file.name,
+                      )
+
+            self.mobi_file.save('%s.mobi' % self.slug, File(open(mobi_file.name)))
+        finally:
+            unlink(mobi_file.name)
+
     def build_epub(self, remove_descendants=True):
         """ (Re)builds the epub file.
             If book has a parent, does nothing.
@@ -554,7 +520,7 @@ class Book(models.Model):
 
         epub_file = StringIO()
         try:
-            epub.transform(BookImportDocProvider(self), self.slug, output_file=epub_file)
+            epub.transform(ORMDocProvider(self), self.slug, output_file=epub_file)
             self.epub_file.save('%s.epub' % self.slug, ContentFile(epub_file.getvalue()))
             FileRecord(slug=self.slug, type='epub', sha1=sha1(epub_file.getvalue()).hexdigest()).save()
         except NoDublinCore:
@@ -660,6 +626,15 @@ class Book(models.Model):
             result = create_zip_task(paths, settings.ALL_PDF_ZIP)
             return result
 
+    @staticmethod
+    def zip_mobi():
+        books = Book.objects.all()
+
+        paths = filter(lambda x: x is not None,
+                       map(lambda b: b.mobi_file and b.mobi_file.path or None, books))
+        result = create_zip_task.delay(paths, settings.ALL_MOBI_ZIP)
+        return settings.MEDIA_URL + result.wait()
+
     def zip_audiobooks(self):
         bm = BookMedia.objects.filter(book=self)
         paths = map(lambda bm: bm.file.path, bm)
@@ -670,7 +645,6 @@ class Book(models.Model):
             result = create_zip_task(paths, self.slug)
             return result
 
-
     @classmethod
     def from_xml_file(cls, xml_file, **kwargs):
         # use librarian to parse meta-data
@@ -685,7 +659,8 @@ class Book(models.Model):
             xml_file.close()
 
     @classmethod
-    def from_text_and_meta(cls, raw_file, book_info, overwrite=False, build_epub=True, build_txt=True, build_pdf=True):
+    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
 
         # check for parts before we do anything
@@ -761,6 +736,9 @@ class Book(models.Model):
         if not settings.NO_BUILD_PDF and build_pdf:
             book.root_ancestor.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
@@ -872,6 +850,24 @@ class Book(models.Model):
         return objects
 
 
+def _has_factory(ftype):
+    has = lambda self: bool(getattr(self, "%s_file" % ftype))
+    has.short_description = t.upper()
+    has.boolean = True
+    has.__name__ = "has_%s_file" % ftype
+    return has
+
+    
+# add the file fields
+for t in Book.file_types:
+    field_name = "%s_file" % t
+    models.FileField(_("%s file" % t.upper()),
+            upload_to=book_upload_path(t),
+            blank=True).contribute_to_class(Book, field_name)
+
+    setattr(Book, "has_%s_file" % t, _has_factory(t))
+
+
 class Fragment(models.Model):
     text = models.TextField()
     short_text = models.TextField(editable=False)
index 7905efb..48e9c90 100644 (file)
@@ -10,7 +10,8 @@ class WLTestCase(TestCase):
     """
     def setUp(self):
         self._MEDIA_ROOT, settings.MEDIA_ROOT = settings.MEDIA_ROOT, tempfile.mkdtemp(prefix='djangotest_')
-        settings.NO_BUILD_PDF = settings.NO_BUILD_EPUB = settings.NO_BUILD_TXT = True
+        settings.NO_BUILD_PDF = settings.NO_BUILD_MOBI = settings.NO_BUILD_EPUB = settings.NO_BUILD_TXT = True
+        settings.USE_CELERY = False
 
     def tearDown(self):
         shutil.rmtree(settings.MEDIA_ROOT, True)
index 7fd265f..fbb1618 100644 (file)
@@ -25,6 +25,7 @@ urlpatterns = patterns('catalogue.views',
     # zip
     url(r'^zip/pdf/.*\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'),
     url(r'^zip/epub/.*\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'),
+    url(r'^zip/mobi/.*\.zip$', 'download_zip', {'format': 'mobi', 'slug': None}, 'download_zip_mobi'),
     url(r'^zip/audiobook/(?P<slug>[a-zA-Z0-9-]+)\.zip', 'download_zip', {'format': 'audiobook'}, 'download_zip_audiobook'),
 
     # tools
index 07458c9..3a8d1b0 100644 (file)
@@ -55,8 +55,8 @@ class ExistingFile(UploadedFile):
         pass
 
 
-class BookImportDocProvider(DocProvider):
-    """Used for joined EPUB and PDF files."""
+class ORMDocProvider(DocProvider):
+    """Used for getting books' children."""
 
     def __init__(self, book):
         self.book = book
index 90ef8c2..9168099 100644 (file)
@@ -606,7 +606,7 @@ def download_shelf(request, slug):
     if form.is_valid():
         formats = form.cleaned_data['formats']
     if len(formats) == 0:
-        formats = ['pdf', 'epub', 'odt', 'txt']
+        formats = ['pdf', 'epub', 'mobi', 'odt', 'txt']
 
     # Create a ZIP archive
     temp = tempfile.TemporaryFile()
@@ -617,6 +617,9 @@ def download_shelf(request, slug):
         if 'pdf' in formats and book.pdf_file:
             filename = book.pdf_file.path
             archive.write(filename, str('%s.pdf' % book.slug))
+        if 'mobi' in formats and book.mobi_file:
+            filename = book.mobi_file.path
+            archive.write(filename, str('%s.mobi' % book.slug))
         if book.root_ancestor not in already and 'epub' in formats and book.root_ancestor.epub_file:
             filename = book.root_ancestor.epub_file.path
             archive.write(filename, str('%s.epub' % book.root_ancestor.slug))
@@ -646,13 +649,15 @@ def shelf_book_formats(request, shelf):
     """
     shelf = get_object_or_404(models.Tag, slug=shelf, category='set')
 
-    formats = {'pdf': False, 'epub': False, 'odt': False, 'txt': False}
+    formats = {'pdf': False, 'epub': False, 'mobi': False, 'odt': False, 'txt': False}
 
     for book in collect_books(models.Book.tagged.with_all(shelf)):
         if book.pdf_file:
             formats['pdf'] = True
         if book.root_ancestor.epub_file:
             formats['epub'] = True
+        if book.mobi_file:
+            formats['mobi'] = True
         if book.txt_file:
             formats['txt'] = True
         for format in ('odt',):
@@ -780,6 +785,8 @@ def download_zip(request, format, slug):
         url = models.Book.zip_pdf()
     elif format == 'epub':
         url = models.Book.zip_epub()
+    elif format == 'mobi':
+        url = models.Book.zip_mobi()
     elif format == 'audiobook' and slug is not None:
         book = models.Book.objects.get(slug=slug)
         url = book.zip_audiobooks()
index a6ee2dd..f844298 160000 (submodule)
@@ -1 +1 @@
-Subproject commit a6ee2dd83d3c4d5d2d3e8cb3401734ced2b12c22
+Subproject commit f8442984a021793164661a54ec84ae9caaf9862b
index 826d111..0b9fcdf 100644 (file)
@@ -227,9 +227,11 @@ MAX_TAG_LIST = 6
 NO_BUILD_EPUB = False
 NO_BUILD_TXT = False
 NO_BUILD_PDF = False
+NO_BUILD_MOBI = False
 
 ALL_EPUB_ZIP = 'wolnelektury_pl_epub'
 ALL_PDF_ZIP = 'wolnelektury_pl_pdf'
+ALL_MOBI_ZIP = 'wolnelektury_pl_mobi'
 
 PAGINATION_INVALID_PAGE_RAISES_404 = True
 
diff --git a/wolnelektury/static/img/mobi.png b/wolnelektury/static/img/mobi.png
new file mode 100644 (file)
index 0000000..b278ec2
Binary files /dev/null and b/wolnelektury/static/img/mobi.png differ
index 11f16d0..ff7b519 100644 (file)
                     <a class="online" href="{% url book_text book.slug %}">{% trans "Read online" %}</a>
                 {% endif %}
                 <div class="download">
-                    {% if book.has_pdf_file %}
+                    {% if book.pdf_file %}
                         <a href="{{ book.pdf_file.url }}"><img src="{{ STATIC_URL }}img/pdf.png" title="{% trans "Download PDF" %} &ndash; {% trans "for reading" %} {% trans "and printing using" %} Adobe Reader" %}" alt="{% trans "Download PDF" %}" /></a>
                     {% endif %}
                     {% if book.root_ancestor.epub_file %}
                         <a href="{{ book.root_ancestor.epub_file.url }}"><img src="{{ STATIC_URL }}img/epub.png" title="{% trans "Download EPUB" %} &ndash; {% trans "for reading" %} {% trans "on mobile devices" %}" alt="{% trans "Download EPUB" %}" /></a>
                     {% endif %}
-                    {% if book.has_txt_file %}
+                    {% if book.mobi_file %}
+                        <a href="{{ book.mobi_file.url }}"><img src="{{ STATIC_URL }}img/mobi.png" title="{% trans "Download MOBI" %} &ndash; {% trans "for reading" %} {% trans "on mobile devices" %}" alt="{% trans "Download MOBI" %}" /></a>
+                    {% endif %}
+                    {% if book.txt_file %}
                         <a href="{{ book.txt_file.url }}"><img src="{{ STATIC_URL }}img/txt.png" title="{% trans "Download TXT" %} &ndash; {% trans "for reading" %} {% trans "on small displays, for example mobile phones" %}" alt="{% trans "Download TXT" %}" /></a>
                     {% endif %}
                     {% for media in book.get_odt %}
index 4809ad1..fec9105 100644 (file)
@@ -36,6 +36,7 @@
                     <p>{% trans "Choose books' formats which you want to download:" %}</p>
                     <li data-format="pdf"><label for="id_formats_2"><input type="checkbox" name="formats" value="pdf" id="id_formats_2" /> PDF</label> <em><strong>{% trans "for reading" %}</strong> {% trans "and printing using" %} <a href="http://get.adobe.com/reader/">Adobe Reader</a></em></li>
                     <li data-format="epub"><label for="id_formats_5"><input type="checkbox" name="formats" value="epub" id="id_formats_5" /> EPUB</label> <em><strong>{% trans "for reading" %}</strong> {% trans "on mobile devices" %}</em></li>
+                    <li data-format="mobi"><label for="id_formats_7"><input type="checkbox" name="formats" value="mobi" id="id_formats_7" /> MOBI</label> <em><strong>{% trans "for reading" %}</strong> {% trans "on mobile devices" %}</em></li>
                     <li data-format="odt"><label for="id_formats_3"><input type="checkbox" name="formats" value="odt" id="id_formats_3" /> ODT</label> <em><strong>{% trans "for reading" %}</strong> {% trans "and editing using" %} <a href="http://pl.openoffice.org/">OpenOffice.org</a></em></li>
                     <li data-format="txt"><label for="id_formats_4"><input type="checkbox" name="formats" value="txt" id="id_formats_4" /> TXT</label> <em><strong>{% trans "for reading" %}</strong> {% trans "on small displays, for example mobile phones" %}</em></li>
                     <li data-format="mp3"><label for="id_formats_0"><input type="checkbox" name="formats" value="mp3" id="id_formats_0" /> MP3</label> <em><strong>{% trans "for listening" %}</strong> {% trans "on favourite MP3 player" %}</em></li>