Add audience and thema data to catalogue. Also: nicer cover view.
[redakcja.git] / src / depot / models.py
1 import json
2 import os
3 import tempfile
4 import traceback
5 import zipfile
6 from datetime import datetime
7 from django.conf import settings
8 from django.db import models
9 from django.utils.timezone import now
10 from librarian.cover import make_cover
11 from librarian.builders import EpubBuilder, MobiBuilder
12 from .legimi import legimi
13
14
15 class Package(models.Model):
16     created_at = models.DateTimeField(auto_now_add=True)
17     placed_at = models.DateTimeField(null=True, blank=True)
18     finished_at = models.DateTimeField(null=True, blank=True)
19     definition_json = models.TextField(blank=True)
20     books = models.ManyToManyField('documents.Book')
21     status_json = models.TextField(blank=True)
22     logo = models.FileField(blank=True, upload_to='depot/logo')
23     file = models.FileField(blank=True, upload_to='depot/package/')
24
25     def save(self, *args, **kwargs):
26         try:
27             self.set_status(self.get_status())
28         except:
29             pass
30         
31         try:
32             self.set_definition(self.get_definition())
33         except:
34             pass
35
36         super().save(*args, **kwargs)
37     
38     def get_status(self):
39         return json.loads(self.status_json)
40
41     def set_status(self, status):
42         self.status_json = json.dumps(status, indent=4, ensure_ascii=False)
43
44     def get_definition(self):
45         return json.loads(self.definition_json)
46
47     def set_definition(self, definition):
48         self.definition_json = json.dumps(definition, indent=4, ensure_ascii=False)
49
50     def build(self):
51         f = tempfile.NamedTemporaryFile(prefix='depot-', suffix='.zip', mode='wb', delete=False)
52         book_count = self.books.all().count()
53         with zipfile.ZipFile(f, 'w') as z:
54             for i, book in enumerate(self.books.all()):
55                 print(f'{i}/{book_count} {book.slug}')
56                 self.build_for(book, z)
57         f.close()
58         with open(f.name, 'rb') as ff:
59             self.file.save('package-{}.zip'.format(datetime.now().isoformat(timespec='seconds')), ff)
60         os.unlink(f.name)
61
62     def build_for(self, book, z):
63         wldoc2 = book.wldocument(librarian2=True)
64         slug = wldoc2.meta.url.slug
65         for item in self.get_definition():
66             wldoc = book.wldocument()
67             wldoc2 = book.wldocument(librarian2=True)
68             base_url = 'file://' + book.gallery_path() + '/'
69
70             ext = item['type']
71
72             if item['type'] == 'cover':
73                 kwargs = {}
74                 if self.logo:
75                     kwargs['cover_logo'] = self.logo.path
76                 for k in 'format', 'width', 'height', 'cover_class':
77                     if k in item:
78                         kwargs[k] = item[k]
79                 cover = make_cover(wldoc.book_info, **kwargs)
80                 output = cover.output_file()
81                 ext = cover.ext()
82
83             elif item['type'] == 'pdf':
84                 cover_kwargs = {}
85                 if 'cover_class' in item:
86                     cover_kwargs['cover_class'] = item['cover_class']
87                 if self.logo:
88                     cover_kwargs['cover_logo'] = self.logo.path
89                 cover = lambda *args, **kwargs: make_cover(*args, **kwargs, **cover_kwargs)
90                 output = wldoc.as_pdf(cover=cover, base_url=base_url)
91
92             elif item['type'] == 'epub':
93                 cover_kwargs = {}
94                 if 'cover_class' in item:
95                     cover_kwargs['cover_class'] = item['cover_class']
96                 if self.logo:
97                     cover_kwargs['cover_logo'] = self.logo.path
98                 cover = lambda *args, **kwargs: make_cover(*args, **kwargs, **cover_kwargs)
99
100                 output = EpubBuilder(
101                     cover=cover,
102                     base_url=base_url,
103                     fundraising=item.get('fundraising', []),
104                 ).build(wldoc2)
105
106             elif item['type'] == 'mobi':
107                 output = MobiBuilder(
108                     cover=cover,
109                     base_url=base_url,
110                     fundraising=item.get('fundraising', []),
111                 ).build(wldoc2)
112
113             fname = f'{slug}/{slug}.'
114             if 'slug' in item:
115                 fname += item['slug'] + '.'
116             fname += ext
117
118             z.writestr(
119                 fname,
120                 output.get_bytes()
121             )
122
123
124 class LegimiBookPublish(models.Model):
125     book = models.ForeignKey('documents.Book', models.CASCADE)
126     user = models.ForeignKey(settings.AUTH_USER_MODEL, models.SET_NULL, null=True)
127     created_at = models.DateTimeField()
128     started_at = models.DateTimeField(null=True, blank=True)
129     finished_at = models.DateTimeField(null=True, blank=True)
130     status = models.PositiveSmallIntegerField(choices=[
131         (0, 'queued'),
132         (10, 'running'),
133         (100, 'done'),
134         (110, 'error'),
135     ], default=0)
136     error = models.TextField(blank=True)
137
138     @classmethod
139     def create_for(cls, book, user):
140         book.assert_publishable()
141         changes = book.get_current_changes(publishable=True)
142         me = cls.objects.create(book=book, user=user, created_at=now())
143         for change in changes:
144             me.legimichunkpublish_set.create(change=change)
145         return me
146
147     def publish(self):
148         self.status = 10
149         self.started_at = now()
150         self.save(update_fields=['status', 'started_at'])
151         try:
152             changes = [
153                 p.change for p in
154                 self.legimichunkpublish_set.order_by('change__chunk__number')
155             ]
156             legimi.send_book(self.book, changes=changes)
157             legimi.edit_sale(self.book)
158         except Exception:
159             self.status = 110
160             self.error = traceback.format_exc()
161         else:
162             self.status = 100
163             self.error = ''
164         self.finished_at = now()
165         self.save(update_fields=['status', 'finished_at', 'error'])
166
167
168 class LegimiChunkPublish(models.Model):
169     book_publish = models.ForeignKey(LegimiBookPublish, models.CASCADE)
170     change = models.ForeignKey('documents.ChunkChange', models.CASCADE)