mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-18 14:16:45 +00:00
Merge pull request #1138 from joachimesque/images-django-imagekit
Generate thumnails for books
This commit is contained in:
commit
0829696add
27 changed files with 296 additions and 51 deletions
|
@ -43,6 +43,9 @@ EMAIL_HOST_PASSWORD=emailpassword123
|
|||
EMAIL_USE_TLS=true
|
||||
EMAIL_USE_SSL=false
|
||||
|
||||
# Thumbnails Generation
|
||||
ENABLE_THUMBNAIL_GENERATION=false
|
||||
|
||||
# S3 configuration
|
||||
USE_S3=false
|
||||
AWS_ACCESS_KEY_ID=
|
||||
|
@ -58,6 +61,7 @@ AWS_SECRET_ACCESS_KEY=
|
|||
# AWS_S3_REGION_NAME=None # "fr-par"
|
||||
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
||||
|
||||
|
||||
# Preview image generation can be computing and storage intensive
|
||||
# ENABLE_PREVIEW_IMAGES=True
|
||||
|
||||
|
|
|
@ -43,6 +43,9 @@ EMAIL_HOST_PASSWORD=emailpassword123
|
|||
EMAIL_USE_TLS=true
|
||||
EMAIL_USE_SSL=false
|
||||
|
||||
# Thumbnails Generation
|
||||
ENABLE_THUMBNAIL_GENERATION=false
|
||||
|
||||
# S3 configuration
|
||||
USE_S3=false
|
||||
AWS_ACCESS_KEY_ID=
|
||||
|
@ -58,6 +61,7 @@ AWS_SECRET_ACCESS_KEY=
|
|||
# AWS_S3_REGION_NAME=None # "fr-par"
|
||||
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
||||
|
||||
|
||||
# Preview image generation can be computing and storage intensive
|
||||
# ENABLE_PREVIEW_IMAGES=True
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ def site_settings(request): # pylint: disable=unused-argument
|
|||
return {
|
||||
"site": models.SiteSettings.objects.get(),
|
||||
"active_announcements": models.Announcement.active_announcements(),
|
||||
"thumbnail_generation_enabled": settings.ENABLE_THUMBNAIL_GENERATION,
|
||||
"media_full_url": settings.MEDIA_FULL_URL,
|
||||
"preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES,
|
||||
"request_protocol": request_protocol,
|
||||
|
|
113
bookwyrm/imagegenerators.py
Normal file
113
bookwyrm/imagegenerators.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
"""Generators for all the different thumbnail sizes"""
|
||||
from imagekit import ImageSpec, register
|
||||
from imagekit.processors import ResizeToFit
|
||||
|
||||
|
||||
class BookXSmallWebp(ImageSpec):
|
||||
"""Handles XSmall size in Webp format"""
|
||||
|
||||
processors = [ResizeToFit(80, 80)]
|
||||
format = "WEBP"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookXSmallJpg(ImageSpec):
|
||||
"""Handles XSmall size in Jpeg format"""
|
||||
|
||||
processors = [ResizeToFit(80, 80)]
|
||||
format = "JPEG"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookSmallWebp(ImageSpec):
|
||||
"""Handles Small size in Webp format"""
|
||||
|
||||
processors = [ResizeToFit(100, 100)]
|
||||
format = "WEBP"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookSmallJpg(ImageSpec):
|
||||
"""Handles Small size in Jpeg format"""
|
||||
|
||||
processors = [ResizeToFit(100, 100)]
|
||||
format = "JPEG"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookMediumWebp(ImageSpec):
|
||||
"""Handles Medium size in Webp format"""
|
||||
|
||||
processors = [ResizeToFit(150, 150)]
|
||||
format = "WEBP"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookMediumJpg(ImageSpec):
|
||||
"""Handles Medium size in Jpeg format"""
|
||||
|
||||
processors = [ResizeToFit(150, 150)]
|
||||
format = "JPEG"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookLargeWebp(ImageSpec):
|
||||
"""Handles Large size in Webp format"""
|
||||
|
||||
processors = [ResizeToFit(200, 200)]
|
||||
format = "WEBP"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookLargeJpg(ImageSpec):
|
||||
"""Handles Large size in Jpeg format"""
|
||||
|
||||
processors = [ResizeToFit(200, 200)]
|
||||
format = "JPEG"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookXLargeWebp(ImageSpec):
|
||||
"""Handles XLarge size in Webp format"""
|
||||
|
||||
processors = [ResizeToFit(250, 250)]
|
||||
format = "WEBP"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookXLargeJpg(ImageSpec):
|
||||
"""Handles XLarge size in Jpeg format"""
|
||||
|
||||
processors = [ResizeToFit(250, 250)]
|
||||
format = "JPEG"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookXxLargeWebp(ImageSpec):
|
||||
"""Handles XxLarge size in Webp format"""
|
||||
|
||||
processors = [ResizeToFit(500, 500)]
|
||||
format = "WEBP"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
class BookXxLargeJpg(ImageSpec):
|
||||
"""Handles XxLarge size in Jpeg format"""
|
||||
|
||||
processors = [ResizeToFit(500, 500)]
|
||||
format = "JPEG"
|
||||
options = {"quality": 95}
|
||||
|
||||
|
||||
register.generator("bw:book:xsmall:webp", BookXSmallWebp)
|
||||
register.generator("bw:book:xsmall:jpg", BookXSmallJpg)
|
||||
register.generator("bw:book:small:webp", BookSmallWebp)
|
||||
register.generator("bw:book:small:jpg", BookSmallJpg)
|
||||
register.generator("bw:book:medium:webp", BookMediumWebp)
|
||||
register.generator("bw:book:medium:jpg", BookMediumJpg)
|
||||
register.generator("bw:book:large:webp", BookLargeWebp)
|
||||
register.generator("bw:book:large:jpg", BookLargeJpg)
|
||||
register.generator("bw:book:xlarge:webp", BookXLargeWebp)
|
||||
register.generator("bw:book:xlarge:jpg", BookXLargeJpg)
|
||||
register.generator("bw:book:xxlarge:webp", BookXxLargeWebp)
|
||||
register.generator("bw:book:xxlarge:jpg", BookXxLargeJpg)
|
|
@ -7,10 +7,16 @@ from django.db import models
|
|||
from django.dispatch import receiver
|
||||
from model_utils import FieldTracker
|
||||
from model_utils.managers import InheritanceManager
|
||||
from imagekit.models import ImageSpecField
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.preview_images import generate_edition_preview_image_task
|
||||
from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE, ENABLE_PREVIEW_IMAGES
|
||||
from bookwyrm.settings import (
|
||||
DOMAIN,
|
||||
DEFAULT_LANGUAGE,
|
||||
ENABLE_PREVIEW_IMAGES,
|
||||
ENABLE_THUMBNAIL_GENERATION,
|
||||
)
|
||||
|
||||
from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
|
||||
from .base_model import BookWyrmModel
|
||||
|
@ -97,6 +103,40 @@ class Book(BookDataModel):
|
|||
objects = InheritanceManager()
|
||||
field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"])
|
||||
|
||||
if ENABLE_THUMBNAIL_GENERATION:
|
||||
cover_bw_book_xsmall_webp = ImageSpecField(
|
||||
source="cover", id="bw:book:xsmall:webp"
|
||||
)
|
||||
cover_bw_book_xsmall_jpg = ImageSpecField(
|
||||
source="cover", id="bw:book:xsmall:jpg"
|
||||
)
|
||||
cover_bw_book_small_webp = ImageSpecField(
|
||||
source="cover", id="bw:book:small:webp"
|
||||
)
|
||||
cover_bw_book_small_jpg = ImageSpecField(source="cover", id="bw:book:small:jpg")
|
||||
cover_bw_book_medium_webp = ImageSpecField(
|
||||
source="cover", id="bw:book:medium:webp"
|
||||
)
|
||||
cover_bw_book_medium_jpg = ImageSpecField(
|
||||
source="cover", id="bw:book:medium:jpg"
|
||||
)
|
||||
cover_bw_book_large_webp = ImageSpecField(
|
||||
source="cover", id="bw:book:large:webp"
|
||||
)
|
||||
cover_bw_book_large_jpg = ImageSpecField(source="cover", id="bw:book:large:jpg")
|
||||
cover_bw_book_xlarge_webp = ImageSpecField(
|
||||
source="cover", id="bw:book:xlarge:webp"
|
||||
)
|
||||
cover_bw_book_xlarge_jpg = ImageSpecField(
|
||||
source="cover", id="bw:book:xlarge:jpg"
|
||||
)
|
||||
cover_bw_book_xxlarge_webp = ImageSpecField(
|
||||
source="cover", id="bw:book:xxlarge:webp"
|
||||
)
|
||||
cover_bw_book_xxlarge_jpg = ImageSpecField(
|
||||
source="cover", id="bw:book:xxlarge:jpg"
|
||||
)
|
||||
|
||||
@property
|
||||
def author_text(self):
|
||||
"""format a list of authors"""
|
||||
|
|
|
@ -75,6 +75,7 @@ INSTALLED_APPS = [
|
|||
"django_rename_app",
|
||||
"bookwyrm",
|
||||
"celery",
|
||||
"imagekit",
|
||||
"storages",
|
||||
]
|
||||
|
||||
|
@ -191,6 +192,9 @@ USER_AGENT = "%s (BookWyrm/%s; +https://%s/)" % (
|
|||
DOMAIN,
|
||||
)
|
||||
|
||||
# Imagekit generated thumbnails
|
||||
ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False)
|
||||
IMAGEKIT_CACHEFILE_DIR = "thumbnails"
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
|
|
@ -232,16 +232,21 @@ body {
|
|||
/* Cover caption
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.no-cover .cover_caption {
|
||||
.no-cover .cover-caption {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 0.25em;
|
||||
padding: 0.5em;
|
||||
font-size: 0.75em;
|
||||
color: white;
|
||||
background-color: #002549;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: initial;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/** Avatars
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Handles backends for storages"""
|
||||
import os
|
||||
from tempfile import SpooledTemporaryFile
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
|
||||
|
||||
|
@ -15,3 +17,33 @@ class ImagesStorage(S3Boto3Storage): # pylint: disable=abstract-method
|
|||
location = "images"
|
||||
default_acl = "public-read"
|
||||
file_overwrite = False
|
||||
|
||||
"""
|
||||
This is our custom version of S3Boto3Storage that fixes a bug in
|
||||
boto3 where the passed in file is closed upon upload.
|
||||
From:
|
||||
https://github.com/matthewwithanm/django-imagekit/issues/391#issuecomment-275367006
|
||||
https://github.com/boto/boto3/issues/929
|
||||
https://github.com/matthewwithanm/django-imagekit/issues/391
|
||||
"""
|
||||
|
||||
def _save(self, name, content):
|
||||
"""
|
||||
We create a clone of the content file as when this is passed to
|
||||
boto3 it wrongly closes the file upon upload where as the storage
|
||||
backend expects it to still be open
|
||||
"""
|
||||
# Seek our content back to the start
|
||||
content.seek(0, os.SEEK_SET)
|
||||
|
||||
# Create a temporary file that will write to disk after a specified
|
||||
# size. This file will be automatically deleted when closed by
|
||||
# boto3 or after exiting the `with` statement if the boto3 is fixed
|
||||
with SpooledTemporaryFile() as content_autoclose:
|
||||
|
||||
# Write our original content into our copy that will be closed by boto3
|
||||
content_autoclose.write(content.read())
|
||||
|
||||
# Upload the object which will auto close the
|
||||
# content_autoclose instance
|
||||
return super()._save(name, content_autoclose)
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
|
||||
<div class="columns">
|
||||
<div class="column is-one-fifth">
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m-mobile' %}
|
||||
{% include 'snippets/book_cover.html' with size='xxlarge' size_mobile='medium' book=book cover_class='is-h-m-mobile' %}
|
||||
{% include 'snippets/rate_action.html' with user=request.user book=book %}
|
||||
|
||||
<div class="mb-3">
|
||||
|
|
|
@ -220,7 +220,7 @@
|
|||
<h2 class="title is-4">{% trans "Cover" %}</h2>
|
||||
<div class="columns">
|
||||
<div class="column is-3 is-cover">
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xl-mobile is-w-auto-tablet' %}
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xl-mobile is-w-auto-tablet' size_mobile='xlarge' size='large' %}
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<div class="columns is-gapless mb-6">
|
||||
<div class="column is-cover">
|
||||
<a href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-m is-h-m align to-l-mobile' %}
|
||||
{% include 'snippets/book_cover.html' with size='medium' book=book cover_class='is-w-m is-h-m align to-l-mobile' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<a
|
||||
class="align to-b to-l"
|
||||
href="{{ book.local_path }}"
|
||||
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' %}</a>
|
||||
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' size='xlarge' %}</a>
|
||||
|
||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||
<h3 class="title is-6">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{% if status.book or status.mention_books.exists %}
|
||||
{% load_book status as book %}
|
||||
<a href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' %}
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' size='large' %}
|
||||
</a>
|
||||
|
||||
<div class="block mt-2">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load i18n %}
|
||||
<div class="column is-cover">
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-l is-h-m-mobile' %}
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-l is-h-m-mobile' size_mobile='medium' size='large' %}
|
||||
|
||||
<div class="select is-small mt-1 mb-3">
|
||||
<select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
<td>
|
||||
{% if item.book %}
|
||||
<a href="{{ item.book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' %}
|
||||
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' size='small' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
href="{{ book.local_path }}"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-xs-mobile is-w-s is-h-xs-mobile is-h-s' %}
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-xs-mobile is-w-s is-h-xs-mobile is-h-s' size_mobile='xsmall' size='small' %}
|
||||
</a>
|
||||
|
||||
<div class="column ml-3">
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
>
|
||||
<div class="column is-3-mobile is-2-tablet is-cover align to-t">
|
||||
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet is-align-items-flex-start' %}
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet is-align-items-flex-start' size='medium' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
@ -161,7 +161,7 @@
|
|||
href="{{ book.local_path }}"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto is-h-s-mobile align to-t' %}
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto is-h-s-mobile align to-t' size='small' %}
|
||||
</a>
|
||||
|
||||
<div class="column ml-3">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="card-image columns is-mobile is-gapless is-clipped">
|
||||
{% for book in list_books %}
|
||||
<a class="column is-cover" href="{{ book.book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=book.book cover_class='is-h-s' aria='show' %}
|
||||
{% include 'snippets/book_cover.html' with book=book.book cover_class='is-h-s' size='small' aria='show' %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -2,41 +2,67 @@
|
|||
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load imagekit %}
|
||||
{% load utilities %}
|
||||
|
||||
<figure
|
||||
class="
|
||||
cover-container
|
||||
{{ cover_class }}
|
||||
|
||||
{% if not book.cover %}
|
||||
no-cover
|
||||
{% endif %}
|
||||
"
|
||||
|
||||
{% if book.alt_text %}
|
||||
title="{{ book.alt_text }}"
|
||||
{% endif %}
|
||||
>
|
||||
<img
|
||||
class="book-cover"
|
||||
|
||||
{% if book.cover %}
|
||||
src="{% if img_path is None %}{% get_media_prefix %}{% else %}{{ img_path }}{% endif %}{{ book.cover }}"
|
||||
itemprop="thumbnailUrl"
|
||||
|
||||
{% if book.alt_text %}
|
||||
alt="{{ book.alt_text }}"
|
||||
{% endif %}
|
||||
{% if book.cover %}
|
||||
<picture class="cover-container {{ cover_class }}">
|
||||
{% if external_path %}
|
||||
<img
|
||||
class="book-cover"
|
||||
src="{{ book.cover }}"
|
||||
itemprop="thumbnailUrl"
|
||||
alt="{{ book.alt_text|default:'' }}"
|
||||
>
|
||||
{% else %}
|
||||
|
||||
{% if thumbnail_generation_enabled %}
|
||||
|
||||
{% if size_mobile %}
|
||||
<source
|
||||
media="(max-width: 768px)"
|
||||
type="image/webp"
|
||||
srcset="{% get_book_cover_thumbnail book=book size=size_mobile ext='webp' %}"
|
||||
/>
|
||||
<source
|
||||
media="(max-width: 768px)"
|
||||
type="image/jpg"
|
||||
srcset="{% get_book_cover_thumbnail book=book size=size_mobile ext='jpg' %}"
|
||||
/>
|
||||
{% endif %}
|
||||
|
||||
<source
|
||||
type="image/webp"
|
||||
srcset="{% get_book_cover_thumbnail book=book size=size ext='webp' %}"
|
||||
/>
|
||||
<source
|
||||
type="image/jpg"
|
||||
srcset="{% get_book_cover_thumbnail book=book size=size ext='jpg' %}"
|
||||
/>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<img
|
||||
alt="{{ book.alt_text|default:'' }}"
|
||||
class="book-cover"
|
||||
itemprop="thumbnailUrl"
|
||||
src="{% if img_path is None %}{% get_media_prefix %}{% else %}{{ img_path }}{% endif %}{{ book.cover }}"
|
||||
>
|
||||
|
||||
{% endif %}
|
||||
</picture>
|
||||
{% endif %}
|
||||
|
||||
{% if not book.cover and book.alt_text %}
|
||||
<figure class="cover-container no-cover {{ cover_class }}">
|
||||
<img
|
||||
class="book-cover"
|
||||
src="{% static "images/no_cover.jpg" %}"
|
||||
alt="{% trans "No cover" %}"
|
||||
{% endif %}
|
||||
>
|
||||
|
||||
{% if not book.cover and book.alt_text %}
|
||||
<figcaption class="cover_caption">
|
||||
>
|
||||
<figcaption class="cover-caption">
|
||||
<p>{{ book.alt_text }}</p>
|
||||
</figcaption>
|
||||
{% endif %}
|
||||
</figure>
|
||||
</figure>
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load i18n %}
|
||||
<div class="columns is-mobile is-gapless">
|
||||
<div class="column is-cover">
|
||||
{% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' img_path=false %}
|
||||
{% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' external_path=True %}
|
||||
</div>
|
||||
|
||||
<div class="column is-10 ml-3">
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div class="column is-cover">
|
||||
<div class="columns is-mobile is-gapless">
|
||||
<div class="column is-cover">
|
||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-mobile is-h-l-tablet' %}</a>
|
||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-mobile is-h-l-tablet' size_mobile='medium' size='large' %}</a>
|
||||
|
||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{% with book=status.book|default:status.mention_books.first %}
|
||||
<div class="columns is-mobile is-gapless">
|
||||
<a class="column is-cover is-narrow" href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xs is-h-s-tablet' %}
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xs is-h-s-tablet' size='small' size_mobile='xsmall' %}
|
||||
</a>
|
||||
|
||||
<div class="column ml-3">
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
{% spaceless %}
|
||||
<tr class="book-preview">
|
||||
<td class="book-preview-top-row">
|
||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-tablet is-h-s' %}</a>
|
||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-tablet is-h-s' size='small' %}</a>
|
||||
</td>
|
||||
<td data-title="{% trans "Title" %}">
|
||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
{% for book in shelf.books %}
|
||||
<div class="control">
|
||||
<a href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m is-h-s-mobile' %}
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m is-h-s-mobile' size_mobile='small' size='medium' %}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
from uuid import uuid4
|
||||
from django import template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.templatetags.static import static
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
@ -50,3 +51,13 @@ def truncatepath(value, arg):
|
|||
except ValueError: # invalid literal for int()
|
||||
return path_list[-1] # Fail silently.
|
||||
return "%s/…%s" % (path_list[0], path_list[-1][-length:])
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def get_book_cover_thumbnail(book, size="medium", ext="jpg"):
|
||||
"""Returns a book thumbnail at the specified size and extension, with fallback if needed"""
|
||||
try:
|
||||
cover_thumbnail = getattr(book, "cover_bw_book_%s_%s" % (size, ext))
|
||||
return cover_thumbnail.url
|
||||
except OSError:
|
||||
return static("images/no_cover.jpg")
|
||||
|
|
4
bw-dev
4
bw-dev
|
@ -126,6 +126,9 @@ case "$CMD" in
|
|||
populate_suggestions)
|
||||
runweb python manage.py populate_suggestions
|
||||
;;
|
||||
generate_thumbnails)
|
||||
runweb python manage.py generateimages
|
||||
;;
|
||||
generate_preview_images)
|
||||
runweb python manage.py generate_preview_images $@
|
||||
;;
|
||||
|
@ -171,6 +174,7 @@ case "$CMD" in
|
|||
echo " black"
|
||||
echo " populate_streams [--stream=<stream name>]"
|
||||
echo " populate_suggestions"
|
||||
echo " generate_thumbnails"
|
||||
echo " generate_preview_images [--all]"
|
||||
echo " copy_media_to_s3"
|
||||
echo " set_cors_to_s3 [cors file]"
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
celery==4.4.2
|
||||
colorthief==0.2.1
|
||||
Django==3.2.4
|
||||
django-imagekit==4.0.2
|
||||
django-model-utils==4.0.0
|
||||
environs==7.2.0
|
||||
flower==0.9.4
|
||||
Markdown==3.3.3
|
||||
Pillow>=7.1.0
|
||||
Pillow>=8.2.0
|
||||
psycopg2==2.8.4
|
||||
pycryptodome==3.9.4
|
||||
python-dateutil==2.8.1
|
||||
|
|
Loading…
Reference in a new issue