X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/4157358510703a54cde8f3b0f9814f2cd1c9f40a..dbe0a80a192f561f29f917cd741b78329834a89f:/src/catalogue/models/book.py diff --git a/src/catalogue/models/book.py b/src/catalogue/models/book.py index fc07fc5f7..10e9d22e3 100644 --- a/src/catalogue/models/book.py +++ b/src/catalogue/models/book.py @@ -1,5 +1,5 @@ -# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Wolne Lektury. See NOTICE for more information. # from collections import OrderedDict import json @@ -15,11 +15,12 @@ import django.dispatch from django.contrib.contenttypes.fields import GenericRelation from django.template.loader import render_to_string from django.urls import reverse -from django.utils.translation import ugettext_lazy as _, get_language +from django.utils.translation import gettext_lazy as _, get_language from fnpdjango.storage import BofhFileSystemStorage from lxml import html from librarian.cover import WLCover from librarian.html import transform_abstrakt +from librarian.builders import builders from newtagging import managers from catalogue import constants from catalogue import fields @@ -34,29 +35,29 @@ bofh_storage = BofhFileSystemStorage() class Book(models.Model): """Represents a book imported from WL-XML.""" - title = models.CharField(_('title'), max_length=32767) - sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False) + title = models.CharField('tytuł', max_length=32767) + sort_key = models.CharField('klucz sortowania', max_length=120, db_index=True, editable=False) sort_key_author = models.CharField( - _('sort key by author'), max_length=120, db_index=True, editable=False, default='') - slug = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True) - common_slug = models.SlugField(_('slug'), max_length=120, 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) - abstract = models.TextField(_('abstract'), blank=True) - toc = models.TextField(_('toc'), blank=True) - created_at = models.DateTimeField(_('creation date'), auto_now_add=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 = models.TextField(_('extra information'), default='{}') + 'klucz sortowania wg autora', max_length=120, db_index=True, editable=False, default='') + slug = models.SlugField('slug', max_length=120, db_index=True, unique=True) + common_slug = models.SlugField('wspólny slug', max_length=120, db_index=True) + language = models.CharField('kod języka', max_length=3, db_index=True, default=app_settings.DEFAULT_LANGUAGE) + description = models.TextField('opis', blank=True) + abstract = models.TextField('abstrakt', blank=True) + toc = models.TextField('spis treści', blank=True) + created_at = models.DateTimeField('data utworzenia', auto_now_add=True, db_index=True) + changed_at = models.DateTimeField('data motyfikacji', auto_now=True, db_index=True) + parent_number = models.IntegerField('numer w ramach rodzica', default=0) + extra_info = models.TextField('dodatkowe informacje', 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) - audio_length = models.CharField(_('audio length'), blank=True, max_length=8) - preview = models.BooleanField(_('preview'), default=False) - preview_until = models.DateField(_('preview until'), blank=True, null=True) + print_on_demand = models.BooleanField('druk na żądanie', default=False) + recommended = models.BooleanField('polecane', default=False) + audio_length = models.CharField('długość audio', blank=True, max_length=8) + preview = models.BooleanField('prapremiera', default=False) + preview_until = models.DateField('prapremiera do', blank=True, null=True) preview_key = models.CharField(max_length=32, blank=True, null=True) - findable = models.BooleanField(_('findable'), default=True, db_index=True) + findable = models.BooleanField('wyszukiwalna', default=True, db_index=True) # files generated during publication xml_file = fields.XmlField(storage=bofh_storage, with_etag=False) @@ -67,15 +68,15 @@ class Book(models.Model): mobi_file = fields.MobiField(storage=bofh_storage) pdf_file = fields.PdfField(storage=bofh_storage) - cover = fields.CoverField(_('cover'), storage=bofh_storage) + cover = fields.CoverField('okładka', storage=bofh_storage) # Cleaner version of cover for thumbs - cover_clean = fields.CoverCleanField(_('clean cover')) - cover_thumb = fields.CoverThumbField(_('cover thumbnail')) + cover_clean = fields.CoverCleanField('czysta okładka') + cover_thumb = fields.CoverThumbField('miniatura okładki') cover_api_thumb = fields.CoverApiThumbField( - _('cover thumbnail for mobile app')) - simple_cover = fields.SimpleCoverField(_('cover for mobile app')) + 'mniaturka okładki dla aplikacji') + simple_cover = fields.SimpleCoverField('okładka dla aplikacji') cover_ebookpoint = fields.CoverEbookpointField( - _('cover for Ebookpoint')) + 'okładka dla Ebookpoint') ebook_formats = constants.EBOOK_FORMATS formats = ebook_formats + ['html', 'xml'] @@ -103,8 +104,8 @@ class Book(models.Model): class Meta: ordering = ('sort_key_author', 'sort_key') - verbose_name = _('book') - verbose_name_plural = _('books') + verbose_name = 'książka' + verbose_name_plural = 'książki' app_label = 'catalogue' def __str__(self): @@ -330,6 +331,9 @@ class Book(models.Model): total += app_settings.GET_MP3_LENGTH(media.file.path) return int(total) + def get_time(self): + return round(self.xml_file.size / 1000 * 40) + def has_media(self, type_): if type_ in Book.formats: return bool(getattr(self, "%s_file" % type_)) @@ -396,7 +400,7 @@ class Book(models.Model): def has_description(self): return len(self.description) > 0 - has_description.short_description = _('description') + has_description.short_description = 'opis' has_description.boolean = True def has_mp3_file(self): @@ -414,6 +418,21 @@ class Book(models.Model): has_daisy_file.short_description = 'DAISY' has_daisy_file.boolean = True + def has_sync_file(self): + return self.has_media("sync") + + def get_sync(self): + with self.get_media('sync').first().file.open('r') as f: + sync = f.read().split('\n') + offset = float(sync[0]) + items = [] + for line in sync[1:]: + if not line: + continue + start, end, elid = line.split() + items.append([elid, float(start) + offset]) + return json.dumps(items) + def has_audio_epub_file(self): return self.has_media("audio.epub") @@ -510,24 +529,15 @@ class Book(models.Model): licenses.add(license) readme = render_to_string('catalogue/audiobook_zip_readme.txt', { 'licenses': licenses, + 'meta': self.wldocument2().meta, }) return create_zip(paths, "%s_%s" % (self.slug, format_), {'informacje.txt': readme}) - def search_index(self, book_info=None, index=None, index_tags=True, commit=True): + def search_index(self, index=None): if not self.findable: return - if index is None: - from search.index import Index - index = Index() - try: - index.index_book(self, book_info) - if index_tags: - index.index_tags() - if commit: - index.index.commit() - except Exception as e: - index.index.rollback() - raise e + from search.index import Index + Index.index_book(self) # will make problems in conjunction with paid previews def download_pictures(self, remote_gallery_url): @@ -587,7 +597,7 @@ class Book(models.Model): @classmethod def from_text_and_meta(cls, raw_file, book_info, overwrite=False, dont_build=None, search_index=True, - search_index_tags=True, remote_gallery_url=None, days=0, findable=True): + remote_gallery_url=None, days=0, findable=True, logo=None, logo_mono=None, logo_alt=None): from catalogue import tasks if dont_build is None: @@ -601,7 +611,7 @@ class Book(models.Model): try: children.append(Book.objects.get(slug=part_url.slug)) except Book.DoesNotExist: - raise Book.DoesNotExist(_('Book "%s" does not exist.') % part_url.slug) + raise Book.DoesNotExist('Książka "%s" nie istnieje.' % part_url.slug) # Read book metadata book_slug = book_info.url.slug @@ -617,7 +627,7 @@ class Book(models.Model): book.preview_until = date.today() + timedelta(days) else: if not overwrite: - raise Book.AlreadyExists(_('Book %s already exists') % book_slug) + raise Book.AlreadyExists('Książka %s już istnieje' % book_slug) # Save shelves for this book book_shelves = list(book.tags.filter(category='set')) old_cover = book.cover_info() @@ -634,7 +644,14 @@ class Book(models.Model): book.common_slug = book_info.variant_of.slug else: book.common_slug = book.slug - book.extra_info = json.dumps(book_info.to_dict()) + extra = book_info.to_dict() + if logo: + extra['logo'] = logo + if logo_mono: + extra['logo_mono'] = logo_mono + if logo_alt: + extra['logo_alt'] = logo_alt + book.extra_info = json.dumps(extra) book.load_abstract() book.load_toc() book.save() @@ -696,7 +713,7 @@ class Book(models.Model): getattr(book, '%s_file' % format_).build_delay() if not settings.NO_SEARCH_INDEX and search_index and findable: - tasks.index_book.delay(book.id, book_info=book_info, index_tags=search_index_tags) + tasks.index_book.delay(book.id) for child in notify_cover_changed: child.parent_cover_changed() @@ -707,45 +724,46 @@ class Book(models.Model): cls.published.send(sender=cls, instance=book) return book - def get_master(self): - master_tags = [ - 'opowiadanie', - 'powiesc', - 'dramat_wierszowany_l', - 'dramat_wierszowany_lp', - 'dramat_wspolczesny', 'liryka_l', 'liryka_lp', - 'wywiad', - ] - from librarian.parser import WLDocument - wld = WLDocument.from_file(self.xml_file.path, parse_dublincore=False) - root = wld.edoc.getroot() - for master in root.iter(): - if master.tag in master_tags: - return master - def update_references(self): - from references.models import Entity, Reference - master = self.get_master() - if master is None: - master = [] - found = set() - for i, sec in enumerate(master): - for ref in sec.findall('.//ref'): - href = ref.attrib.get('href', '') - if not href or href in found: - continue - found.add(href) - entity, created = Entity.objects.get_or_create( - uri=href - ) - ref, created = Reference.objects.get_or_create( - book=self, - entity=entity - ) - ref.first_section = 'sec%d' % (i + 1) - entity.populate() - entity.save() - Reference.objects.filter(book=self).exclude(entity__uri__in=found).delete() + Entity = apps.get_model('references', 'Entity') + doc = self.wldocument2() + doc._compat_assign_section_ids() + doc._compat_assign_ordered_ids() + refs = {} + for ref_elem in doc.references(): + uri = ref_elem.attrib.get('href', '') + if not uri: + continue + if uri in refs: + ref = refs[uri] + else: + entity, entity_created = Entity.objects.get_or_create(uri=uri) + if entity_created: + try: + entity.populate() + except: + pass + else: + entity.save() + ref, ref_created = entity.reference_set.get_or_create(book=self) + refs[uri] = ref + if not ref_created: + ref.occurence_set.all().delete() + sec = ref_elem.get_link() + m = re.match(r'sec(\d+)', sec) + assert m is not None + sec = int(m.group(1)) + snippet = ref_elem.get_snippet() + b = builders['html-snippet']() + for s in snippet: + s.html_build(b) + html = b.output().get_bytes().decode('utf-8') + + ref.occurence_set.create( + section=sec, + html=html + ) + self.reference_set.exclude(entity__uri__in=refs).delete() @property def references(self): @@ -935,7 +953,7 @@ class Book(models.Model): def stage_note(self): stage = self.get_extra_info_json().get('stage') if stage and stage < '0.4': - return (_('This work needs modernisation'), + return (_('Ten utwór wymaga uwspółcześnienia'), reverse('infopage', args=['wymagajace-uwspolczesnienia'])) else: return None, None