logo_mono = forms.CharField(required=False)
logo_alt = forms.CharField(required=False)
can_sell = forms.BooleanField(required=False)
+ isbn_mp3 = forms.CharField(required=False)
def clean(self):
from django.core.files.base import ContentFile
logo=self.cleaned_data['logo'],
logo_mono=self.cleaned_data['logo_mono'],
logo_alt=self.cleaned_data['logo_alt'],
- can_sell=self.cleaned_data['can_sell'],
- **kwargs)
+ can_sell=self.cleaned_data['can_sell'],
+ isbn_mp3=self.cleaned_data['isbn_mp3'],
+ **kwargs)
FORMATS = [(f, f.upper()) for f in Book.ebook_formats]
--- /dev/null
+# Generated by Django 4.0.8 on 2026-04-24 10:23
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0055_book_can_sell'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='book',
+ name='isbn_mp3',
+ field=models.CharField(blank=True, max_length=32, verbose_name='ISBN audiobooka'),
+ ),
+ ]
preview_key = models.CharField(max_length=32, blank=True, null=True)
findable = models.BooleanField('wyszukiwalna', default=True, db_index=True)
can_sell = models.BooleanField('do sprzedaży', default=True)
+ isbn_mp3 = models.CharField('ISBN audiobooka', max_length=32, blank=True)
# files generated during publication
xml_file = fields.XmlField(storage=bofh_storage, with_etag=False)
@classmethod
def from_text_and_meta(cls, raw_file, book_info, overwrite=False, dont_build=None, search_index=True,
- remote_gallery_url=None, days=0, findable=True, logo=None, logo_mono=None, logo_alt=None, can_sell=None):
+ remote_gallery_url=None, days=0, findable=True, logo=None, logo_mono=None, logo_alt=None, can_sell=None, isbn_mp3=None):
from catalogue import tasks
if dont_build is None:
extra['logo_alt'] = logo_alt
if can_sell is not None:
book.can_sell = can_sell
+ if isbn_mp3 is not None:
+ book.isbn_mp3 = isbn_mp3
book.extra_info = json.dumps(extra)
book.load_abstract()
book.load_toc()
model = models.PriceLevel
extra = 0
+class AudioPriceLevelInline(admin.TabularInline):
+ model = models.AudioPriceLevel
+ extra = 0
@admin.register(models.Partner)
class PartnerAdmin(admin.ModelAdmin):
inlines = [
PriceLevelInline,
+ AudioPriceLevelInline,
]
urlpatterns = [
path('<slug:key>/books/',
views.PartnerBooksView.as_view()),
+ path('<slug:key>/audiobooks/',
+ views.PartnerAudiobooksView.as_view()),
]
return self.context['partner'].get_price(obj.pages)
+class PartnerAudiobookSerializer(BookSerializer2):
+ price = serializers.SerializerMethodField()
+ duration = serializers.SerializerMethodField()
+ files = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Book
+ fields = [
+ 'slug', 'title',
+ 'href', 'url', 'language',
+ 'authors', 'translators',
+ 'epochs', 'genres', 'kinds',
+ 'files',
+ 'cover',
+ 'isbn_mp3',
+ 'abstract',
+ 'content_warnings', 'audiences',
+ 'changed_at', 'duration',
+ 'price',
+ ]
+
+ def get_duration(self, obj):
+ return obj.get_audiobooks(True)[2]
+
+ def get_files(self, obj):
+ def get_for_single(b):
+ fs = []
+ for m in b.media.filter(type='mp3'):
+ fs.append({
+ "name": m.name,
+ "part_name": m.part_name,
+ "url": 'https://wolnelektury.pl' + m.file.url,
+ })
+ for c in b.get_children():
+ fs.extend(get_for_single(c))
+ return fs
+ return get_for_single(b)
+
+ def get_price(self, obj):
+ duration = obj.get_audiobooks(True)[2]
+ if not duration:
+ return None
+ duration /= 60
+ return self.context['partner'].get_audio_price(obj.pages)
+
+
@method_decorator(never_cache, name='dispatch')
class PartnerBooksView(ListAPIView):
serializer_class = PartnerBookSerializer
def get_queryset(self):
return Book.objects.filter(parent=None).filter(can_sell=True).exclude(pages=None)
+
+
+@method_decorator(never_cache, name='dispatch')
+class PartnerAudiobooksView(ListAPIView):
+ serializer_class = PartnerAudiobookSerializer
+
+ def get_serializer_context(self):
+ ctx = super().get_serializer_context()
+ ctx['partner'] = get_object_or_404(models.Partner, key=self.kwargs['key'])
+ return ctx
+
+ def get_queryset(self):
+ return Book.objects.exclude(isbn_mp3='')
--- /dev/null
+# Generated by Django 4.0.8 on 2026-04-24 10:48
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('partners', '0002_alter_pricelevel_price'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AudioPriceLevel',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('min_minutes', models.IntegerField(blank=True, null=True)),
+ ('price', models.DecimalField(decimal_places=2, max_digits=10)),
+ ('partner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='partners.partner')),
+ ],
+ options={
+ 'ordering': ('price',),
+ },
+ ),
+ ]
return None
return price_obj.price
+ def get_audio_price(self, minutes):
+ price_obj = self.audiopricelevel_set.exclude(
+ min_minutes__gt=minutes
+ ).order_by('-price').first()
+ if price_obj is None:
+ return None
+ return price_obj.price
+
class PriceLevel(models.Model):
partner = models.ForeignKey(Partner, models.CASCADE)
class Meta:
ordering = ('price',)
+
+
+class AudioPriceLevel(models.Model):
+ partner = models.ForeignKey(Partner, models.CASCADE)
+ min_minutes = models.IntegerField(null=True, blank=True)
+ price = models.DecimalField(max_digits=10, decimal_places=2)
+
+ class Meta:
+ ordering = ('price',)