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 .publishers.legimi import Legimi
13 from .publishers.woblink import Woblink
16 class Package(models.Model):
17 created_at = models.DateTimeField(auto_now_add=True)
18 placed_at = models.DateTimeField(null=True, blank=True)
19 finished_at = models.DateTimeField(null=True, blank=True)
20 definition_json = models.TextField(blank=True)
21 books = models.ManyToManyField('documents.Book')
22 status_json = models.TextField(blank=True)
23 logo = models.FileField(blank=True, upload_to='depot/logo')
24 file = models.FileField(blank=True, upload_to='depot/package/')
26 def save(self, *args, **kwargs):
28 self.set_status(self.get_status())
33 self.set_definition(self.get_definition())
37 super().save(*args, **kwargs)
40 return json.loads(self.status_json)
42 def set_status(self, status):
43 self.status_json = json.dumps(status, indent=4, ensure_ascii=False)
45 def get_definition(self):
46 return json.loads(self.definition_json)
48 def set_definition(self, definition):
49 self.definition_json = json.dumps(definition, indent=4, ensure_ascii=False)
52 f = tempfile.NamedTemporaryFile(prefix='depot-', suffix='.zip', mode='wb', delete=False)
53 book_count = self.books.all().count()
54 with zipfile.ZipFile(f, 'w') as z:
55 for i, book in enumerate(self.books.all()):
56 print(f'{i}/{book_count} {book.slug}')
57 self.build_for(book, z)
59 with open(f.name, 'rb') as ff:
60 self.file.save('package-{}.zip'.format(datetime.now().isoformat(timespec='seconds')), ff)
63 def build_for(self, book, z):
64 wldoc2 = book.wldocument(librarian2=True)
65 slug = wldoc2.meta.url.slug
66 for item in self.get_definition():
67 wldoc = book.wldocument()
68 wldoc2 = book.wldocument(librarian2=True)
69 base_url = 'file://' + book.gallery_path() + '/'
73 if item['type'] == 'cover':
76 kwargs['cover_logo'] = self.logo.path
77 for k in 'format', 'width', 'height', 'cover_class':
80 cover = make_cover(wldoc.book_info, **kwargs)
81 output = cover.output_file()
84 elif item['type'] == 'pdf':
86 if 'cover_class' in item:
87 cover_kwargs['cover_class'] = item['cover_class']
89 cover_kwargs['cover_logo'] = self.logo.path
90 cover = lambda *args, **kwargs: make_cover(*args, **kwargs, **cover_kwargs)
91 output = wldoc.as_pdf(cover=cover, base_url=base_url)
93 elif item['type'] == 'epub':
95 if 'cover_class' in item:
96 cover_kwargs['cover_class'] = item['cover_class']
98 cover_kwargs['cover_logo'] = self.logo.path
99 cover = lambda *args, **kwargs: make_cover(*args, **kwargs, **cover_kwargs)
101 output = EpubBuilder(
104 fundraising=item.get('fundraising', []),
107 elif item['type'] == 'mobi':
108 output = MobiBuilder(
111 fundraising=item.get('fundraising', []),
114 fname = f'{slug}/{slug}.'
116 fname += item['slug'] + '.'
125 class SiteBook(models.Model):
126 site = models.ForeignKey('Site', models.SET_NULL, null=True)
127 book = models.ForeignKey('documents.Book', models.CASCADE)
128 external_id = models.CharField(max_length=255, blank=True)
129 created_at = models.DateTimeField(auto_now_add=True)
132 unique_together = (('book', 'site'),)
135 return f'{self.site} : {self.book} : {self.external_id}'
138 class SiteBookPublish(models.Model):
139 site_book = models.ForeignKey(SiteBook, models.PROTECT, null=True, blank=True)
140 user = models.ForeignKey(settings.AUTH_USER_MODEL, models.SET_NULL, null=True)
141 created_at = models.DateTimeField()
142 started_at = models.DateTimeField(null=True, blank=True)
143 finished_at = models.DateTimeField(null=True, blank=True)
144 status = models.PositiveSmallIntegerField(choices=[
150 error = models.TextField(blank=True)
153 def create_for(cls, book, user, site):
154 book.assert_publishable()
155 changes = book.get_current_changes(publishable=True)
156 site_book, created = SiteBook.objects.get_or_create(
159 me = cls.objects.create(
160 site_book=site_book, user=user, created_at=now())
161 for change in changes:
162 me.sitechunkpublish_set.create(change=change)
167 self.started_at = now()
168 self.save(update_fields=['status', 'started_at'])
172 self.sitechunkpublish_set.order_by('change__chunk__number')
175 self.site.publish(self, changes=changes)
179 self.error = traceback.format_exc()
183 self.finished_at = now()
184 self.save(update_fields=['status', 'finished_at', 'error'])
187 class SiteChunkPublish(models.Model):
188 book_publish = models.ForeignKey(SiteBookPublish, models.CASCADE)
189 change = models.ForeignKey('documents.ChunkChange', models.CASCADE)
192 class Site(models.Model):
193 name = models.CharField(max_length=255)
194 site_type = models.CharField(max_length=32, choices=[
195 ('legimi', 'Legimi'),
196 ('woblink', 'Woblink'),
198 username = models.CharField(max_length=255)
199 password = models.CharField(max_length=255)
200 publisher_handle = models.CharField(max_length=255, blank=True)
201 description_add = models.TextField(blank=True)
207 return [t.text for t in self.mediainserttext_set.all()]
209 def get_price(self, words, pages):
210 price_obj = self.pricelevel_set.exclude(
214 ).order_by('-price').first()
215 if price_obj is None:
217 return price_obj.price
219 def get_publisher(self):
220 if self.site_type == 'legimi':
222 elif self.site_type == 'woblink':
224 return pub_class(self.username, self.password, self.publisher_handle)
226 def publish(self, site_book_publish, changes):
227 self.get_publisher().send_book(
232 def can_publish(self, book):
233 return self.get_publisher().can_publish(self, book)
235 def get_last(self, book):
236 return SiteBookPublish.objects.filter(
237 site_book__site=self, site_book__book=book
238 ).order_by('-created_at').first()
240 def get_external_id_for_book(self, book):
241 site_book = self.sitebook_set.filter(book=book).first()
242 return (site_book and site_book.external_id) or ''
244 class PriceLevel(models.Model):
245 site = models.ForeignKey(Site, models.CASCADE)
246 min_pages = models.IntegerField(null=True, blank=True)
247 min_words = models.IntegerField(null=True, blank=True)
248 price = models.IntegerField()
251 ordering = ('price',)
254 class MediaInsertText(models.Model):
255 site = models.ForeignKey(Site, models.CASCADE)
256 ordering = models.IntegerField()
257 text = models.TextField()
260 ordering = ('ordering',)