X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/c1755a26b0576dab27d4eb72371a5b838d0c3116..e9cd7fc641895a7702a8f81fcd11c388dd7d9db7:/apps/catalogue/models.py diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 22cd0ea76..288a18906 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -2,7 +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 datetime import datetime +from collections import namedtuple from django.db import models from django.db.models import permalink, Q @@ -23,7 +23,8 @@ 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 create_zip +from catalogue.utils import create_zip, split_tags +from catalogue.tasks import touch_tag from shutil import copy from glob import glob import re @@ -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 @@ -67,7 +61,6 @@ class Tag(TagBase): category = models.CharField(_('category'), max_length=50, blank=False, null=False, db_index=True, choices=TAG_CATEGORIES) description = models.TextField(_('description'), blank=True) - main_page = models.BooleanField(_('main page'), default=False, db_index=True, help_text=_('Show tag on main page')) user = models.ForeignKey(User, blank=True, null=True) book_count = models.IntegerField(_('book count'), blank=True, null=True) @@ -112,25 +105,22 @@ class Tag(TagBase): has_description.boolean = True def get_count(self): - """ returns global book count for book tags, fragment count for themes """ - - if self.book_count is None: - if self.category == 'book': - # never used - objects = Book.objects.none() - elif self.category == 'theme': - objects = Fragment.tagged.with_all((self,)) - else: - objects = Book.tagged.with_all((self,)).order_by() - if self.category != 'set': - # eliminate descendants - l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects]) - descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)] - if descendants_keys: - objects = objects.exclude(pk__in=descendants_keys) - self.book_count = objects.count() - self.save() - return self.book_count + """Returns global book count for book tags, fragment count for themes.""" + + if self.category == 'book': + # never used + objects = Book.objects.none() + elif self.category == 'theme': + objects = Fragment.tagged.with_all((self,)) + else: + objects = Book.tagged.with_all((self,)).order_by() + if self.category != 'set': + # eliminate descendants + l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects]) + descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)] + if descendants_keys: + objects = objects.exclude(pk__in=descendants_keys) + return objects.count() @staticmethod def get_tag_list(tags): @@ -184,12 +174,16 @@ class Tag(TagBase): try: tag_names = getattr(info, field_name) except: - tag_names = [getattr(info, category)] + try: + tag_names = [getattr(info, category)] + except: + # For instance, Pictures do not have 'genre' field. + continue 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_name = tag_name.readable() tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category) if created: tag.name = tag_name @@ -205,10 +199,8 @@ def get_dynamic_path(media, filename, ext=None, maxlen=100): # 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: @@ -245,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) @@ -358,8 +359,11 @@ 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'] - + cover = models.FileField(_('cover'), upload_to=book_upload_path('png'), + null=True, blank=True) + 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) @@ -456,14 +460,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) @@ -486,6 +490,7 @@ class Book(models.Model): cache_key = "Book.short_html/%d/%s" for lang, langname in settings.LANGUAGES: cache.delete(cache_key % (self.id, lang)) + cache.delete("Book.mini_box/%d" % (self.id, )) # Fragment.short_html relies on book's tags, so reset it here too for fragm in self.fragments.all(): fragm.reset_short_html() @@ -500,24 +505,17 @@ class Book(models.Model): if short_html is not None: return mark_safe(short_html) else: - tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book'))) - tags = [mark_safe(u'%s' % (tag.get_absolute_url(), tag.name)) for tag in tags] + tags = self.tags.filter(category__in=('author', 'kind', 'genre', 'epoch')) + tags = split_tags(tags) formats = [] # files generated during publication - if self.has_media("html"): - formats.append(u'%s' % (reverse('book_text', args=[self.fileid()]), _('Read online'))) - if self.has_media("pdf"): - formats.append(u'PDF' % self.get_media('pdf').url) - if self.has_media("mobi"): - formats.append(u'MOBI' % self.get_media('mobi').url) - if self.root_ancestor.has_media("epub"): - formats.append(u'EPUB' % self.root_ancestor.get_media('epub').url) - if self.has_media("txt"): - formats.append(u'TXT' % self.get_media('txt').url) - # other files - for m in self.media.order_by('type'): - formats.append(u'%s' % (m.file.url, m.type.upper())) + for ebook_format in self.ebook_formats: + if self.has_media(ebook_format): + formats.append(u'%s' % ( + "", #self.get_media(ebook_format).url, + ebook_format.upper() + )) formats = [mark_safe(format) for format in formats] @@ -528,17 +526,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_box/%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 @@ -546,11 +549,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' @@ -574,6 +572,20 @@ class Book(models.Model): provider=ORMDocProvider(self), parse_dublincore=parse_dublincore) + def build_cover(self, book_info=None): + """(Re)builds the cover image.""" + from StringIO import StringIO + from django.core.files.base import ContentFile + from librarian.cover import WLCover + + if book_info is None: + book_info = self.wldocument().book_info + + cover = WLCover(book_info).image() + imgstr = StringIO() + cover.save(imgstr, 'png') + self.cover.save(None, ContentFile(imgstr.getvalue())) + def build_pdf(self, customizations=None, file_name=None): """ (Re)builds the pdf file. customizations - customizations which are passed to LaTeX class file. @@ -617,34 +629,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) @@ -811,26 +804,32 @@ class Book(models.Model): if not settings.NO_BUILD_TXT and build_txt: book.build_txt() + book.build_cover(book_info) + 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()) + descendants_tags = set() # add l-tag to descendants and their fragments - # delete unnecessary EPUB files while len(book_descendants) > 0: child_book = book_descendants.pop(0) + descendants_tags.update(child_book.tags) child_book.tags = list(child_book.tags) + [book_tag] child_book.save() for fragment in child_book.fragments.all(): fragment.tags = set(list(fragment.tags) + [book_tag]) book_descendants += list(child_book.children.all()) + for tag in descendants_tags: + touch_tag.delay(tag) + book.save() # refresh cache @@ -992,7 +991,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), @@ -1054,7 +1053,8 @@ class Fragment(models.Model): def _tags_updated_handler(sender, affected_tags, **kwargs): # reset tag global counter # we want Tag.changed_at updated for API to know the tag was touched - Tag.objects.filter(pk__in=[tag.pk for tag in affected_tags]).update(book_count=None, changed_at=datetime.now()) + for tag in affected_tags: + touch_tag.delay(tag) # if book tags changed, reset book tag counter if isinstance(sender, Book) and \