Add sellable flag for audiobooks.
[wolnelektury.git] / src / catalogue / models / book.py
index 35ca3c1..d0487cd 100644 (file)
@@ -7,6 +7,7 @@ from datetime import date, timedelta
 from random import randint
 import os.path
 import re
 from random import randint
 import os.path
 import re
+import requests
 from slugify import slugify
 from sortify import sortify
 from urllib.request import urlretrieve
 from slugify import slugify
 from sortify import sortify
 from urllib.request import urlretrieve
@@ -38,9 +39,9 @@ bofh_storage = BofhFileSystemStorage()
 class Book(models.Model):
     """Represents a book imported from WL-XML."""
     title = models.CharField('tytuł', max_length=32767)
 class Book(models.Model):
     """Represents a book imported from WL-XML."""
     title = models.CharField('tytuł', max_length=32767)
-    sort_key = models.CharField('klucz sortowania', max_length=120, db_index=True, editable=False)
+    sort_key = models.CharField('klucz sortowania', max_length=120, db_index=True, db_collation='C', editable=False)
     sort_key_author = models.CharField(
     sort_key_author = models.CharField(
-        'klucz sortowania wg autora', max_length=120, db_index=True, editable=False, default='')
+        'klucz sortowania wg autora', max_length=120, db_index=True, db_collation='C', 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)
     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)
@@ -61,6 +62,9 @@ class Book(models.Model):
     preview_until = models.DateField('prapremiera do', blank=True, null=True)
     preview_key = models.CharField(max_length=32, blank=True, null=True)
     findable = models.BooleanField('wyszukiwalna', default=True, db_index=True)
     preview_until = models.DateField('prapremiera do', blank=True, null=True)
     preview_key = models.CharField(max_length=32, blank=True, null=True)
     findable = models.BooleanField('wyszukiwalna', default=True, db_index=True)
+    can_sell = models.BooleanField('do sprzedaży', default=True)
+    can_sell_mp3 = models.BooleanField('do sprzedaży mp3', default=True)
+    isbn_mp3 = models.CharField('ISBN audiobooka', max_length=32, blank=True)
 
     # files generated during publication
     xml_file = fields.XmlField(storage=bofh_storage, with_etag=False)
 
     # files generated during publication
     xml_file = fields.XmlField(storage=bofh_storage, with_etag=False)
@@ -98,7 +102,9 @@ class Book(models.Model):
     translators = models.ManyToManyField(Tag, blank=True)
     narrators = models.ManyToManyField(Tag, blank=True, related_name='narrated')
     has_audio = models.BooleanField(default=False)
     translators = models.ManyToManyField(Tag, blank=True)
     narrators = models.ManyToManyField(Tag, blank=True, related_name='narrated')
     has_audio = models.BooleanField(default=False)
-
+    read_time = models.IntegerField(blank=True, null=True)
+    pages = models.IntegerField(blank=True, null=True)
+    
     html_built = django.dispatch.Signal()
     published = django.dispatch.Signal()
 
     html_built = django.dispatch.Signal()
     published = django.dispatch.Signal()
 
@@ -187,6 +193,10 @@ class Book(models.Model):
     def isbn_mobi(self):
         return self.get_extra_info_json().get('isbn_mobi')
 
     def isbn_mobi(self):
         return self.get_extra_info_json().get('isbn_mobi')
 
+    @property
+    def redakcja(self):
+        return self.get_extra_info_json().get('about')
+    
     def is_accessible_to(self, user):
         if not self.preview:
             return True
     def is_accessible_to(self, user):
         if not self.preview:
             return True
@@ -471,10 +481,9 @@ class Book(models.Model):
             None, ContentFile(sync)
             )
 
             None, ContentFile(sync)
             )
 
-    
     def get_sync(self):
         if not self.has_sync_file():
     def get_sync(self):
         if not self.has_sync_file():
-            return '[]'
+            return []
         with self.get_media('sync').first().file.open('r') as f:
             sync = f.read().split('\n')
         offset = float(sync[0])
         with self.get_media('sync').first().file.open('r') as f:
             sync = f.read().split('\n')
         offset = float(sync[0])
@@ -484,8 +493,22 @@ class Book(models.Model):
                 continue
             start, end, elid = line.split()
             items.append([elid, float(start) + offset])
                 continue
             start, end, elid = line.split()
             items.append([elid, float(start) + offset])
-        return json.dumps(items)
-    
+        return items
+
+    def sync_ts(self, ts):
+        elid = None
+        for cur_id, t in self.get_sync():
+            if ts >= t:
+                elid = cur_id
+            else:
+                break
+        return elid
+
+    def sync_elid(self, elid):
+        for cur_id, t in self.get_sync():
+            if cur_id == elid:
+                return t
+
     def has_audio_epub_file(self):
         return self.has_media("audio.epub")
 
     def has_audio_epub_file(self):
         return self.has_media("audio.epub")
 
@@ -664,7 +687,7 @@ class Book(models.Model):
 
     @classmethod
     def from_text_and_meta(cls, raw_file, book_info, overwrite=False, dont_build=None, search_index=True,
 
     @classmethod
     def from_text_and_meta(cls, raw_file, book_info, overwrite=False, dont_build=None, search_index=True,
-                           remote_gallery_url=None, days=0, findable=True, logo=None, logo_mono=None, logo_alt=None):
+                           remote_gallery_url=None, days=0, findable=True, logo=None, logo_mono=None, logo_alt=None, can_sell=None, isbn_mp3=None):
         from catalogue import tasks
 
         if dont_build is None:
         from catalogue import tasks
 
         if dont_build is None:
@@ -719,11 +742,17 @@ class Book(models.Model):
             extra['logo_mono'] = logo_mono
         if logo_alt:
             extra['logo_alt'] = logo_alt
             extra['logo_mono'] = logo_mono
         if logo_alt:
             extra['logo_alt'] = logo_alt
+        if can_sell is not None:
+            book.can_sell = can_sell
+        if isbn_mp3 is not None:
+            book.isbn_mp3 = isbn_mp3
         book.extra_info = json.dumps(extra)
         book.load_abstract()
         book.load_toc()
         book.save()
 
         book.extra_info = json.dumps(extra)
         book.load_abstract()
         book.load_toc()
         book.save()
 
+        book.update_stats()
+        
         meta_tags = Tag.tags_from_info(book_info)
 
         just_tags = [t for (t, rel) in meta_tags if not rel]
         meta_tags = Tag.tags_from_info(book_info)
 
         just_tags = [t for (t, rel) in meta_tags if not rel]
@@ -791,6 +820,16 @@ class Book(models.Model):
         cls.published.send(sender=cls, instance=book)
         return book
 
         cls.published.send(sender=cls, instance=book)
         return book
 
+    def update_stats(self):
+        stats = self.wldocument2().get_statistics()['total']
+        self.pages = round(
+            stats.get('verses_with_fn', 0) / 30 +
+            stats.get('chars_out_verse_with_fn', 0) / 1800)
+        self.read_time = round(self.get_time())
+        self.save(update_fields=['pages', 'read_time'])
+        if self.parent is not None:
+            self.parent.update_stats()
+
     def update_references(self):
         Entity = apps.get_model('references', 'Entity')
         doc = self.wldocument2()
     def update_references(self):
         Entity = apps.get_model('references', 'Entity')
         doc = self.wldocument2()
@@ -872,6 +911,18 @@ class Book(models.Model):
             narrators.append(t)
         self.narrators.set(narrators)
 
             narrators.append(t)
         self.narrators.set(narrators)
 
+    def update_can_sell_mp3(self):
+        ret = True
+        for child in self.get_children():
+            child.update_can_sell_mp3()
+            if not child.can_sell_mp3:
+                ret = False
+        if self.has_mp3_file():
+            audio_items = requests.get(f'https://audio.wolnelektury.pl/archive/book/{self.slug}.json').json()['items']
+            if not all(x['project']['can_sell'] for x in audio_items):
+                ret = False
+        self.can_sell_audio = ret
+
     @classmethod
     @transaction.atomic
     def repopulate_ancestors(cls):
     @classmethod
     @transaction.atomic
     def repopulate_ancestors(cls):
@@ -988,6 +1039,12 @@ class Book(models.Model):
         elif isinstance(publisher, list):
             return ', '.join(publisher)
 
         elif isinstance(publisher, list):
             return ', '.join(publisher)
 
+    def get_recommended(self, limit=4):
+        books_qs = type(self).objects.filter(findable=True)
+        books_qs = books_qs.exclude(common_slug=self.common_slug).exclude(ancestor=self)
+        books = type(self).tagged.related_to(self, books_qs)[:limit]
+        return books
+
     @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`.
@@ -1107,6 +1164,24 @@ class Book(models.Model):
     def ridero_link(self):
         return 'https://ridero.eu/%s/books/wl_%s/' % (get_language(), self.slug.replace('-', '_'))
 
     def ridero_link(self):
         return 'https://ridero.eu/%s/books/wl_%s/' % (get_language(), self.slug.replace('-', '_'))
 
+    def elevenreader_link(self):
+        first_text = self.get_first_text()
+        if first_text is None:
+            return None
+        return 'https://elevenreader.io/audiobooks/wolnelektury:' + first_text.slug
+
+    def content_warnings(self):
+        warnings_def = {
+            'wulgaryzmy': _('wulgaryzmy'),
+        }
+        warnings = self.get_extra_info_json().get('content_warnings', [])
+        warnings = [
+            warnings_def.get(w, w)
+            for w in warnings
+        ]
+        warnings.sort()
+        return warnings
+
     def full_sort_key(self):
         return self.SORT_KEY_SEP.join((self.sort_key_author, self.sort_key, str(self.id)))
 
     def full_sort_key(self):
         return self.SORT_KEY_SEP.join((self.sort_key_author, self.sort_key, str(self.id)))