view_name='catalogue_api_book',
lookup_field='slug'
)
+ children = serializers.SerializerMethodField()
audiences = serializers.ListField(source='audiences_pl')
class Meta:
'href', 'url', 'language',
'authors', 'translators',
'epochs', 'genres', 'kinds',
- #'children',
+ 'children',
'parent', 'preview',
'epub', 'mobi', 'pdf', 'html', 'txt', 'fb2', 'xml',
'cover_thumb', 'cover',
'abstract',
'has_mp3_file', 'has_sync_file',
'elevenreader_link', 'content_warnings', 'audiences',
+ 'changed_at', 'read_time', 'pages', 'redakcja'
]
+ def get_children(self, obj):
+ return list(obj.get_children().values('slug', 'title'))
+
class BookSerializer11Labs(serializers.ModelSerializer):
url = AbsoluteURLField()
href = AbsoluteURLField(view_name='catalogue_api_book', view_args=['slug'])
'osoba': ('em', True, {'class': 'osoba'}, None, None),
'didaskalia': ('div', True, {'class': 'didaskalia'}, None, None),
'kwestia': ('div', False, {'class': 'kwestia'}, None, None),
- 'didask_tekst': ('em', False, {'class': 'didask_tekst'}, None, None),
+ 'didask_tekst': ('em', True, {'class': 'didask_tekst'}, None, None),
'naglowek_czesc': ('h2', True, None, None, None),
'naglowek_akt': ('h2', True, None, None, None),
'br': ('br', False, None, None, None),
'indeks_dolny': ('em', True, {'class': 'indeks_dolny'}, None, False),
'mat': ('span', True, {'class': 'mat'}, None, False),
+
+ 'mfenced': ('math_mfenced', True, None, None, False),
+ 'mfrac': ('math_mfrac', True, None, None, False),
+ 'mrow': ('math_mrow', True, None, None, False),
+ 'mi': ('math_mi', True, None, None, False),
+ 'mn': ('math_mn', True, None, None, False),
+ 'mo': ('math_mo', True, None, None, False),
+ 'msup': ('math_msup', True, None, None, False),
+
+ 'list': ('blockquote', False, {'class': 'list'}, None, None),
+ 'wywiad_pyt': ('blockquote', False, {'class': 'wywiad_pyt'}, None, None),
+ 'wywiad_odp': ('blockquote', False, {'class': 'wywiad_odp'}, None, None),
+ 'rownolegle': ('blockquote', False, {'class': 'rownolegle'}, None, None),
+ 'animacja': ('div', False, {'class': 'animacja'}, None, None),
+ 'data': ('div', True, {'class': 'data'}, None, None),
+ 'podpis': ('div', True, {'class': 'podpis'}, None, None),
+ 'naglowek_listu': ('div', True, {'class': 'naglowek_listu'}, None, None),
+ 'pozdrowienie': ('div', True, {'class': 'pozdrowienie'}, None, None),
+ 'adresat': ('div', True, {'class': 'adresat'}, None, None),
+ 'tytul_oryg': ('div', True, {'class': 'tytul_oryg'}, None, None),
+ 'miejsce_data': ('div', True, {'class': 'miejsce_data'}, None, None),
+ 'audio': ('_ignore', False, None, None, None),
+ 'www': ('a', True, {'class': 'www'}, {'href': '.text'}, False),
+
+ 'tabela': ('table', False, None, None, None),
+ 'tabelka': ('table', False, None, None, None),
+ 'wiersz': ('tr', False, None, None, None),
+ 'kol': ('td', True, None, None, None),
+
+ 'ilustr': ('img', False, None, {'src': 'src'}, False),
+ 'tab': ('span', False, {'class': 'tab'}, {'szer': 'szer'}, False),
+
}
id_prefixes = {
if attr_map:
output.setdefault('attr', {})
for k, v in attr_map.items():
- output['attr'][k] = elem.attrib[v]
+ if v == '.text':
+ val = elem.text
+ else:
+ val = elem.attrib[v]
+ output['attr'][k] = val
output['contents'] = contents
output = [output]
if elem.tag == 'strofa':
piwik_track_view(views.BookFragmentView.as_view()),
name='catalogue_api_book_fragment'
),
+ path('books/<slug:slug>/children/',
+ views.BookChildrenView.as_view()
+ ),
path('books/<slug:slug>/media/<slug:type>/',
views.BookMediaView.as_view()
),
Book,
slug=self.kwargs['slug']
)
- return book.recommended(limit=3)
+ return book.get_recommended(limit=3)
class BookList11Labs(BookList2):
return book.choose_fragment()
+class BookChildrenView(ListAPIView):
+ serializer_class = serializers.BookSerializer2
+ pagination_class = None
+
+ def get_queryset(self):
+ book = get_object_or_404(Book, slug=self.kwargs['slug'])
+ return book.get_children()
+
+
class BookMediaView(ListAPIView):
serializer_class = serializers.MediaSerializer2
pagination_class = None
--- /dev/null
+# Generated by Django 4.0.8 on 2026-02-17 14:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0051_book_has_audio'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='book',
+ name='pages',
+ field=models.FloatField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='book',
+ name='read_time',
+ field=models.FloatField(blank=True, null=True),
+ ),
+ ]
translators = models.ManyToManyField(Tag, blank=True)
narrators = models.ManyToManyField(Tag, blank=True, related_name='narrated')
has_audio = models.BooleanField(default=False)
-
+ read_time = models.FloatField(blank=True, null=True)
+ pages = models.FloatField(blank=True, null=True)
+
html_built = django.dispatch.Signal()
published = django.dispatch.Signal()
def isbn_mobi(self):
return self.get_extra_info_json().get('isbn_mobi')
+ @property
+ def redakcja(self):
+ return self.get_extra_info_json().get('about')
+
def is_accessible_to(self, user):
if not self.preview:
return True
book.load_toc()
book.save()
+ book.update_stats()
+
meta_tags = Tag.tags_from_info(book_info)
just_tags = [t for (t, rel) in meta_tags if not rel]
cls.published.send(sender=cls, instance=book)
return book
+ def update_stats(self):
+ stats = self.wldocument2().get_statistics()['total']
+ self.pages = (
+ stats['verses_with_fn'] / 30 +
+ stats['chars_out_verse_with_fn'] / 1800)
+ self.read_time = self.get_time()
+ self.save(update_fields=['pages', 'read_time'])
+ if self.parent is not None:
+ self.parent.update_stats()
+
def update_references(self):
Entity = apps.get_model('references', 'Entity')
doc = self.wldocument2()
elif isinstance(publisher, list):
return ', '.join(publisher)
- def recommended(self, limit=4):
+ def get_recommended(self, limit=4):
books_qs = type(self).objects.filter(findable=True)
books_qs = books_qs.exclude(common_slug=self.common_slug).exclude(ancestor=self)
books = type(self).tagged.related_to(self, books_qs)[:limit]
@register.simple_tag
def related_books(book, limit=4, taken=0):
- return book.recommended(limit=limit - taken)
+ return book.get_recommended(limit=limit - taken)
@register.simple_tag
receipt = cls.generate_receipt(email, year)
if receipt:
content, optout, payments = receipt
+ else:
+ return
ctx = {
"email": email,
"year": year,
czy wiesz, że możesz odliczyć od podstawy opodatkowania wszystkie darowizny przekazane na prowadzenie biblioteki Wolne Lektury i działalność Fundacji?
-Łącznie w {{ year }} otrzymaliśmy od Ciebie {{ total }} zł na zapewnienie dostępu do książek wszystkim dzieciakom.
+Łącznie w {{ year }} otrzymaliśmy od Ciebie {{ total }} zł na udostępnianie nowych ebooków i audiobooków.
Zestawienie darowizn za rok {{ year }} znajdziesz w załączniku!
Dane z załącznika wprowadź do formularza PIT. Pamiętaj, że w przypadku kontroli z urzędu skarbowego musisz mieć potwierdzenie wykonanych przelewów z Twojego banku.
-Podczas wypełniania swojego PIT-u możesz także przekazać 1,5% swojego podatku na Wolne Lektury. Dzięki temu ufundujesz darmową e-książkę, która trafi do tysięcy dzieciaków. Wystarczy, że w odpowiednim polu wpiszesz nazwę organizacji „fundacja Wolne Lektury” oraz numer KRS 0000070056. Wspierasz Wolne Lektury w ten sposób już od dawna? W takim razie nic nie musisz zmieniać, bo system ponownie sam wprowadzi dane fundacji.
+Podczas wypełniania swojego PIT-u możesz także przekazać 1,5% swojego podatku na Wolne Lektury. Dzięki temu ufundujesz pomożesz nam udostępniać kolejne ebooki i audiobooki w tym roku . Wystarczy, że w odpowiednim polu wpiszesz nazwę organizacji „fundacja Wolne Lektury” oraz numer KRS 0000070056. Wspierasz Wolne Lektury w ten sposób już od dawna? W takim razie nic nie musisz zmieniać, bo system ponownie sam wprowadzi dane fundacji.
Serdecznie dziękujemy za Twoje wsparcie!
-Paulina Choromańska i Jarosław Lipszyc
+Paulina Choromańska i Radosław Czajka
w imieniu całego zespołu Wolnych Lektur
validated_data['name'],
create=True
)
- instance.userlistitem_set.all().delete()
- for book in validated_data['books']:
- instance.append(book)
+ if 'books' in validated_data:
+ instance.userlistitem_set.all().delete()
+ for book in validated_data['books']:
+ instance.append(book)
return instance
def update(self, instance, validated_data):
- instance.userlistitem_set.all().delete()
- for book in validated_data['books']:
- instance.append(instance)
+ if 'books' in validated_data:
+ instance.userlistitem_set.all().delete()
+ for book in validated_data['books']:
+ instance.append(instance)
return instance
class UserListBooksSerializer(UserListSerializer):
)
else:
return get_object_or_404(
- models.UserList,
+ models.UserList.all_objects.all(),
slug=self.kwargs['slug'],
user=self.request.user)
return super().save(*args, **kwargs)
+class ActiveManager(models.Manager):
+ def get_queryset(self):
+ return super().get_queryset().filter(deleted=False)
+
+
class UserList(Syncable, models.Model):
slug = models.SlugField(unique=True)
user = models.ForeignKey(User, models.CASCADE)
reported_timestamp = models.DateTimeField()
syncable_fields = ['name', 'public', 'deleted']
-
+
+ objects = ActiveManager()
+ all_objects = models.Manager()
+
def get_absolute_url(self):
return reverse(
'tagged_object_list',
# merge?
lists = list(cls.objects.filter(user=user, favorites=True))
for l in lists[1:]:
- t.userlistitem_set.all().update(
+ l.userlistitem_set.all().update(
list=lists[0]
)
l.delete()
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'api.pagination.WLLimitOffsetPagination',
- 'PAGE_SIZE': 10,
+ 'PAGE_SIZE': 20,
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
}