python-docx==0.8.11
Wikidata==0.7
-librarian==23.07.1
+librarian==23.08
## Django
Django==4.1.9
def get_absolute_url(self):
return reverse("catalogue_author", args=[self.slug])
+ @classmethod
+ def get_by_literal(cls, literal):
+ names = literal.split(',', 1)
+ names = [n.strip() for n in names]
+ if len(names) == 2:
+ return cls.objects.filter(last_name=names[0], first_name=names[1]).first()
+ else:
+ return cls.objects.filter(last_name=names[0], first_name='').first() or \
+ cls.objects.filter(first_name=names[0], last_name='').first()
+
@property
def name(self):
return f"{self.last_name}, {self.first_name}"
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
-from depot.woblink import get_woblink_session
+import depot.models
@login_required
def woblink_author_autocomplete(request):
- session = get_woblink_session()
+ shop = depot.models.Shop.objects.filter(shop='woblink').first()
+ if shop is None:
+ return JsonResponse({})
+ woblink = shop.get_publisher()
term = request.GET.get('term')
if not term:
return JsonResponse({})
- response = session.get(
+ response = woblink.session.get(
'https://publisher.woblink.com/author/autocomplete/' + term
).json()
return JsonResponse({
+from admin_ordering.admin import OrderableAdmin
from django.contrib import admin
from . import models
@admin.register(models.Package)
class PackageAdmin(admin.ModelAdmin):
raw_id_fields = ['books']
+
+
+class MediaInsertTextInline(OrderableAdmin, admin.TabularInline):
+ model = models.MediaInsertText
+ extra = 0
+
+
+class PriceLevelInline(OrderableAdmin, admin.TabularInline):
+ model = models.PriceLevel
+ extra = 0
+
+
+@admin.register(models.Shop)
+class ShopAdmin(admin.ModelAdmin):
+ inlines = [
+ MediaInsertTextInline,
+ PriceLevelInline,
+ ]
+++ /dev/null
-from datetime import date
-import re
-from django.conf import settings
-from librarian.functions import lang_code_3to2
-from librarian.html import transform_abstrakt
-from librarian.builders import EpubBuilder, MobiBuilder
-from librarian.covers.marquise import MarquiseCover, LabelMarquiseCover
-import requests
-from slugify import slugify
-
-
-
-fundraising=[
- "Książka, którą czytasz, pochodzi z <a href=\"https://wolnelektury.pl/\">Wolnych Lektur</a>. Naszą misją jest wspieranie dzieciaków w dostępie do lektur szkolnych oraz zachęcanie ich do czytania. Miło Cię poznać!",
- "Podoba Ci się to, co robimy? Jesteśmy organizacją pożytku publicznego. Wesprzyj Wolne Lektury drobną wpłatą: <a href=\"https://wolnelektury.pl/towarzystwo/\">wolnelektury.pl/towarzystwo/</a>",
- "Przyjaciele Wolnych Lektur otrzymują dostęp do prapremier wcześniej niż inni. Zadeklaruj stałą wpłatę i dołącz do Towarzystwa Przyjaciół Wolnych Lektur: <a href=\"https://wolnelektury.pl/towarzystwo/\">wolnelektury.pl/towarzystwo/</a>",
- "Informacje o nowościach w naszej bibliotece w Twojej skrzynce mailowej? Nic prostszego, zapisz się do newslettera. Kliknij, by pozostawić swój adres e-mail: <a href=\"https://wolnelektury.pl/newsletter/zapisz-sie/\">wolnelektury.pl/newsletter/zapisz-sie/</a>",
- "Przekaż 1% podatku na Wolne Lektury.<br/>\nKRS: 0000070056<br/>\nNazwa organizacji: Fundacja Nowoczesna Polska<br/>\nKażda wpłacona kwota zostanie przeznaczona na rozwój Wolnych Lektur."
-]
-
-description_add = '<p>Książkę polecają <a href="https://wolnelektury.pl">Wolne Lektury</a> — najpopularniejsza biblioteka on-line.</p>'
-
-
-class Legimi:
- #BASE_URL = 'https://wydawca.legimi.com'
- BASE_URL = 'https://panel.legimi.pl'
- LOGIN_URL = BASE_URL + '/publishers/membership'
- UPLOAD_URL = BASE_URL + '/administration/upload/start'
- CREATE_URL = BASE_URL + '/publishers/publications/create'
- EDIT_URL = BASE_URL + '/publishers/publications/edit/%s'
- EDIT_FILES_URL = BASE_URL + '/publishers/publications/editfiles/%s'
- EDIT_SALE_URL = BASE_URL + '/publishers/publications/editsale/%s'
-
- CATEGORIES = {
- 'Dla dzieci i młodzieży': 94,
- 'Książki dla dzieci': 15,
- 'Literatura młodzieżowa': 24,
- 'Kryminał': 29,
- 'Kryminał klasyczny': 31,
- 'Kryminał współczesny': 32,
- 'Kryminał historyczny': 30,
- 'default': 8886,
- 'Edukacja': 10,
- 'Słowniki i leksykony': 14,
- 'Encyklopedie': 13,
- 'Lektury': 11,
- 'Starożytność': 80,
- 'Barok': 83,
- 'Oświecenie': 84,
- 'Dwudziestolecie międzywojenne': 88,
- 'Średniowiecze': 81,
- 'Współczesność': 90,
- 'Modernizm': 87,
- 'Pozytywizm': 86,
- 'Renesans': 82,
- 'Romantyzm': 85,
- 'Młoda Polska': 89,
- 'Podręczniki': 52,
- 'Fantastyka i sci-fi': 25,
- 'Fantastyka': 26,
- 'Science fiction': 27,
- 'Języki obce': 59,
- 'Antyki i kolekcjonerstwo': 53,
- 'Astrologia i wróżbiarstwo': 54,
- 'Zdrowie i rodzina': 57,
- 'Hobby': 55,
- 'Medycyna i zdrowie': 58,
- 'Psychologiczne': 78,
- 'Styl': 56,
- 'Humanistyka': 97,
- 'Kultura i sztuka': 64,
- 'Film': 66,
- 'Muzyka': 65,
- 'Eseje literackie': 49,
- 'Historia': 60,
- 'Styl życia': 73,
- 'Wakacje i podróże': 69,
- 'Dla mężczyzn': 79,
- 'Sport': 76,
- 'Obyczajowe i romanse': 93,
- 'Humor': 68,
- 'Obyczajowe': 35,
- 'Powieść': 41,
- 'Powieść przygodowa': 42,
- 'Współczesna powieść przygodowa': 44,
- 'Historyczna powieść przygodowa': 43,
- 'Powieść historyczna': 46,
- 'Powieść psychologiczna': 47,
- 'Powieść religijna': 45,
- 'Romans': 36,
- 'Romans klasyczny': 38,
- 'Romans współczesny': 39,
- 'Literatura erotyczna': 40,
- 'Romans historyczny': 37,
- 'Dla kobiet': 77,
- 'Sensacja, thriller, horror': 91,
- 'Horror': 28,
- 'Sensacja': 33,
- 'Thriller': 34,
- 'Aktualności': 70,
- 'Czasopisma': 71,
- 'Literatura faktu, reportaże, biografie': 92,
- 'Literatura faktu': 16,
- 'Biografie': 17,
- 'Publicystyka': 20,
- 'Dzienniki': 19,
- 'Dokument, esej': 18,
- 'Historia literatury i krytyka literacka': 23,
- 'Literatura popularnonaukowa': 22,
- 'Reportaż': 21,
- 'Społeczno-polityczne': 72,
- 'Poezja i dramat': 95,
- 'Dramat': 48,
- 'Poezja': 50,
- 'Religia i duchowość': 51,
- 'Nauka i nowe technologie': 98,
- 'Nauka i technika': 61,
- 'Nauki ścisłe': 62,
- 'Nauki humanistyczne': 63,
- 'Technologia i Internet': 75,
- 'Specjalistyczne': 99,
- 'Biznes i finanse': 1,
- 'Ekonomia': 5,
- 'Finanse': 6,
- 'Zarządzanie': 3,
- 'Marketing': 2,
- 'Rozwój osobisty': 7,
- 'Kariera i sukces zawodowy': 8,
- 'Psychologia, motywacja': 9,
- 'PR': 4,
- 'Prawo': 67,
- 'Branżowe': 74,
- }
-
- def __init__(self, username, password, publisher_id):
- self.username = username
- self.password = password
- self.publisher_id = publisher_id
- self._session = None
-
- @property
- def session(self):
- if self._session is None:
- session = requests.Session()
- response = session.post(
- self.LOGIN_URL,
- data={
- 'ValidationTrue': 'true',
- 'UserName': self.username,
- 'Password': self.password,
- })
- self._session = session
- return self._session
-
- def list(self):
- return self.session.get('https://wydawca.legimi.com/publishers/publications')
-
- def upload(self, content):
- response = self.session.post(
- self.UPLOAD_URL,
- files={
- "files": content,
- })
- model = response.json()['model']
- return {
- "name": model['Name'],
- "token": model['Token'],
- "url": model['Url'],
- }
-
- def send_book(self, book, changes=None):
- wlbook = book.wldocument(librarian2=True, changes=changes)
- meta = wlbook.meta
-
- cover = LabelMarquiseCover(meta, width=1200).output_file()
- epub_file = EpubBuilder(
- cover=MarquiseCover,
- fundraising=fundraising,
- base_url='file://' + book.gallery_path() + '/'
- ).build(wlbook).get_file()
- mobi_file = MobiBuilder(
- cover=MarquiseCover,
- fundraising=fundraising,
- base_url='file://' + book.gallery_path() + '/'
- ).build(wlbook).get_file()
-
- thema = []
- if meta.thema_main:
- thema.append(meta.thema_main)
- thema.extend(meta.thema)
-
- book_data = {
- "Title": meta.title,
- "Author": ", ".join(p.readable() for p in meta.authors),
- "Year": str(date.today().year),
-
- 'GenreId': str(self.get_genre(wlbook)),
- 'themaCategories': ';'.join(thema),
- 'thema-search': '',
- 'Isbn': '',
- 'LanguageLocale': lang_code_3to2(meta.language),
-
- 'Description': self.get_description(wlbook),
- }
- if meta.isbn_html:
- isbn = meta.isbn_html
- if isbn.upper().startswith(('ISBN ', 'ISBN-')):
- isbn = isbn[5:]
- isbn = isbn.strip()
- book_data['Isbn'] = isbn
-
- files_data = {}
-
- cover_data = self.upload(
- (meta.url.slug + '.jpg', cover.get_file(), 'image/jpeg')
- )
- book_data.update({
- "Cover.Name": cover_data['name'],
- "Cover.Token": cover_data['token'],
- "Cover.Url": cover_data['url'],
- })
-
- epub_data = self.upload(
- (meta.url.slug + '.epub', epub_file, 'application/epub+zip')
- )
- files_data.update({
- 'BookEpub.Token': epub_data['token'],
- 'BookEpub.Name': epub_data['name'],
- 'SampleEpubType': 'Generation',
- })
-
- mobi_data = self.upload(
- (meta.url.slug + '.mobi', mobi_file, 'application/x-mobipocket-ebook')
- )
- files_data.update({
- 'BookMobi.Token': mobi_data['token'],
- 'BookMobi.Name': mobi_data['name'],
- })
-
- if book.legimi_id:
- self.edit(
- book.legimi_id,
- book_data
- )
- self.edit_files(
- book.legimi_id,
- files_data
- )
- else:
- legimi_id = self.create_book(book_data, files_data)
- if legimi_id:
- book.legimi_id = legimi_id
- book.save(update_fields=['legimi_id'])
-
- def get_description(self, wlbook):
- description = ''
- abstract = wlbook.tree.find('.//abstrakt')
- if abstract is not None:
- description = transform_abstrakt(abstract)
- description += description_add
- description += '<p>'
- description += ', '.join(
- '<a href="https://wolnelektury.pl/katalog/autor/{}/">{}</a>'.format(
- slugify(p.readable()),
- p.readable(),
- )
- for p in wlbook.meta.authors
- ) + '<br>'
- description += '<a href="https://wolnelektury.pl/katalog/lektura/{}/">{}</a><br>'.format(
- wlbook.meta.url.slug,
- wlbook.meta.title
- )
- if wlbook.meta.translators:
- description += 'tłum. ' + ', '.join(p.readable() for p in wlbook.meta.translators) + '<br>'
- description += 'Epoka: ' + ', '.join(
- '<a href="https://wolnelektury.pl/katalog/epoka/{}/">{}</a>'.format(
- slugify(p),
- p,
- )
- for p in wlbook.meta.epochs
- ) + ' '
- description += 'Rodzaj: ' + ', '.join(
- '<a href="https://wolnelektury.pl/katalog/rodzaj/{}/">{}</a>'.format(
- slugify(p),
- p,
- )
- for p in wlbook.meta.kinds
- ) + ' '
- description += 'Gatunek: ' + ', '.join(
- '<a href="https://wolnelektury.pl/katalog/gatunek/{}/">{}</a>'.format(
- slugify(p),
- p,
- )
- for p in wlbook.meta.genres
- ) + '</p>'
-
- # TODO: Move away from using audiences for this.
- if wlbook.meta.audience in ('L', 'SP1', 'SP2', 'SP3', 'SP4'):
- description += '<p><em>{}</em> to lektura szkolna.'.format(wlbook.meta.title)
- if wlbook.tree.find('//pe') is not None:
- description += '<br>Ebook <em>{title}</em> zawiera przypisy opracowane specjalnie dla uczennic i uczniów {school}.'.format(
- title=wlbook.meta.title,
- school='szkoły podstawowej' if wlbook.meta.audience.startswith('SP') else 'liceum i technikum'
- )
- description += '</p>'
- return description
-
- def get_genre(self, wlbook):
- if wlbook.meta.legimi and wlbook.meta.legimi in self.CATEGORIES:
- return self.CATEGORIES[wlbook.meta.legimi]
- for epoch in wlbook.meta.epochs:
- if epoch in self.CATEGORIES:
- return self.CATEGORIES[epoch]
- return self.CATEGORIES['Lektury']
-
- def create_book(self, book_data, files_data):
- data = {
- 'createValidationTrue': 'true',
- 'PublisherId': self.publisher_id,#3609954
- 'IsLibraryPass': 'False',
-
- 'SamplesGenerationType': 'Quantity',
- 'SamplesGenerationPercent': '10',
-
- 'EnterToTheMarketType': 'No',
- 'EnterToTheMarketDate': '',
- 'HidingDate': '',
- 'SalesNoLimitOption': 'false',
- 'SalesNoLimitKindle': 'false',
- 'SalesInStoreEbookGrossValue': '0,00',
- 'SalesPromotion': 'False',
- 'SalesPromotionGrossValue': '0,00',
- 'SalesPromotionDatesRange.DateStart': '',
- 'SalesPromotionDatesRange.DateEnd': '',
- }
-
- for form in 'Epub', 'Mobi', 'Pdf':
- data.update({
- f'Book{form}.Token': '',
- f'Book{form}.Name': '',
- f'Book{form}.StorageName': '',
- f'Book{form}.Order': '',
-
- f'Sample{form}Type': 'Files',
- f'Sample{form}.Token': '',
- f'Sample{form}.Name': '',
- f'Sample{form}.StorageName': '',
- f'Sample{form}.Order': '',
- })
-
- data.update(book_data)
- data.update(files_data)
-
- response = self.session.post(self.CREATE_URL, data=data)
- m = re.search(r'/(\d+)$', response.url)
- if m is not None:
- return m.group(1)
-
- def edit(self, legimi_id, data):
- current = {
- 'ValidationTrue': 'true',
- 'Id': legimi_id
- }
-
- current.update(data)
-
- self.session.post(
- self.EDIT_URL % legimi_id,
- data=current
- )
-
- def edit_files(self, legimi_id, files_data):
- current = {
- 'ValidationTrue': 'true',
- 'Id': legimi_id,
- 'SamplesGenerationType': 'Quantity',
- 'SamplesGenerationPercent': '10',
- }
-
- for form in 'Epub', 'Mobi', 'Pdf':
- current.update({
- f'Book{form}.Token': '',
- f'Book{form}.Name': '',
- f'Book{form}.StorageName': '',
- f'Book{form}.Order': '',
-
- f'Sample{form}.Type': 'Files',
- f'Sample{form}.Token': '',
- f'Sample{form}.Name': '',
- f'Sample{form}.StorageName': '',
- f'Sample{form}.Order': '',
- })
-
- current.update(files_data)
-
- response = self.session.post(
- self.EDIT_FILES_URL % legimi_id,
- data=current
- )
-
- def edit_sale(self, book):
- assert book.legimi_id
-
- words = book.wldocument().get_statistics()['total']['words_with_fn']
-
- price = settings.LEGIMI_SMALL_PRICE
- if words > settings.LEGIMI_SMALL_WORDS:
- price = settings.LEGIMI_BIG_PRICE
-
- abo = 'true' if words > settings.LEGIMI_BIG_WORDS else 'false'
-
- data = {
- 'ValidationTrue': 'true',
- 'Id': book.legimi_id,
- 'SalesPromotionId': "0",
- 'IsLibraryPass': "False",
- 'OriginalEnterToTheMarketType': "No",
- 'OriginalHidingDate': "",
- 'OriginalEnterToTheMarketDate': "",
- 'EnterToTheMarketType': "Yes",
- 'EnterToTheMarketDate': "",
- 'HidingDate': "",
- 'SalesNoLimitOption': abo,
- 'SalesNoLimitKindle': abo,
- 'SalesInStoreEbookGrossValue': f'{price},00',
- 'SalesPromotion': "False",
- 'SalesPromotionGrossValue': "0,00",
- 'SalesPromotionDatesRange.DateStart': "",
- 'SalesPromotionDatesRange.DateEnd': "",
- }
-
- self.session.post(
- self.EDIT_SALE_URL % book.legimi_id,
- data=data
- )
-
-
-legimi = Legimi(
- settings.LEGIMI_USERNAME,
- settings.LEGIMI_PASSWORD,
- settings.LEGIMI_PUBLISHER_ID,
-)
from django.core.management.base import BaseCommand
-from depot.models import LegimiBookPublish
+from depot.models import ShopBookPublish
class Command(BaseCommand):
def handle(self, **options):
- for p in LegimiBookPublish.objects.filter(status=0).order_by('created_at'):
- print(p, p.book.slug, p.created_at)
+ for p in ShopBookPublish.objects.filter(status=0).order_by('created_at'):
+ print(p.id, p.shop, p.book.slug, p.created_at)
p.publish()
--- /dev/null
+# Generated by Django 4.1.9 on 2023-08-10 18:36
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ("documents", "0011_book_woblink_id"),
+ ("depot", "0002_legimibookpublish_legimichunkpublish"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Shop",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "shop",
+ models.CharField(
+ choices=[("legimi", "Legimi"), ("woblink", "Woblink")],
+ max_length=32,
+ ),
+ ),
+ ("username", models.CharField(max_length=255)),
+ ("password", models.CharField(max_length=255)),
+ ],
+ ),
+ migrations.RenameModel(
+ old_name="LegimiBookPublish",
+ new_name="ShopBookPublish",
+ ),
+ migrations.RenameModel(
+ old_name="LegimiChunkPublish",
+ new_name="ShopChunkPublish",
+ ),
+ migrations.CreateModel(
+ name="PriceLevel",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("min_pages", models.IntegerField(blank=True, null=True)),
+ ("min_words", models.IntegerField(blank=True, null=True)),
+ ("price", models.IntegerField()),
+ (
+ "shop",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="depot.shop"
+ ),
+ ),
+ ],
+ options={
+ "ordering": ("price",),
+ },
+ ),
+ migrations.CreateModel(
+ name="MediaInsertText",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("ordering", models.IntegerField()),
+ ("text", models.TextField()),
+ (
+ "shop",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="depot.shop"
+ ),
+ ),
+ ],
+ options={
+ "ordering": ("ordering",),
+ },
+ ),
+ ]
--- /dev/null
+# Generated by Django 4.1.9 on 2023-08-10 18:36
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("depot", "0003_shop_rename_legimibookpublish_shopbookpublish_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="shopbookpublish",
+ name="shop",
+ field=models.ForeignKey(
+ null=True, on_delete=django.db.models.deletion.SET_NULL, to="depot.shop"
+ ),
+ ),
+ ]
--- /dev/null
+# Generated by Django 4.1.9 on 2023-08-10 18:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("depot", "0004_shopbookpublish_shop"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="shop",
+ name="name",
+ field=models.CharField(default="", max_length=255),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="shop",
+ name="publisher_handle",
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ ]
--- /dev/null
+# Generated by Django 4.1.9 on 2023-08-10 20:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("depot", "0005_shop_name_shop_publisher_handle"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="shop",
+ name="description_add",
+ field=models.TextField(blank=True),
+ ),
+ ]
from django.utils.timezone import now
from librarian.cover import make_cover
from librarian.builders import EpubBuilder, MobiBuilder
-from .legimi import legimi
+from .publishers.legimi import Legimi
+from .publishers.woblink import Woblink
class Package(models.Model):
self.set_status(self.get_status())
except:
pass
-
+
try:
self.set_definition(self.get_definition())
except:
pass
super().save(*args, **kwargs)
-
+
def get_status(self):
return json.loads(self.status_json)
)
-class LegimiBookPublish(models.Model):
+class ShopBookPublish(models.Model):
book = models.ForeignKey('documents.Book', models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, models.SET_NULL, null=True)
+ shop = models.ForeignKey('Shop', models.SET_NULL, null=True)
created_at = models.DateTimeField()
started_at = models.DateTimeField(null=True, blank=True)
finished_at = models.DateTimeField(null=True, blank=True)
error = models.TextField(blank=True)
@classmethod
- def create_for(cls, book, user):
+ def create_for(cls, book, user, shop):
book.assert_publishable()
changes = book.get_current_changes(publishable=True)
- me = cls.objects.create(book=book, user=user, created_at=now())
+ me = cls.objects.create(book=book, user=user, shop=shop, created_at=now())
for change in changes:
- me.legimichunkpublish_set.create(change=change)
+ me.shopchunkpublish_set.create(change=change)
return me
def publish(self):
try:
changes = [
p.change for p in
- self.legimichunkpublish_set.order_by('change__chunk__number')
+ self.shopchunkpublish_set.order_by('change__chunk__number')
]
- legimi.send_book(self.book, changes=changes)
- legimi.edit_sale(self.book)
+
+ self.shop.publish(self.book, changes=changes)
+
except Exception:
self.status = 110
self.error = traceback.format_exc()
self.save(update_fields=['status', 'finished_at', 'error'])
-class LegimiChunkPublish(models.Model):
- book_publish = models.ForeignKey(LegimiBookPublish, models.CASCADE)
+class ShopChunkPublish(models.Model):
+ book_publish = models.ForeignKey(ShopBookPublish, models.CASCADE)
change = models.ForeignKey('documents.ChunkChange', models.CASCADE)
+
+
+class Shop(models.Model):
+ name = models.CharField(max_length=255)
+ shop = models.CharField(max_length=32, choices=[
+ ('legimi', 'Legimi'),
+ ('woblink', 'Woblink'),
+ ])
+ username = models.CharField(max_length=255)
+ password = models.CharField(max_length=255)
+ publisher_handle = models.CharField(max_length=255, blank=True)
+ description_add = models.TextField(blank=True)
+
+ def __str__(self):
+ return self.shop
+
+ def get_texts(self):
+ return [t.text for t in self.mediainserttext_set.all()]
+
+ def get_price(self, words, pages):
+ price_obj = self.pricelevel_set.exclude(
+ min_pages__gt=pages
+ ).exclude(
+ min_words__gt=words
+ ).order_by('-price').first()
+ if price_obj is None:
+ return None
+ return price_obj.price
+
+ def get_publisher(self):
+ if self.shop == 'legimi':
+ pub_class = Legimi
+ elif self.shop == 'woblink':
+ pub_class = Woblink
+ return pub_class(self.username, self.password, self.publisher_handle)
+
+ def publish(self, book, changes):
+ self.get_publisher().send_book(
+ self, book, changes=changes,
+ )
+
+ def can_publish(self, book):
+ return self.get_publisher().can_publish(self, book)
+
+ def get_last(self, book):
+ return self.shopbookpublish_set.filter(book=book).order_by('-created_at').first()
+
+
+class PriceLevel(models.Model):
+ shop = models.ForeignKey(Shop, models.CASCADE)
+ min_pages = models.IntegerField(null=True, blank=True)
+ min_words = models.IntegerField(null=True, blank=True)
+ price = models.IntegerField()
+
+ class Meta:
+ ordering = ('price',)
+
+
+class MediaInsertText(models.Model):
+ shop = models.ForeignKey(Shop, models.CASCADE)
+ ordering = models.IntegerField()
+ text = models.TextField()
+
+ class Meta:
+ ordering = ('ordering',)
--- /dev/null
+import requests
+from librarian.html import transform_abstrakt
+from slugify import slugify
+
+
+class BasePublisher:
+ def __init__(self, username, password, publisher_handle):
+ self.username = username
+ self.password = password
+ self.publisher_handle = publisher_handle
+ self._session = None
+
+ @property
+ def session(self):
+ if self._session is None:
+ self._session = requests.Session()
+ self.login()
+ return self._session
+
+ def send_book(self, shop, book, changes=None):
+ raise NotImplementedError()
+
+ def get_description(self, wlbook, description_add=''):
+ description = ''
+ abstract = wlbook.tree.find('.//abstrakt')
+ if abstract is not None:
+ description = transform_abstrakt(abstract)
+ description += description_add
+ description += '<p>'
+ description += ', '.join(
+ '<a href="https://wolnelektury.pl/katalog/autor/{}/">{}</a>'.format(
+ slugify(p.readable()),
+ p.readable(),
+ )
+ for p in wlbook.meta.authors
+ ) + '<br>'
+ description += '<a href="https://wolnelektury.pl/katalog/lektura/{}/">{}</a><br>'.format(
+ wlbook.meta.url.slug,
+ wlbook.meta.title
+ )
+ if wlbook.meta.translators:
+ description += 'tłum. ' + ', '.join(p.readable() for p in wlbook.meta.translators) + '<br>'
+ description += 'Epoka: ' + ', '.join(
+ '<a href="https://wolnelektury.pl/katalog/epoka/{}/">{}</a>'.format(
+ slugify(p),
+ p,
+ )
+ for p in wlbook.meta.epochs
+ ) + ' '
+ description += 'Rodzaj: ' + ', '.join(
+ '<a href="https://wolnelektury.pl/katalog/rodzaj/{}/">{}</a>'.format(
+ slugify(p),
+ p,
+ )
+ for p in wlbook.meta.kinds
+ ) + ' '
+ description += 'Gatunek: ' + ', '.join(
+ '<a href="https://wolnelektury.pl/katalog/gatunek/{}/">{}</a>'.format(
+ slugify(p),
+ p,
+ )
+ for p in wlbook.meta.genres
+ ) + '</p>'
+
+ # TODO: Move away from using audiences for this.
+ if wlbook.meta.audience in ('L', 'SP1', 'SP2', 'SP3', 'SP4'):
+ description += '<p><em>{}</em> to lektura szkolna.'.format(wlbook.meta.title)
+ if wlbook.tree.find('//pe') is not None:
+ description += '<br>Ebook <em>{title}</em> zawiera przypisy opracowane specjalnie dla uczennic i uczniów {school}.'.format(
+ title=wlbook.meta.title,
+ school='szkoły podstawowej' if wlbook.meta.audience.startswith('SP') else 'liceum i technikum'
+ )
+ description += '</p>'
+ return description
+
--- /dev/null
+from datetime import date
+import re
+from django.conf import settings
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+from librarian.functions import lang_code_3to2
+from librarian.builders import EpubBuilder, MobiBuilder
+from librarian.covers.marquise import MarquiseCover, LabelMarquiseCover
+from .base import BasePublisher
+
+
+class Legimi(BasePublisher):
+ BASE_URL = 'https://panel.legimi.pl'
+ LOGIN_URL = BASE_URL + '/publishers/membership'
+ UPLOAD_URL = BASE_URL + '/administration/upload/start'
+ CREATE_URL = BASE_URL + '/publishers/publications/create'
+ EDIT_URL = BASE_URL + '/publishers/publications/edit/%s'
+ EDIT_FILES_URL = BASE_URL + '/publishers/publications/editfiles/%s'
+ EDIT_SALE_URL = BASE_URL + '/publishers/publications/editsale/%s'
+
+ CATEGORIES = {
+ 'Dla dzieci i młodzieży': 94,
+ 'Książki dla dzieci': 15,
+ 'Literatura młodzieżowa': 24,
+ 'Kryminał': 29,
+ 'Kryminał klasyczny': 31,
+ 'Kryminał współczesny': 32,
+ 'Kryminał historyczny': 30,
+ 'default': 8886,
+ 'Edukacja': 10,
+ 'Słowniki i leksykony': 14,
+ 'Encyklopedie': 13,
+ 'Lektury': 11,
+ 'Starożytność': 80,
+ 'Barok': 83,
+ 'Oświecenie': 84,
+ 'Dwudziestolecie międzywojenne': 88,
+ 'Średniowiecze': 81,
+ 'Współczesność': 90,
+ 'Modernizm': 87,
+ 'Pozytywizm': 86,
+ 'Renesans': 82,
+ 'Romantyzm': 85,
+ 'Młoda Polska': 89,
+ 'Podręczniki': 52,
+ 'Fantastyka i sci-fi': 25,
+ 'Fantastyka': 26,
+ 'Science fiction': 27,
+ 'Języki obce': 59,
+ 'Antyki i kolekcjonerstwo': 53,
+ 'Astrologia i wróżbiarstwo': 54,
+ 'Zdrowie i rodzina': 57,
+ 'Hobby': 55,
+ 'Medycyna i zdrowie': 58,
+ 'Psychologiczne': 78,
+ 'Styl': 56,
+ 'Humanistyka': 97,
+ 'Kultura i sztuka': 64,
+ 'Film': 66,
+ 'Muzyka': 65,
+ 'Eseje literackie': 49,
+ 'Historia': 60,
+ 'Styl życia': 73,
+ 'Wakacje i podróże': 69,
+ 'Dla mężczyzn': 79,
+ 'Sport': 76,
+ 'Obyczajowe i romanse': 93,
+ 'Humor': 68,
+ 'Obyczajowe': 35,
+ 'Powieść': 41,
+ 'Powieść przygodowa': 42,
+ 'Współczesna powieść przygodowa': 44,
+ 'Historyczna powieść przygodowa': 43,
+ 'Powieść historyczna': 46,
+ 'Powieść psychologiczna': 47,
+ 'Powieść religijna': 45,
+ 'Romans': 36,
+ 'Romans klasyczny': 38,
+ 'Romans współczesny': 39,
+ 'Literatura erotyczna': 40,
+ 'Romans historyczny': 37,
+ 'Dla kobiet': 77,
+ 'Sensacja, thriller, horror': 91,
+ 'Horror': 28,
+ 'Sensacja': 33,
+ 'Thriller': 34,
+ 'Aktualności': 70,
+ 'Czasopisma': 71,
+ 'Literatura faktu, reportaże, biografie': 92,
+ 'Literatura faktu': 16,
+ 'Biografie': 17,
+ 'Publicystyka': 20,
+ 'Dzienniki': 19,
+ 'Dokument, esej': 18,
+ 'Historia literatury i krytyka literacka': 23,
+ 'Literatura popularnonaukowa': 22,
+ 'Reportaż': 21,
+ 'Społeczno-polityczne': 72,
+ 'Poezja i dramat': 95,
+ 'Dramat': 48,
+ 'Poezja': 50,
+ 'Religia i duchowość': 51,
+ 'Nauka i nowe technologie': 98,
+ 'Nauka i technika': 61,
+ 'Nauki ścisłe': 62,
+ 'Nauki humanistyczne': 63,
+ 'Technologia i Internet': 75,
+ 'Specjalistyczne': 99,
+ 'Biznes i finanse': 1,
+ 'Ekonomia': 5,
+ 'Finanse': 6,
+ 'Zarządzanie': 3,
+ 'Marketing': 2,
+ 'Rozwój osobisty': 7,
+ 'Kariera i sukces zawodowy': 8,
+ 'Psychologia, motywacja': 9,
+ 'PR': 4,
+ 'Prawo': 67,
+ 'Branżowe': 74,
+ }
+
+ def login(self):
+ self._session.post(
+ self.LOGIN_URL,
+ data={
+ 'ValidationTrue': 'true',
+ 'UserName': self.username,
+ 'Password': self.password,
+ })
+
+ def can_publish(self, shop, book):
+ meta = book.wldocument(librarian2=True).meta
+ d = {
+ 'errors': [],
+ 'warnings': [],
+ }
+ if meta.thema_main or meta.thema:
+ if meta.thema_main:
+ comment = "w kategorii <b><tt>{code}</tt></b>".format(
+ code=escape(meta.thema_main)
+ )
+ if meta.thema:
+ comment += " oraz: " + ", ".join(
+ "<b><tt>{code}</tt></b>".format(code=escape(t))
+ for t in meta.thema
+ )
+ d['comment'] = mark_safe(comment)
+ elif meta.thema:
+ d['comment'] = mark_safe(
+ "w kategorii " + ", ".join(
+ "<b><tt>{code}</tt></b>".format(code=escape(t))
+ for t in meta.thema
+ )
+ )
+ d['warnings'].append('Brak głównej kategorii Thema')
+ else:
+ d['errors'].append('Brak kategorii Thema.')
+ return d
+
+ def list(self):
+ return self.session.get('https://wydawca.legimi.com/publishers/publications')
+
+ def upload(self, content):
+ response = self.session.post(
+ self.UPLOAD_URL,
+ files={
+ "files": content,
+ })
+ model = response.json()['model']
+ return {
+ "name": model['Name'],
+ "token": model['Token'],
+ "url": model['Url'],
+ }
+
+ def send_book(self, shop, book, changes=None):
+ wlbook = book.wldocument(librarian2=True, changes=changes)
+ meta = wlbook.meta
+
+ cover = LabelMarquiseCover(meta, width=1200).output_file()
+ texts = shop.get_texts()
+ epub_file = EpubBuilder(
+ cover=MarquiseCover,
+ fundraising=texts,
+ base_url='file://' + book.gallery_path() + '/'
+ ).build(wlbook).get_file()
+ mobi_file = MobiBuilder(
+ cover=MarquiseCover,
+ fundraising=texts,
+ base_url='file://' + book.gallery_path() + '/'
+ ).build(wlbook).get_file()
+
+ thema = []
+ if meta.thema_main:
+ thema.append(meta.thema_main)
+ thema.extend(meta.thema)
+
+ book_data = {
+ "Title": meta.title,
+ "Author": ", ".join(p.readable() for p in meta.authors),
+ "Year": str(date.today().year),
+
+ 'GenreId': str(self.get_genre(wlbook)),
+ 'themaCategories': ';'.join(thema),
+ 'thema-search': '',
+ 'Isbn': '',
+ 'LanguageLocale': lang_code_3to2(meta.language),
+
+ 'Description': self.get_description(wlbook, shop.description_add),
+ }
+ if meta.isbn_html:
+ isbn = meta.isbn_html
+ if isbn.upper().startswith(('ISBN ', 'ISBN-')):
+ isbn = isbn[5:]
+ isbn = isbn.strip()
+ book_data['Isbn'] = isbn
+
+ files_data = {}
+
+ cover_data = self.upload(
+ (meta.url.slug + '.jpg', cover.get_file(), 'image/jpeg')
+ )
+ book_data.update({
+ "Cover.Name": cover_data['name'],
+ "Cover.Token": cover_data['token'],
+ "Cover.Url": cover_data['url'],
+ })
+
+ epub_data = self.upload(
+ (meta.url.slug + '.epub', epub_file, 'application/epub+zip')
+ )
+ files_data.update({
+ 'BookEpub.Token': epub_data['token'],
+ 'BookEpub.Name': epub_data['name'],
+ 'SampleEpubType': 'Generation',
+ })
+
+ mobi_data = self.upload(
+ (meta.url.slug + '.mobi', mobi_file, 'application/x-mobipocket-ebook')
+ )
+ files_data.update({
+ 'BookMobi.Token': mobi_data['token'],
+ 'BookMobi.Name': mobi_data['name'],
+ })
+
+ if book.legimi_id:
+ self.edit(
+ book.legimi_id,
+ book_data
+ )
+ self.edit_files(
+ book.legimi_id,
+ files_data
+ )
+ else:
+ legimi_id = self.create_book(book_data, files_data)
+ if legimi_id:
+ book.legimi_id = legimi_id
+ book.save(update_fields=['legimi_id'])
+
+ self.edit_sale(book)
+
+ def get_genre(self, wlbook):
+ if wlbook.meta.legimi and wlbook.meta.legimi in self.CATEGORIES:
+ return self.CATEGORIES[wlbook.meta.legimi]
+ for epoch in wlbook.meta.epochs:
+ if epoch in self.CATEGORIES:
+ return self.CATEGORIES[epoch]
+ return self.CATEGORIES['Lektury']
+
+ def create_book(self, book_data, files_data):
+ data = {
+ 'createValidationTrue': 'true',
+ 'PublisherId': self.publisher_handle,
+ 'IsLibraryPass': 'False',
+
+ 'SamplesGenerationType': 'Quantity',
+ 'SamplesGenerationPercent': '10',
+
+ 'EnterToTheMarketType': 'No',
+ 'EnterToTheMarketDate': '',
+ 'HidingDate': '',
+ 'SalesNoLimitOption': 'false',
+ 'SalesNoLimitKindle': 'false',
+ 'SalesInStoreEbookGrossValue': '0,00',
+ 'SalesPromotion': 'False',
+ 'SalesPromotionGrossValue': '0,00',
+ 'SalesPromotionDatesRange.DateStart': '',
+ 'SalesPromotionDatesRange.DateEnd': '',
+ }
+
+ for form in 'Epub', 'Mobi', 'Pdf':
+ data.update({
+ f'Book{form}.Token': '',
+ f'Book{form}.Name': '',
+ f'Book{form}.StorageName': '',
+ f'Book{form}.Order': '',
+
+ f'Sample{form}Type': 'Files',
+ f'Sample{form}.Token': '',
+ f'Sample{form}.Name': '',
+ f'Sample{form}.StorageName': '',
+ f'Sample{form}.Order': '',
+ })
+
+ data.update(book_data)
+ data.update(files_data)
+
+ response = self.session.post(self.CREATE_URL, data=data)
+ m = re.search(r'/(\d+)$', response.url)
+ if m is not None:
+ return m.group(1)
+
+ def edit(self, legimi_id, data):
+ current = {
+ 'ValidationTrue': 'true',
+ 'Id': legimi_id
+ }
+
+ current.update(data)
+
+ self.session.post(
+ self.EDIT_URL % legimi_id,
+ data=current
+ )
+
+ def edit_files(self, legimi_id, files_data):
+ current = {
+ 'ValidationTrue': 'true',
+ 'Id': legimi_id,
+ 'SamplesGenerationType': 'Quantity',
+ 'SamplesGenerationPercent': '10',
+ }
+
+ for form in 'Epub', 'Mobi', 'Pdf':
+ current.update({
+ f'Book{form}.Token': '',
+ f'Book{form}.Name': '',
+ f'Book{form}.StorageName': '',
+ f'Book{form}.Order': '',
+
+ f'Sample{form}.Type': 'Files',
+ f'Sample{form}.Token': '',
+ f'Sample{form}.Name': '',
+ f'Sample{form}.StorageName': '',
+ f'Sample{form}.Order': '',
+ })
+
+ current.update(files_data)
+
+ response = self.session.post(
+ self.EDIT_FILES_URL % legimi_id,
+ data=current
+ )
+
+ def edit_sale(self, book):
+ assert book.legimi_id
+
+ words = book.wldocument().get_statistics()['total']['words_with_fn']
+
+ price = settings.LEGIMI_SMALL_PRICE
+ if words > settings.LEGIMI_SMALL_WORDS:
+ price = settings.LEGIMI_BIG_PRICE
+
+ abo = 'true' if words > settings.LEGIMI_BIG_WORDS else 'false'
+
+ data = {
+ 'ValidationTrue': 'true',
+ 'Id': book.legimi_id,
+ 'SalesPromotionId': "0",
+ 'IsLibraryPass': "False",
+ 'OriginalEnterToTheMarketType': "No",
+ 'OriginalHidingDate': "",
+ 'OriginalEnterToTheMarketDate': "",
+ 'EnterToTheMarketType': "Yes",
+ 'EnterToTheMarketDate': "",
+ 'HidingDate': "",
+ 'SalesNoLimitOption': abo,
+ 'SalesNoLimitKindle': abo,
+ 'SalesInStoreEbookGrossValue': f'{price},00',
+ 'SalesPromotion': "False",
+ 'SalesPromotionGrossValue': "0,00",
+ 'SalesPromotionDatesRange.DateStart': "",
+ 'SalesPromotionDatesRange.DateEnd': "",
+ }
+
+ self.session.post(
+ self.EDIT_SALE_URL % book.legimi_id,
+ data=data
+ )
--- /dev/null
+from datetime import date
+import io
+import json
+import re
+from time import sleep
+from django.conf import settings
+from django.utils.html import escape, format_html
+from django.utils.safestring import mark_safe
+from librarian.builders.html import SnippetHtmlBuilder
+from librarian.functions import lang_code_3to2
+from catalogue.models import Author, Thema
+from .. import models
+from .base import BasePublisher
+from .woblink_constants import WOBLINK_CATEGORIES
+
+
+class WoblinkError(ValueError):
+ pass
+
+class NoPrice(WoblinkError):
+ def as_html(self):
+ return format_html(
+ 'Brak <a href="/admin/depot/shop/{price}">określonej ceny</a>.',
+ price=self.args[0].id
+ )
+
+class NoIsbn(WoblinkError):
+ def as_html(self):
+ return 'Brak ISBN.'
+
+class AuthorLiteralForeign(WoblinkError):
+ def as_html(self):
+ return format_html(
+ 'Nie obsługiwane: autor „{author}” w języku {lang}.',
+ author=str(self.args[0]),
+ lang=self.args[0].lang,
+ )
+
+class AuthorNotInCatalogue(WoblinkError):
+ def as_html(self):
+ return format_html(
+ 'Brak autora „{author}” w katalogu.',
+ author=str(self.args[0])
+ )
+
+class AuthorNoWoblink(WoblinkError):
+ def as_html(self):
+ return format_html(
+ 'Autor <a href="/admin/catalogue/author/{author_id}/">{author}</a> bez identyfikatora Woblink.',
+ author_id=self.args[0].id,
+ author=self.args[0].name
+ )
+
+class NoThema(WoblinkError):
+ def as_html(self):
+ return format_html('Brak Thema.')
+
+class UnknownThema(WoblinkError):
+ def as_html(self):
+ return format_html(
+ 'Nieznana Thema {code}.',
+ code=self.args[0]
+ )
+
+
+class ThemaUnknownWoblink(WoblinkError):
+ def as_html(self):
+ return format_html(
+ 'Thema <a href="/admin/catalogue/thema/{id}/">{code}</a> przypisana do nieznanej kategorii Woblink.',
+ id=self.args[0].id,
+ code=self.args[0].code,
+ )
+
+class NoWoblinkCategory(WoblinkError):
+ def as_html(self):
+ return 'Brak kategorii Woblink.'
+
+class WoblinkWarning(Warning):
+ pass
+
+class NoMainThemaWarning(WoblinkWarning):
+ def as_html(self):
+ return format_html(
+ 'Brak głównej kategorii Thema.'
+ )
+
+class ThemaNoWoblink(WoblinkWarning):
+ def as_html(self):
+ return format_html(
+ 'Thema <a href="/admin/catalogue/thema/{id}/">{code}</a> nie przypisana do kategorii Woblink.',
+ id=self.args[0].id,
+ code=self.args[0].code,
+ )
+
+class AuthorLiteralForeignWarning(WoblinkWarning):
+ def as_html(self):
+ return format_html(
+ 'Nie obsługiwane: autor „{author}” w języku {lang}.',
+ author=str(self.args[0]),
+ lang=self.args[0].lang,
+ )
+
+class AuthorNotInCatalogueWarning(WoblinkWarning):
+ def as_html(self):
+ return format_html(
+ 'Brak autora „{author}” w katalogu.',
+ author=str(self.args[0])
+ )
+
+class AuthorNoWoblinkWarning(WoblinkWarning):
+ def as_html(self):
+ return format_html(
+ 'Autor <a href="/admin/catalogue/author/{author_id}/">{author}</a> bez identyfikatora Woblink.',
+ author_id=self.args[0].id,
+ author=self.args[0].name
+ )
+
+
+
+
+class Woblink(BasePublisher):
+ BASE_URL = 'https://publisher.woblink.com/'
+ ADD_URL = BASE_URL + 'catalog/add'
+ STEP1_URL = BASE_URL + 'catalog/edit/%s'
+ STEP2_URL = BASE_URL + 'catalog/edit/%s/2'
+ STEP3_URL = BASE_URL + 'catalog/edit/%s/3'
+ UPLOAD_URL = BASE_URL + 'file/upload-%s'
+ JOB_STATUS_URL = BASE_URL + 'task/status'
+ GENERATE_DEMO_URL = BASE_URL + 'task/run/generate-%s-demo/%s/%d'
+ CHECK_DEMO_URL = BASE_URL + 'task/run/check-%s-demo/%s'
+
+ ROLE_AUTHOR = 1
+ ROLE_TRANSLATOR = 4
+
+ def login(self):
+ response = self.session.get('https://publisher.woblink.com/login')
+ token = re.search(
+ r'name="_csrf_token" value="([^"]+)"',
+ response.text
+ ).group(1)
+ data = {
+ '_csrf_token': token,
+ '_username': self.username,
+ '_password': self.password,
+ }
+ response = self.session.post(
+ 'https://publisher.woblink.com/login_check',
+ data=data,
+ )
+
+ def get_isbn(self, meta, errors=None):
+ if not meta.isbn_epub:
+ if errors is not None:
+ errors.append(NoIsbn())
+ return meta.isbn_epub
+
+ def get_authors_data(self, meta, errors=None):
+ authors = []
+ for role, items, obligatory in [
+ (self.ROLE_AUTHOR, meta.authors, True),
+ (self.ROLE_TRANSLATOR, meta.translators, False)
+ ]:
+ for person_literal in items:
+ if person_literal.lang != 'pl':
+ if errors is not None:
+ if obligatory:
+ errors.append(AuthorLiteralForeign(person_literal))
+ else:
+ errors.append(AuthorLiteralForeignWarning(person_literal))
+ continue
+ aobj = Author.get_by_literal(str(person_literal))
+ if aobj is None:
+ if errors is not None:
+ if obligatory:
+ errors.append(AuthorNotInCatalogue(person_literal))
+ else:
+ errors.append(AuthorNotInCatalogueWarning(person_literal))
+ continue
+ if not aobj.woblink:
+ if errors is not None:
+ if obligatory:
+ errors.append(AuthorNoWoblink(aobj))
+ else:
+ errors.append(AuthorNoWoblinkWarning(aobj))
+ continue
+ authors.append((role, aobj.woblink))
+ return authors
+
+ def get_genres(self, meta, errors=None):
+ thema_codes = []
+ if meta.thema_main:
+ thema_codes.append(meta.thema_main)
+ else:
+ if errors is not None:
+ errors.append(NoMainThemaWarning())
+ thema_codes.extend(meta.thema)
+ if not thema_codes:
+ if errors is not None:
+ errors.append(NoThema())
+ category_ids = []
+ for code in thema_codes:
+ try:
+ thema = Thema.objects.get(code=code)
+ except Thema.DoesNotExist:
+ if errors is not None:
+ errors.append(UnknownThema(code))
+ else:
+ if thema.woblink_category is None:
+ if errors is not None:
+ errors.append(ThemaNoWoblink(thema))
+ elif thema.woblink_category not in WOBLINK_CATEGORIES:
+ if errors is not None:
+ errors.append(ThemaUnknownWoblink(thema))
+ elif thema.woblink_category not in category_ids:
+ category_ids.append(thema.woblink_category)
+ if not category_ids:
+ if errors is not None:
+ errors.append(NoWoblinkCategory())
+ return category_ids
+
+ def get_series(self, meta, errors=None):
+ pass
+
+ def get_abstract(self, wldoc, errors=None, description_add=None):
+ description = self.get_description(wldoc, description_add)
+ parts = description.split('\n', 1)
+ if len(parts) == 1 or len(parts[0]) > 200:
+ p1 = description[:200].rsplit(' ', 1)[0]
+ p2 = description[len(p1):]
+ p1 += '…'
+ p2 = '…' + p2
+ parts = [p1, p2]
+
+ m = re.search(r'<[^>]+$', parts[0])
+ if m is not None:
+ parts[0] = parts[:-len(m.group(0))]
+ parts[1] = m.group(0) + parts[1]
+
+ opened = []
+ for tag in re.findall(r'<[^>]+[^/>]>', parts[0]):
+ if tag[1] == '/':
+ opened.pop()
+ else:
+ opened.append(tag)
+ for tag in reversed(opened):
+ parts[0] += '</' + tag[1:-1].split()[0] + '>'
+ parts[1] = tag + parts[1]
+ return {
+ 'header': parts[0],
+ 'rest': parts[1],
+ }
+
+ def get_lang2code(self, meta, errors=None):
+ return lang_code_3to2(meta.language)
+
+ def get_price(self, shop, wldoc, errors=None):
+ stats = wldoc.get_statistics()['total']
+ words = stats['words_with_fn']
+ pages = stats['chars_with_fn'] / 1800
+ price = shop.get_price(words, pages)
+ if price is None:
+ if errors:
+ errors.append(NoPrice(shop))
+ return 0
+
+ return price
+
+ def can_publish(self, shop, book):
+ wldoc = book.wldocument(librarian2=True)
+ d = {
+ 'warnings': [],
+ 'errors': [],
+ }
+ errors = []
+ book_data = self.get_book_data(shop, wldoc, errors)
+ for error in errors:
+ if not isinstance(error, Warning):
+ errlist = d['errors']
+ else:
+ errlist = d['warnings']
+ errlist.append(error.as_html())
+
+ if book_data.get('genres'):
+ d['comment'] = format_html(
+ 'W kategoriach: {cat} ({price} zł)',
+ cat=', '.join(self.describe_category(g) for g in book_data['genres']),
+ price=book_data['price']
+ )
+
+ return d
+
+ def describe_category(self, category):
+ t = []
+ while category:
+ c = WOBLINK_CATEGORIES[category]
+ t.append(c['name'])
+ category = c.get('parent')
+ return ' / '.join(reversed(t))
+
+ def create_book(self, isbn):
+ isbn = ''.join(c for c in isbn if c.isdigit())
+ assert len(isbn) == 13
+ response = self.session.post(
+ self.ADD_URL,
+ data={
+ 'AddPublication[pubType]': 'ebook',
+ 'AddPublication[pubHasIsbn]': '1',
+ 'AddPublication[pubIsbn]': isbn,
+ ##AddPubation[save]
+ }
+ )
+ m = re.search(r'/(\d+)$', response.url)
+ if m is not None:
+ return m.group(1)
+
+ def send_book(self, shop, book, changes=None):
+ wldoc = book.wldocument(librarian2=True, changes=changes, publishable=False) # TODO pub
+ meta = wldoc.meta
+
+ book_data = self.get_book_data(shop, wldoc)
+
+ if not book.woblink_id:
+ #book.woblink_id = 2959868
+ woblink_id = self.create_book(book_data['isbn'])
+ assert woblink_id
+ book.woblink_id = woblink_id
+ book.save(update_fields=['woblink_id'])
+
+ self.edit_step1(book.woblink_id, book_data)
+ self.edit_step2(book.woblink_id, book_data)
+ self.edit_step3(book.woblink_id, book_data)
+ self.send_cover(book.woblink_id, wldoc)
+ texts = shop.get_texts()
+ self.send_epub(
+ book.woblink_id, wldoc, book.gallery_path(),
+ fundraising=texts
+ )
+ self.send_mobi(
+ book.woblink_id, wldoc, book.gallery_path(),
+ fundraising=texts
+ )
+
+ def get_book_data(self, shop, wldoc, errors=None):
+ return {
+ "title": wldoc.meta.title,
+ "isbn": self.get_isbn(wldoc.meta, errors=errors),
+ "authors": self.get_authors_data(wldoc.meta, errors=errors),
+ "abstract": self.get_abstract(
+ wldoc, errors=errors, description_add=shop.description_add
+ ),
+ "lang2code": self.get_lang2code(wldoc.meta, errors=errors),
+ "genres": self.get_genres(wldoc.meta, errors=errors),
+ "price": self.get_price(shop, wldoc, errors=errors),
+ }
+
+ def with_form_name(self, data, name):
+ return {
+ f"{name}[{k}]": v
+ for (k, v) in data.items()
+ }
+
+ def edit_step1(self, woblink_id, book_data):
+ data = book_data
+
+ authors_data = [
+ {
+ "AhpPubId": woblink_id,
+ "AhpAutId": author_id,
+ "AhpType": author_type,
+ }
+ for (author_type, author_id) in data['authors']
+ ]
+
+ d = {
+ 'pubTitle': book_data['title'],
+ 'npwAuthorHasPublications': json.dumps(authors_data),
+ 'pubShortNote': data['abstract']['header'],
+ 'pubNote': data['abstract']['rest'],
+ 'pubCulture': data['lang2code'],
+ 'npwPublicationHasAwards': '[]',
+ 'npwPublicationHasSeriess': '[]', # TODO
+ # "[{\"Id\":6153,\"PublicationId\":73876,\"SeriesId\":1615,\"Tome\":null}]"
+ }
+ d = self.with_form_name(d, 'EditPublicationStep1')
+ d['roles'] = [author_type for (author_type, author_id) in data['authors']]
+ r = self.session.post(self.STEP1_URL % woblink_id, data=d)
+ return r
+
+
+ def edit_step2(self, woblink_id, book_data):
+ gd = {}
+ legacy = None
+ for i, g in enumerate(book_data['genres']):
+ gdata = WOBLINK_CATEGORIES[g]
+ if legacy is None:
+ legacy = gdata.get('legacy')
+ if p := gdata.get('parent'):
+ gd.setdefault(p, {'isMain': False})
+ gd[p].setdefault('children', [])
+ gd[p]['children'].append(str(g))
+ gd[p].setdefault('mainChild', str(g))
+ if legacy is None:
+ legacy = WOBLINK_CATEGORIES[p].get('legacy')
+ else:
+ gd.setdefault(p, {})
+ ds[p]['isMain'] = True
+ gd = [
+ {
+ "pubId": woblink_id,
+ "category": str(k),
+ **v
+ }
+ for k, v in gd.items()
+ ]
+
+ data = {
+ 'npwPublicationHasNewGenres': json.dumps(gd),
+ 'genre': legacy or '',
+ }
+ data = self.with_form_name(data, 'AddPublicationStep2')
+ return self.session.post(self.STEP2_URL % woblink_id, data=data)
+
+ def edit_step3(self, woblink_id, book_data):
+ d = {
+ 'pubBasePrice': book_data['price'],
+ 'pubPremiereDate': '2023-08-09', #date.today().isoformat(),
+ 'pubIsLicenseIndefinite': '1',
+ 'pubFileFormat': 'epub+mobi',
+ 'pubIsAcs': '0',
+ 'pubPublisherIndex': '',
+ }
+ d = self.with_form_name(d, 'EditPublicationStep3')
+ return self.session.post(self.STEP3_URL % woblink_id, data=d)
+
+ def wait_for_job(self, job_id):
+ while True:
+ response = self.session.post(
+ self.JOB_STATUS_URL,
+ data={'ids[]': job_id}
+ )
+ data = response.json()[job_id]
+ if data['ready']:
+ assert data['successful']
+ return
+ sleep(2)
+
+ def upload_file(self, woblink_id, filename, content, form_name, field_name, mime_type):
+ data = {
+ 'pubId': woblink_id,
+ }
+ files = {
+ field_name: (filename, content, mime_type)
+ }
+ response = self.session.post(
+ self.UPLOAD_URL % field_name,
+ data=self.with_form_name(data, form_name),
+ files=self.with_form_name(files, form_name),
+ )
+ resp_data = response.json()
+ assert resp_data['success'] is True
+ if 'jobId' in resp_data:
+ self.wait_for_job(resp_data['jobId'])
+
+ def generate_demo(self, woblink_id, file_format, check=True):
+ percent = 10
+ while True:
+ job_id = self.session.get(
+ self.GENERATE_DEMO_URL % (file_format, woblink_id, percent),
+ ).json()['jobId']
+ try:
+ self.wait_for_job(job_id)
+ except AssertionError:
+ if percent < 50:
+ percent += 10
+ else:
+ raise
+ else:
+ break
+
+ if check:
+ self.wait_for_job(
+ self.session.get(
+ self.CHECK_DEMO_URL % (file_format, woblink_id)
+ ).json()['jobId']
+ )
+
+ def send_epub(self, woblink_id, doc, gallery_path, fundraising=None):
+ from librarian.builders import EpubBuilder
+ content = EpubBuilder(
+ base_url='file://' + gallery_path + '/',
+ fundraising=fundraising or [],
+ ).build(doc).get_file()
+ self.upload_file(
+ woblink_id,
+ doc.meta.url.slug + '.epub',
+ content,
+ 'UploadEpub',
+ 'epub',
+ 'application/epub+zip'
+ )
+ self.generate_demo(woblink_id, 'epub')
+
+ def send_mobi(self, woblink_id, doc, gallery_path, fundraising=None):
+ from librarian.builders import MobiBuilder
+ content = MobiBuilder(
+ base_url='file://' + gallery_path + '/',
+ fundraising=fundraising or [],
+ ).build(doc).get_file()
+ self.upload_file(
+ woblink_id,
+ doc.meta.url.slug + '.mobi',
+ content,
+ 'UploadMobi',
+ 'mobi',
+ 'application/x-mobipocket-ebook'
+ )
+ self.generate_demo(woblink_id, 'mobi', check=False)
+
+ def send_cover(self, woblink_id, doc):
+ from librarian.cover import make_cover
+ # TODO Labe
+ # A5 @ 300ppi.
+ cover = make_cover(doc.meta, cover_class='m-label', width=1748, height=2480)
+ content = io.BytesIO()
+ cover.final_image().save(content, cover.format)
+ content.seek(0)
+ self.upload_file(
+ woblink_id,
+ doc.meta.url.slug + '.jpeg',
+ content,
+ 'UploadCover',
+ 'cover',
+ cover.mime_type()
+ )
--- /dev/null
+# This comes from Woblink publisher panel.
+
+WOBLINK_CATEGORIES = {
+ 2: {"name": "Dla dzieci", "legacy": 459},
+ 3: {"parent": 2, "name": "Baśnie dla dzieci", "legacy": 459},
+ 4: {"parent": 2, "name": "Edukacyjne, popularnonaukowe i encyklopedyczne", "legacy": 459},
+ 5: {"parent": 2, "name": "Lektury szkolne", "legacy": 459},
+ 6: {"parent": 2, "name": "Literatura religijna", "legacy": 459},
+ 7: {"parent": 2, "name": "Opowiadania", "legacy": 459},
+ 8: {"parent": 2, "name": "Powieść", "legacy": 459},
+ 9: {"parent": 2, "name": "Wiersze, rymowanki i piosenki", "legacy": 459},
+ 10: {"parent": 2, "name": "Zagadki, rozrywka", "legacy": 459},
+ 11: {"parent": 2, "name": "Inne", "legacy": 459},
+ 465: {"parent": 2, "name": "Lektury szkolne podstawówka"},
+ 469: {"parent": 2, "name": "Klasyka literatury dla dzieci"},
+ 470: {"parent": 2, "name": "Bajki i opowiadania dla dzieci"},
+ 474: {"parent": 2, "name": "Książki dla 6,7 i 8-latka"},
+ 475: {"parent": 2, "name": "Książki dla 9,10,11 i 12-latka"},
+ 478: {"parent": 2, "name": "Detektywistyczne książki dla dzieci"},
+ 480: {"parent": 2, "name": "Książki edukacyjne dla dzieci"},
+ 482: {"parent": 2, "name": "Książki dla 3,4 i 5-latka"},
+ 501: {"parent": 2, "name": "Książki przygodowe dla dzieci"},
+ 503: {"parent": 2, "name": "Książki o zwierzętach dla dzieci"},
+ 508: {"parent": 2, "name": "Książki fantastyczne dla dzieci"},
+ 512: {"parent": 2, "name": "Obcojęzyczne książki dla dzieci"},
+ 12: {"name": "Dla młodzieży", "legacy": 459},
+ 212: {"parent": 12, "name": "Poradniki Edukacja, Hobby", "legacy": 473},
+ 213: {"parent": 12, "name": "Powieści i lektury", "legacy": 453},
+ 24: {"parent": 12, "name": "Pozostałe", "legacy": 459},
+ 25: {"name": "Podręczniki i Encyklopedie", "legacy": 474},
+ 27: {"parent": 25, "name": "Słowniki i informatory tematyczne", "legacy": 474},
+ 198: {"parent": 25, "name": "Podręczniki i materiały edukacyjne", "legacy": 474},
+ 208: {"parent": 25, "name": "Inne", "legacy": 474},
+ 26: {"parent": 25, "name": "Encyklopedie", "legacy": 474},
+ 200: {"parent": 25, "name": "Podręczniki akademickie", "legacy": 474},
+ 199: {"parent": 25, "name": "Podręczniki szkolne", "legacy": 474},
+ 219: {"name": "Nauka języków obcych", "legacy": 474},
+ 201: {"parent": 219, "name": "Książki do nauki angielskiego", "legacy": 474},
+ 202: {"parent": 219, "name": "Książki do nauki niemieckiego", "legacy": 474},
+ 203: {"parent": 219, "name": "Książki do nauki francuskiego", "legacy": 474},
+ 204: {"parent": 219, "name": "Książki do nauki hiszpańskiego", "legacy": 474},
+ 205: {"parent": 219, "name": "Książki do nauki włoskiego", "legacy": 474},
+ 206: {"parent": 219, "name": "Książki do nauki rosyjskiego", "legacy": 474},
+ 207: {"parent": 219, "name": "Pozostałe książki do nauki języków", "legacy": 474},
+ 28: {"parent": 219, "name": "Słowniki językowe", "legacy": 474},
+ 29: {"parent": 219, "name": "Pozostałe", "legacy": 474},
+ 498: {"parent": 219, "name": "Książki do nauki ukraińskiego"},
+ 499: {"parent": 219, "name": "Język polski dla obcokrajowców"},
+ 30: {"name": "Książki o filozofii", "legacy": 461},
+ 31: {"parent": 30, "name": "Epistemologia", "legacy": 461},
+ 32: {"parent": 30, "name": "Etyka", "legacy": 461},
+ 33: {"parent": 30, "name": "Historia filozofii", "legacy": 461},
+ 34: {"parent": 30, "name": "Logika", "legacy": 461},
+ 35: {"parent": 30, "name": "Ontologia", "legacy": 461},
+ 36: {"parent": 30, "name": "Szkoły i kierunki w filozofii", "legacy": 461},
+ 37: {"parent": 30, "name": "Inne", "legacy": 461},
+ 473: {"parent": 30, "name": "Klasycy filozofii"},
+ 38: {"name": "Książki historyczne", "legacy": 464},
+ 214: {"parent": 38, "name": "Książki o średniowieczu", "legacy": 464},
+ 215: {"parent": 38, "name": "Książki o nowożytności", "legacy": 464},
+ 216: {"parent": 38, "name": "Książki o historii najnowszej", "legacy": 464},
+ 43: {"parent": 38, "name": "II wojna światowa", "legacy": 464},
+ 45: {"parent": 38, "name": "Pozostałe", "legacy": 464},
+ 489: {"parent": 38, "name": "Książki o historii Polski"},
+ 490: {"parent": 38, "name": "Historiografia"},
+ 491: {"parent": 38, "name": "Książki o historii Europy i świata"},
+ 492: {"parent": 38, "name": "Książki o militariach i historii wojskowości"},
+ 493: {"parent": 38, "name": "Książki o archeologii"},
+ 40: {"parent": 38, "name": "Książki o starożytności", "legacy": 464},
+ 46: {"name": "Komiks i książka graficzna", "legacy": 452},
+ 47: {"name": "Internet, komputery, informatyka", "legacy": 485},
+ 48: {"parent": 47, "name": "Informatyka - zagadnienia ogólne", "legacy": 485},
+ 49: {"parent": 47, "name": "Bazy danych, analiza danych", "legacy": 485},
+ 51: {"parent": 47, "name": "Internet, media elektroniczne", "legacy": 485},
+ 50: {"parent": 47, "name": "Cyfrowy styl życia", "legacy": 485},
+ 52: {"parent": 47, "name": "Języki programowania i programowanie", "legacy": 485},
+ 53: {"parent": 47, "name": "Komputery - hardware", "legacy": 485},
+ 54: {"parent": 47, "name": "Komputery - oprogramowanie", "legacy": 485},
+ 55: {"parent": 47, "name": "Systemy operacyjne", "legacy": 485},
+ 56: {"parent": 47, "name": "Inne", "legacy": 485},
+ 57: {"name": "Książki obcojęzyczne", "legacy": 482},
+ 58: {"parent": 57, "name": "Język angielski", "legacy": 482},
+ 59: {"parent": 57, "name": "Inne", "legacy": 482},
+ 60: {"name": "Kultura, Sztuka, Design", "legacy": 477},
+ 70: {"parent": 60, "name": "Design / Architektura", "legacy": 477},
+ 62: {"parent": 60, "name": "Architektura", "legacy": 477},
+ 67: {"parent": 60, "name": "Kino / Teatr / Telewizja", "legacy": 477},
+ 64: {"parent": 60, "name": "Fotografia / Malarstwo / Rzeźba", "legacy": 477},
+ 66: {"parent": 60, "name": "Rzeźba", "legacy": 477},
+ 63: {"parent": 60, "name": "Fotografia", "legacy": 477},
+ 61: {"parent": 60, "name": "Historia i teoria sztuki", "legacy": 477},
+ 65: {"parent": 60, "name": "Muzyka / Taniec", "legacy": 477},
+ 71: {"parent": 60, "name": "Taniec", "legacy": 477},
+ 72: {"parent": 60, "name": "Pozostałe", "legacy": 477},
+ 68: {"parent": 60, "name": "Konserwacja sztuki", "legacy": 477},
+ 69: {"parent": 60, "name": "Wystawy i muzea", "legacy": 477},
+ 73: {"name": "Literatura", "legacy": 478},
+ 83: {"parent": 73, "name": "Romans", "legacy": 484},
+ 74: {"parent": 73, "name": "Fantastyka i Horror", "legacy": 456},
+ 77: {"parent": 73, "name": "Literatura piękna", "legacy": 478},
+ 78: {"parent": 73, "name": "Poezja i Dramat", "legacy": 476},
+ 79: {"parent": 73, "name": "Powieść historyczna", "legacy": 478},
+ 80: {"parent": 73, "name": "Powieść obyczajowa", "legacy": 458},
+ 82: {"parent": 73, "name": "Kryminał i Sensacja", "legacy": 457},
+ 84: {"parent": 73, "name": "Pozostałe", "legacy": 485},
+ 464: {"parent": 73, "name": "Erotyka", "legacy": 484},
+ 85: {"name": "Literatura faktu", "legacy": 460},
+ 89: {"parent": 85, "name": "Literatura popularnonaukowa", "legacy": 472},
+ 186: {"parent": 85, "name": "Książki podróżnicze i Przewodniki", "legacy": 463},
+ 92: {"parent": 85, "name": "Pozostałe", "legacy": 460},
+ 93: {"name": "Marketing, Zarządzanie, Finanse", "legacy": 471},
+ 94: {"parent": 93, "name": "Finanse i rachunkowość", "legacy": 485},
+ 95: {"parent": 93, "name": "Biznes / Przedsiębiorczość / Rozwój osobisty", "legacy": 485},
+ 96: {"parent": 93, "name": "E-biznes", "legacy": 485},
+ 101: {"parent": 93, "name": "Zarządzanie i administracja", "legacy": 485},
+ 99: {"parent": 93, "name": "Psychologia biznesu", "legacy": 485},
+ 100: {"parent": 93, "name": "Szkolenia i kariera", "legacy": 485},
+ 97: {"parent": 93, "name": "Ekonomia", "legacy": 485},
+ 98: {"parent": 93, "name": "Marketing i sprzedaż", "legacy": 485},
+ 102: {"parent": 93, "name": "Pozostałe", "legacy": 485},
+ 110: {"name": "Psychologia, Społeczeństwo, Polityka", "legacy": 485},
+ 111: {"parent": 110, "name": "Edukacja i pedagogika", "legacy": 485},
+ 112: {"parent": 110, "name": "Polityka i administracja państwowa", "legacy": 485},
+ 113: {"parent": 110, "name": "Psychologia", "legacy": 485},
+ 114: {"parent": 110, "name": "Służby publiczne, socjalne, kryminologia, wojsko", "legacy": 485},
+ 115: {"parent": 110, "name": "Socjologia i antropologia", "legacy": 485},
+ 116: {"parent": 110, "name": "Gender\xa0studies", "legacy": 479},
+ 117: {"parent": 110, "name": "Inne", "legacy": 485},
+ 118: {"name": "Nauki ścisłe i Medycyna", "legacy": 485},
+ 103: {"parent": 118, "name": "Medycyna", "legacy": 480},
+ 104: {"parent": 118, "name": "Medycyna ogólnie", "legacy": 480},
+ 105: {"parent": 118, "name": "Zagadnienia specjalistyczne", "legacy": 480},
+ 106: {"parent": 118, "name": "Pielęgniarstwo", "legacy": 480},
+ 107: {"parent": 118, "name": "Weterynaria", "legacy": 480},
+ 108: {"parent": 118, "name": "Inne", "legacy": 480},
+ 109: {"parent": 118, "name": "Nauki społeczne i społeczeństwo", "legacy": 485},
+ 122: {"parent": 118, "name": "Biologia / Geografia / Ekologia", "legacy": 485},
+ 125: {"parent": 118, "name": "Geografia i nauki o ziemi", "legacy": 485},
+ 123: {"parent": 118, "name": "Ekologia i środowisko", "legacy": 485},
+ 121: {"parent": 118, "name": "Biologia i nauki pokrewne", "legacy": 485},
+ 124: {"parent": 118, "name": "Astronomia / Fizyka / Matematyka", "legacy": 485},
+ 120: {"parent": 118, "name": "Astronomia", "legacy": 485},
+ 126: {"parent": 118, "name": "Matematyka", "legacy": 485},
+ 128: {"parent": 118, "name": "Pozostałe", "legacy": 485},
+ 119: {"parent": 118, "name": "Nauka - zagadnienia ogólne", "legacy": 485},
+ 127: {"parent": 118, "name": "Planowanie przestrzenne i regionalne, urbanistyka", "legacy": 485},
+ 217: {"name": "Poradniki", "legacy": 473},
+ 129: {"name": "Prawo", "legacy": 470},
+ 130: {"parent": 129, "name": "Akty prawne, komentarze i\xa0wzory dokumentów", "legacy": 470},
+ 131: {"parent": 129, "name": "Prawo - teoria i zagadnienia ogólne", "legacy": 470},
+ 132: {"parent": 129, "name": "Prawo administracyjne", "legacy": 470},
+ 133: {"parent": 129, "name": "Prawo cywilne", "legacy": 470},
+ 134: {"parent": 129, "name": "Prawo finansowe", "legacy": 470},
+ 135: {"parent": 129, "name": "Prawo handlowe i gospodarcze", "legacy": 470},
+ 136: {"parent": 129, "name": "Prawo karne", "legacy": 470},
+ 137: {"parent": 129, "name": "Prawo międzynarodowe i europejskie", "legacy": 470},
+ 138: {"parent": 129, "name": "Prawo pracy i kodeks pracy", "legacy": 470},
+ 139: {"parent": 129, "name": "Prawo własności intelektualnej", "legacy": 470},
+ 140: {"parent": 129, "name": "Szczegółowe zagadnienia prawa - pozostałe", "legacy": 470},
+ 141: {"parent": 129, "name": "Inne", "legacy": 470},
+ 497: {"parent": 129, "name": "Prawo konstytucyjne"},
+ 142: {"name": "Religia", "legacy": 467},
+ 143: {"parent": 142, "name": "Literatura religijna", "legacy": 467},
+ 144: {"parent": 142, "name": "Religioznawstwo", "legacy": 467},
+ 145: {"parent": 142, "name": "Inne", "legacy": 467},
+ 146: {"name": "Sport i Rekreacja", "legacy": 481},
+ 147: {"parent": 146, "name": "Bieganie i lekkoatletyka", "legacy": 481},
+ 148: {"parent": 146, "name": "Fitness i joga", "legacy": 481},
+ 149: {"parent": 146, "name": "Jeździectwo", "legacy": 481},
+ 150: {"parent": 146, "name": "Kolarstwo", "legacy": 481},
+ 151: {"parent": 146, "name": "Koszykówka", "legacy": 481},
+ 152: {"parent": 146, "name": "Narciarstwo, sporty zimowe", "legacy": 481},
+ 153: {"parent": 146, "name": "Piłka nożna", "legacy": 481},
+ 154: {"parent": 146, "name": "Siatkówka", "legacy": 481},
+ 155: {"parent": 146, "name": "Aktywny wypoczynek", "legacy": 481},
+ 156: {"parent": 146, "name": "Sporty powietrzne", "legacy": 481},
+ 157: {"parent": 146, "name": "Sporty walki", "legacy": 481},
+ 158: {"parent": 146, "name": "Sporty wodne", "legacy": 481},
+ 159: {"parent": 146, "name": "Tenis", "legacy": 481},
+ 160: {"parent": 146, "name": "Wspinaczka, turystyka górska", "legacy": 481},
+ 161: {"parent": 146, "name": "Imprezy sportowe i współzawodnictwo", "legacy": 481},
+ 162: {"parent": 146, "name": "Inne", "legacy": 481},
+ 163: {"name": "Technika, Inżynieria, Rolnictwo", "legacy": 485},
+ 164: {"parent": 163, "name": "Chemia przemysłowa i technologie przemysłowe", "legacy": 485},
+ 165: {"parent": 163, "name": "Elektronika, automatyka", "legacy": 485},
+ 166: {"parent": 163, "name": "Energetyka", "legacy": 485},
+ 167: {"parent": 163, "name": "Inżynieria biochemiczna", "legacy": 485},
+ 168: {"parent": 163, "name": "Inżynieria cywilna, wodna, bezpieczeństwa i transportu", "legacy": 485},
+ 169: {"parent": 163, "name": "Inżynieria mechaniczna i materiałowa", "legacy": 485},
+ 170: {"parent": 163, "name": "Inżynieria środowiska", "legacy": 485},
+ 171: {"parent": 163, "name": "Rolnictwo", "legacy": 485},
+ 172: {"parent": 163, "name": "Technologia - zagadnienia", "legacy": 485},
+ 173: {"parent": 163, "name": "Telekomunikacja", "legacy": 485},
+ 174: {"parent": 163, "name": "Transport i mechanika", "legacy": 485},
+ 175: {"parent": 163, "name": "Inne technologie", "legacy": 485},
+ 176: {"parent": 163, "name": "Inne", "legacy": 485},
+ 177: {"name": "Dom, Moda, Hobby", "legacy": 485},
+ 179: {"parent": 177, "name": "Dom, Wnętrze, Ogród", "legacy": 485},
+ 188: {"parent": 177, "name": "Moda i styl", "legacy": 485},
+ 220: {"parent": 177, "name": "Kolorowanki", "legacy": 485},
+ 191: {"name": "Kuchnia , Diety i Fitness", "legacy": 480},
+ 189: {"parent": 191, "name": "Zainteresowania przyrodnicze", "legacy": 485},
+ 183: {"parent": 191, "name": "Kuchnia", "legacy": 469},
+ 193: {"parent": 191, "name": "Diety / Fitness", "legacy": 480},
+ 218: {"name": "Zdrowie, Rodzina, Związki", "legacy": 473},
+ 192: {"parent": 218, "name": "Dziecko, rodzina i zdrowie", "legacy": 480},
+ 194: {"parent": 218, "name": "Seks - poradniki", "legacy": 480},
+ 195: {"parent": 218, "name": "Umysł, ciało, duch", "legacy": 480},
+ 196: {"parent": 218, "name": "Zdrowie i rozwój osobisty - poradniki", "legacy": 480},
+ 197: {"parent": 218, "name": "Inne", "legacy": 480},
+ 209: {"name": "Prasa", "legacy": 483},
+ 210: {"name": "Pozostałe", "legacy": 485},
+ 462: {"name": "Minibooki", "legacy": 486},
+ 91: {"name": "Reportaż"},
+ 466: {"parent": 91, "name": "Polska szkoła reportażu"},
+ 467: {"parent": 91, "name": "Książki true crime"},
+ 468: {"parent": 91, "name": "Reportaż podróżniczy"},
+ 476: {"parent": 91, "name": "Reportaż wojenny"},
+ 477: {"parent": 91, "name": "Reportaż o Polsce"},
+ 86: {"name": "Biografie i wspomnienia", "legacy": 454},
+ 87: {"parent": 86, "name": "Eseistyka", "legacy": 460},
+ 88: {"parent": 86, "name": "Listy", "legacy": 460},
+ 90: {"parent": 86, "name": "Pamiętniki i\xa0dzienniki", "legacy": 460},
+ 483: {"parent": 86, "name": "Biografie sławnych ludzi"},
+ 484: {"parent": 86, "name": "Biografie muzyków"},
+ 485: {"parent": 86, "name": "Biografie pisarzy"},
+ 486: {"parent": 86, "name": "Autobiografie"},
+ 488: {"parent": 86, "name": "Biografie sportowców"},
+ 494: {"parent": 86, "name": "Biografie postaci historycznych"},
+ 496: {"parent": 86, "name": "Biografie świętych (Hagiografia)"},
+ 500: {"parent": 86, "name": "Biografie polityków"},
+}
--- /dev/null
+from django.template import Library
+from depot.models import Shop
+
+
+register = Library()
+
+
+@register.simple_tag(takes_context=True)
+def depot_shops(context, book):
+ shops = []
+ for shop in Shop.objects.all():
+ d = {
+ 'shop_id': shop.id,
+ 'name': shop.name,
+ }
+ d.update(shop.can_publish(book))
+ d['last'] = shop.get_last(book)
+ d['id'] = getattr(book, shop.shop + '_id')
+ shops.append(d)
+ return shops
urlpatterns = [
- path(
- 'legimi-publish/<int:book_id>/',
- views.LegimiPublishView.as_view(),
- name='depot_legimi_publish'
+ path('shop-publish/<int:shop_id>/<int:book_id>/',
+ views.ShopPublishView.as_view(),
+ name='depot_shop_publish'
)
]
from . import models
-class LegimiPublishView(PermissionRequiredMixin, View):
- permission_required = 'depot.add_legimibookpublish'
+class ShopPublishView(PermissionRequiredMixin, View):
+ permission_required = 'depot.add_shopbookpublish'
- def post(self, request, book_id):
+ def post(self, request, shop_id, book_id):
+ shop = get_object_or_404(models.Shop, pk=shop_id)
book = get_object_or_404(Book, pk=book_id)
try:
- publish = models.LegimiBookPublish.create_for(book, request.user)
+ publish = models.ShopBookPublish.create_for(book, request.user, shop)
except AssertionError:
pass
return redirect(book.get_absolute_url())
+++ /dev/null
-import re
-from django.conf import settings
-import requests
-
-
-def get_woblink_session(*args, **kwargs):
- session = requests.Session()
- response = session.get('https://publisher.woblink.com/login')
- token = re.search(
- r'name="_csrf_token" value="([^"]+)"',
- response.text
- ).group(1)
- data = {
- '_csrf_token': token,
- }
- data.update(settings.WOBLINK_CREDENTIALS)
- response = session.post(
- 'https://publisher.woblink.com/login_check',
- data=data,
- )
- return session
-
-
-
class Meta:
model = Book
- exclude = ['parent', 'parent_number', 'project']
+ fields = [
+ 'title', 'slug', 'public', 'gallery'
+ ]
def __init__(self, *args, **kwargs):
super(DocumentCreateForm, self).__init__(*args, **kwargs)
class Meta:
model = Book
- exclude = ['project', 'cover', 'legimi_id']
+ exclude = ['project', 'cover', 'legimi_id', 'woblink_id']
def __init__(self, *args, **kwargs):
ret = super(BookForm, self).__init__(*args, **kwargs)
--- /dev/null
+# Generated by Django 4.1.9 on 2023-08-04 14:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("documents", "0010_alter_book_cover"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="book",
+ name="woblink_id",
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ ]
related_query_name='document_book',
)
legimi_id = models.CharField(max_length=255, blank=True)
+ woblink_id = models.CharField(max_length=255, blank=True)
class NoTextError(BaseException):
pass
except IndexError:
return None
- def last_legimi_publish(self):
- return self.legimibookpublish_set.order_by('-created_at').first()
-
def assert_publishable(self):
assert self.chunk_set.exists(), _('No chunks in the book.')
try:
{% extends "documents/base.html" %}
{% load book_list i18n %}
{% load bootstrap4 %}
+{% load depot %}
{% block titleextra %}{{ book.title }}{% endblock %}
<div class="col-md-8">
-{% if editable %}<form method='POST'>{% csrf_token %}{% endif %}
- {% bootstrap_form form %}
- {% if editable %}
- {% buttons %}
- <button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
+ {% if editable %}<form method='POST'>{% csrf_token %}{% endif %}
+ {% bootstrap_form form %}
+ {% if editable %}
+ {% buttons %}
+ <button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
{% endbuttons %}
- {% endif %}
-{% if editable %}</form>{% endif %}
+ {% endif %}
+ {% if editable %}</form>{% endif %}
-{% if editable %}
- {% if book.gallery %}
- <p><a href="{% url 'documents_book_gallery' book.slug %}">{% trans "Edit gallery" %}</a></p>
- {% endif %}
+ {% if editable %}
+ {% if book.gallery %}
+ <p><a href="{% url 'documents_book_gallery' book.slug %}">{% trans "Edit gallery" %}</a></p>
+ {% endif %}
- <p style="text-align:right"><a class="btn btn-sm btn-danger" href="{% url 'documents_book_append' book.slug %}">{% trans "Append to other book" %}</a></p>
-{% endif %}
+ <p style="text-align:right"><a class="btn btn-sm btn-danger" href="{% url 'documents_book_append' book.slug %}">{% trans "Append to other book" %}</a></p>
+ {% endif %}
</div>
</div>
<div class="card-body">
- <table class='single-book-list table'><tbody>
- {% for chunk in book %}
- {% include 'documents/book_list/chunk.html' %}
- {% endfor %}
- </tbody></table>
+ <table class='single-book-list table'><tbody>
+ {% for chunk in book %}
+ {% include 'documents/book_list/chunk.html' %}
+ {% endfor %}
+ </tbody></table>
</div>
</div>
-<div class='card mt-4'>
+ <div class='card mt-4'>
-<div class="card-header">
- <h2>{% trans "Publication" %}</h2>
- </div>
-<div class="card-body">
- <div class="row">
- <div class="col-lg-3">
- <img class="cover-preview" src="{% url 'cover_preview' book.slug %}" />
- {% if book.dc_cover_image %}
- <a href="{{ book.dc_cover_image.get_absolute_url }}">{{ book.dc_cover_image }}</a>
- {% endif %}
- <br><br>
-
- <form action="{% url 'cover_quick_import' book.pk %}" method="post">
- {% csrf_token %}
- <div class="input-group">
- <input type="url" name="url" class="form-control" placeholder="URL okładki">
- <button type="submit" class="btn btn-sm btn-info">Ustaw</button>
- </div>
- </form>
-
-
- <br><br>
- <form action="{% url 'cover_preview' book.slug %}">
- <input type="hidden" name="download" value="1">
- Pobierz okładkę:
- <div class="input-group">
- <select class="form-control" name='cover_class'>
- <option value="default">nowa</option>
- <option value="m-label">nowa + label</option>
- <option value="legacy">dawna</option>
- </select><br>
- <input class="form-control" name="width" type="number" required value="600" size="3" placeholder="szer.">
- <input class="form-control" name="height" type="number" size="3" placeholder="wys.">
- <button type="submit" class="btn btn-sm btn-primary">🡇</button>
- </div>
- </form>
+ <div class="card-header">
+ <h2>{% trans "Publication" %}</h2>
</div>
- <div class="col-lg-9">
-<p>{% trans "Last published" %}:
- {% if book.last_published %}
- {{ book.last_published }}
- {% else %}
- —
- {% endif %}
-</p>
-
-{% if publishable %}
- <p>
- <a href="{% url 'documents_book_xml' book.slug %}" rel="nofollow">{% trans "Full XML" %}</a><br/>
- <a target="_blank" href="{% url 'documents_book_html' book.slug %}" rel="nofollow">{% trans "HTML version" %}</a><br/>
- <a href="{% url 'documents_book_txt' book.slug %}" rel="nofollow">{% trans "TXT version" %}</a><br/>
- <a href="{% url 'documents_book_pdf' book.slug %}" rel="nofollow">{% trans "PDF version" %}</a><br/>
- <a href="{% url 'documents_book_pdf_mobile' book.slug %}" rel="nofollow">{% trans "PDF version for mobiles" %}</a><br/>
- <a href="{% url 'documents_book_epub' book.slug %}" rel="nofollow">{% trans "EPUB version" %}</a><br/>
- <a href="{% url 'documents_book_mobi' book.slug %}" rel="nofollow">{% trans "MOBI version" %}</a><br/>
- </p>
-
- {% if user.is_authenticated %}
- <!--
- Angel photos:
- Angels in Ely Cathedral (http://www.flickr.com/photos/21804434@N02/4483220595/) /
- mira66 (http://www.flickr.com/photos/21804434@N02/) /
- CC BY 2.0 (http://creativecommons.org/licenses/by/2.0/)
- -->
- <form method="POST" action="{% url 'documents_publish' book.slug %}">{% csrf_token %}
- {{ publish_options_form.as_p }}
- <img src="{{ STATIC_URL }}img/angel-left.png" style="vertical-align: middle" />
- <button id="publish-button" type="submit">
- <span>{% trans "Publish" %}</span></button>
- <img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" />
- </form>
-
- {% if perms.depot.add_legimibookpublish %}
- <hr>
- {% with thema_main=doc.book_info.thema_main thema=doc.book_info.thema %}
- {% if thema_main or thema %}
- <form method="post" action="{% url 'depot_legimi_publish' book.pk %}">
- {% csrf_token %}
- <button class="btn btn-primary" type="submit">
- Opublikuj na Legimi<br><small>w kategorii:
- {% if thema_main %}
- <tt>{{ thema_main }}</tt>
+ <div class="card-body">
+ <div class="row">
+ <div class="col-lg-3">
+ <img class="cover-preview" src="{% url 'cover_preview' book.slug %}" />
+ {% if book.dc_cover_image %}
+ <a href="{{ book.dc_cover_image.get_absolute_url }}">{{ book.dc_cover_image }}</a>
+ {% endif %}
+ <br><br>
+
+ <form action="{% url 'cover_quick_import' book.pk %}" method="post">
+ {% csrf_token %}
+ <div class="input-group">
+ <input type="url" name="url" class="form-control" placeholder="URL okładki">
+ <button type="submit" class="btn btn-sm btn-info">Ustaw</button>
+ </div>
+ </form>
+
+
+ <br><br>
+ <form action="{% url 'cover_preview' book.slug %}">
+ <input type="hidden" name="download" value="1">
+ Pobierz okładkę:
+ <div class="input-group">
+ <select class="form-control" name='cover_class'>
+ <option value="default">nowa</option>
+ <option value="m-label">nowa + label</option>
+ <option value="legacy">dawna</option>
+ </select><br>
+ <input class="form-control" name="width" type="number" required value="600" size="3" placeholder="szer.">
+ <input class="form-control" name="height" type="number" size="3" placeholder="wys.">
+ <button type="submit" class="btn btn-sm btn-primary">🡇</button>
+ </div>
+ </form>
+ </div>
+ <div class="col-lg-9">
+ <p>{% trans "Last published" %}:
+ {% if book.last_published %}
+ {{ book.last_published }}
+ {% else %}
+ —
+ {% endif %}
+ </p>
+
+ {% if publishable %}
+ <p>
+ <a href="{% url 'documents_book_xml' book.slug %}" rel="nofollow">{% trans "Full XML" %}</a><br/>
+ <a target="_blank" href="{% url 'documents_book_html' book.slug %}" rel="nofollow">{% trans "HTML version" %}</a><br/>
+ <a href="{% url 'documents_book_txt' book.slug %}" rel="nofollow">{% trans "TXT version" %}</a><br/>
+ <a href="{% url 'documents_book_pdf' book.slug %}" rel="nofollow">{% trans "PDF version" %}</a><br/>
+ <a href="{% url 'documents_book_pdf_mobile' book.slug %}" rel="nofollow">{% trans "PDF version for mobiles" %}</a><br/>
+ <a href="{% url 'documents_book_epub' book.slug %}" rel="nofollow">{% trans "EPUB version" %}</a><br/>
+ <a href="{% url 'documents_book_mobi' book.slug %}" rel="nofollow">{% trans "MOBI version" %}</a><br/>
+ </p>
+
+ {% if user.is_authenticated %}
+ <!--
+ Angel photos:
+ Angels in Ely Cathedral (http://www.flickr.com/photos/21804434@N02/4483220595/) /
+ mira66 (http://www.flickr.com/photos/21804434@N02/) /
+ CC BY 2.0 (http://creativecommons.org/licenses/by/2.0/)
+ -->
+ <form method="POST" action="{% url 'documents_publish' book.slug %}">{% csrf_token %}
+ {{ publish_options_form.as_p }}
+ <img src="{{ STATIC_URL }}img/angel-left.png" style="vertical-align: middle" />
+ <button id="publish-button" type="submit">
+ <span>{% trans "Publish" %}</span></button>
+ <img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" />
+ </form>
+
+
+ {% if perms.depot.add_shopbookpublish %}
+ {% depot_shops book as shops %}
+ {% for shop in shops %}
+ <hr>
+ <h3 class="mb-3">{{ shop.name }}</h3>
+ {% if not shop.errors %}
+ <form method="post" action="{% url 'depot_shop_publish' shop.shop_id book.pk %}">
+ {% csrf_token %}
+ <button class="btn btn-primary mb-3" type="submit">
+ Opublikuj na {{ shop.name }}
+ </button>
+ </form>
+ {% if shop.comment %}
+ <div class="alert alert-info">
+ {{ shop.comment }}
+ </div>
+ {% endif %}
+ {% else %}
+ {% for error in shop.errors %}
+ <div class="alert alert-danger">
+ {{ error }}
+ </div>
+ {% endfor %}
{% endif %}
- {% for t in thema %}
- {% if forloop.first and thema_main %}oraz: {% endif %}
- <tt>{{ t }}</tt>
- {% if not forloop.last %}, {% endif %}
+ {% for warning in shop.warnings %}
+ <div class="alert alert-warning">
+ {{ warning }}
+ </div>
{% endfor %}
- {% if not thema_main %}
- <span class="badge badge-secondary" title="Nie ustalono głównej kategorii Thema"> * </small>
+ {% if shop.id %}
+ id:{{ shop.id }}
{% endif %}
- </small></button>
- {% with llp=book.last_legimi_publish %}
- {% if llp %}
- {{ llp.created_at }} →
- {{ llp.started_at }} →
- {{ llp.finished_at }}
- ({{ llp.get_status_display }})
- <!-- {{ llp.id }} -->
+ {% with last=shop.last %}
+ {% if last %}
+ {{ last.created_at }} →
+ {{ last.started_at }} →
+ {{ last.finished_at }}
+ <span title="{{ last.error }}">
+ ({{ last.get_status_display }})
+ </span>
+ <!-- {{ shop.last.id }} -->
{% endif %}
- {% endwith %}
- </form>
+ {% endwith %}
+ {% endfor %}
+ {% endif %}
+
{% else %}
- <div class="alert alert-warning">Nie można opublikować na Legimi, ponieważ nie ustalono kategorii Thema.</div>
+ <a href="{% url 'cas_ng_login' %}">{% trans "Log in to publish." %}</a>
{% endif %}
- {% endwith %}
- {% endif %}
-
- {% else %}
- <a href="{% url 'cas_ng_login' %}">{% trans "Log in to publish." %}</a>
- {% endif %}
-{% else %}
- <p>{% trans "This book can't be published yet, because:" %}</p>
- <ul><li>{{ publishable_error }}</li></ul>
-{% endif %}
-
-
-
+ {% else %}
+ <p>{% trans "This book can't be published yet, because:" %}</p>
+ <ul><li>{{ publishable_error }}</li></ul>
+ {% endif %}
+ </div>
+ </div>
</div>
</div>
-</div>
-</div>
-{% if doc %}
- <div class="card mt-4">
- <div class="card-header">
- <h2>{% trans "Statistics" %}</h2>
- </div>
- <div class="card-body">
- <table class="table">
- <thead>
- <tr>
- <th>
- {% trans "book" %}
- </th>
- <th>{% trans "characters" %}</th>
- <th>{% trans "characters (with footnotes)" %}</th>
- <th>{% trans "words" %}</th>
- <th>{% trans "words (with footnotes)" %}</th>
- <th>{% trans "wiersze (+ inne znaki)" %}</th>
- <th>{% trans "wiersze (+ inne znaki, z przypisami)" %}</th>
- </tr>
- </thead>
- <tbody>
- {% with stats=doc.get_statistics %}
- {% include 'documents/book_stats.html' with book=book stats=stats depth=0 %}
+ {% if doc %}
+ <div class="card mt-4">
+ <div class="card-header">
+ <h2>{% trans "Statistics" %}</h2>
+ </div>
+ <div class="card-body">
+ <table class="table">
+ <thead>
+ <tr>
+ <th>
+ {% trans "book" %}
+ </th>
+ <th>{% trans "characters" %}</th>
+ <th>{% trans "characters (with footnotes)" %}</th>
+ <th>{% trans "words" %}</th>
+ <th>{% trans "words (with footnotes)" %}</th>
+ <th>{% trans "wiersze (+ inne znaki)" %}</th>
+ <th>{% trans "wiersze (+ inne znaki, z przypisami)" %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% with stats=doc.get_statistics %}
+ {% include 'documents/book_stats.html' with book=doc stats=stats depth=0 %}
{% endwith %}
</tbody>
</table>
<tr>
- <td style="padding-left: {{ depth}}.75em;">{{ book.book_info.title|default:"???" }}</td>
- <td>{{ stats.self.chars|default_if_none:"?" }}</td>
- <td>{{ stats.self.chars_with_fn|default_if_none:"?" }}</td>
- <td>{{ stats.self.words|default_if_none:"?" }}</td>
- <td>{{ stats.self.words_with_fn|default_if_none:"?" }}</td>
- <td>{{ stats.self.verses|default_if_none:"?" }} <small>+ {{ stats.self.chars_out_verse|default_if_none:"?" }} zn.</small></td>
- <td>{{ stats.self.verses_with_fn|default_if_none:"?" }} <small> + {{ stats.self.chars_out_verse_with_fn|default_if_none:"?" }} zn.</small></td>
+ <td style="padding-left: {{ depth}}.75em;">
+ {{ book.meta.title|default:"???" }}
+ </td>
+ {% if stats.error %}
+ <td colspan="6">
+ {{ stats.error }}
+ </td>
+ {% else %}
+ <td>{{ stats.self.chars|default_if_none:"?" }}</td>
+ <td>{{ stats.self.chars_with_fn|default_if_none:"?" }}</td>
+ <td>{{ stats.self.words|default_if_none:"?" }}</td>
+ <td>{{ stats.self.words_with_fn|default_if_none:"?" }}</td>
+ <td>{{ stats.self.verses|default_if_none:"?" }} <small>+ {{ stats.self.chars_out_verse|default_if_none:"?" }} zn.</small></td>
+ <td>{{ stats.self.verses_with_fn|default_if_none:"?" }} <small> + {{ stats.self.chars_out_verse_with_fn|default_if_none:"?" }} zn.</small></td>
+ {% endif %}
</tr>
{% if stats.parts %}
{% for part, partstats in stats.parts %}
publishable = publish_error is None
try:
- doc = book.wldocument()
+ doc = book.wldocument(librarian2=True)
except:
doc = None
-
+
return render(request, "documents/book_detail.html", {
"book": book,
"doc": doc,
from librarian.builders import StandaloneHtmlBuilder
from librarian.meta.types.wluri import WLURI
from librarian.meta.types.text import LegimiCategory, Epoch, Kind, Genre, Audience, ThemaCategory, MainThemaCategory
-from depot.legimi import legimi
+from depot.publishers.legimi import Legimi
class XslView(TemplateView):
VALUE_TYPES = {
LegimiCategory: {
'widget': 'select',
- 'options': [''] + list(legimi.CATEGORIES.keys()),
+ 'options': [''] + list(Legimi.CATEGORIES.keys()),
},
Audience: {
'autocomplete': {