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_TLS=true
|
||||||
EMAIL_USE_SSL=false
|
EMAIL_USE_SSL=false
|
||||||
|
|
||||||
|
# Thumbnails Generation
|
||||||
|
ENABLE_THUMBNAIL_GENERATION=false
|
||||||
|
|
||||||
# S3 configuration
|
# S3 configuration
|
||||||
USE_S3=false
|
USE_S3=false
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_ACCESS_KEY_ID=
|
||||||
|
@ -58,6 +61,7 @@ AWS_SECRET_ACCESS_KEY=
|
||||||
# AWS_S3_REGION_NAME=None # "fr-par"
|
# AWS_S3_REGION_NAME=None # "fr-par"
|
||||||
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
||||||
|
|
||||||
|
|
||||||
# Preview image generation can be computing and storage intensive
|
# Preview image generation can be computing and storage intensive
|
||||||
# ENABLE_PREVIEW_IMAGES=True
|
# ENABLE_PREVIEW_IMAGES=True
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ EMAIL_HOST_PASSWORD=emailpassword123
|
||||||
EMAIL_USE_TLS=true
|
EMAIL_USE_TLS=true
|
||||||
EMAIL_USE_SSL=false
|
EMAIL_USE_SSL=false
|
||||||
|
|
||||||
|
# Thumbnails Generation
|
||||||
|
ENABLE_THUMBNAIL_GENERATION=false
|
||||||
|
|
||||||
# S3 configuration
|
# S3 configuration
|
||||||
USE_S3=false
|
USE_S3=false
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_ACCESS_KEY_ID=
|
||||||
|
@ -58,6 +61,7 @@ AWS_SECRET_ACCESS_KEY=
|
||||||
# AWS_S3_REGION_NAME=None # "fr-par"
|
# AWS_S3_REGION_NAME=None # "fr-par"
|
||||||
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
||||||
|
|
||||||
|
|
||||||
# Preview image generation can be computing and storage intensive
|
# Preview image generation can be computing and storage intensive
|
||||||
# ENABLE_PREVIEW_IMAGES=True
|
# ENABLE_PREVIEW_IMAGES=True
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ def site_settings(request): # pylint: disable=unused-argument
|
||||||
return {
|
return {
|
||||||
"site": models.SiteSettings.objects.get(),
|
"site": models.SiteSettings.objects.get(),
|
||||||
"active_announcements": models.Announcement.active_announcements(),
|
"active_announcements": models.Announcement.active_announcements(),
|
||||||
|
"thumbnail_generation_enabled": settings.ENABLE_THUMBNAIL_GENERATION,
|
||||||
"media_full_url": settings.MEDIA_FULL_URL,
|
"media_full_url": settings.MEDIA_FULL_URL,
|
||||||
"preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES,
|
"preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES,
|
||||||
"request_protocol": request_protocol,
|
"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 django.dispatch import receiver
|
||||||
from model_utils import FieldTracker
|
from model_utils import FieldTracker
|
||||||
from model_utils.managers import InheritanceManager
|
from model_utils.managers import InheritanceManager
|
||||||
|
from imagekit.models import ImageSpecField
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from bookwyrm.preview_images import generate_edition_preview_image_task
|
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 .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
|
@ -97,6 +103,40 @@ class Book(BookDataModel):
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"])
|
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
|
@property
|
||||||
def author_text(self):
|
def author_text(self):
|
||||||
"""format a list of authors"""
|
"""format a list of authors"""
|
||||||
|
|
|
@ -75,6 +75,7 @@ INSTALLED_APPS = [
|
||||||
"django_rename_app",
|
"django_rename_app",
|
||||||
"bookwyrm",
|
"bookwyrm",
|
||||||
"celery",
|
"celery",
|
||||||
|
"imagekit",
|
||||||
"storages",
|
"storages",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -191,6 +192,9 @@ USER_AGENT = "%s (BookWyrm/%s; +https://%s/)" % (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Imagekit generated thumbnails
|
||||||
|
ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False)
|
||||||
|
IMAGEKIT_CACHEFILE_DIR = "thumbnails"
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
|
@ -232,16 +232,21 @@ body {
|
||||||
/* Cover caption
|
/* Cover caption
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
.no-cover .cover_caption {
|
.no-cover .cover-caption {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 0.25em;
|
padding: 0.5em;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #002549;
|
background-color: #002549;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: initial;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Avatars
|
/** Avatars
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""Handles backends for storages"""
|
"""Handles backends for storages"""
|
||||||
|
import os
|
||||||
|
from tempfile import SpooledTemporaryFile
|
||||||
from storages.backends.s3boto3 import S3Boto3Storage
|
from storages.backends.s3boto3 import S3Boto3Storage
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,3 +17,33 @@ class ImagesStorage(S3Boto3Storage): # pylint: disable=abstract-method
|
||||||
location = "images"
|
location = "images"
|
||||||
default_acl = "public-read"
|
default_acl = "public-read"
|
||||||
file_overwrite = False
|
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="columns">
|
||||||
<div class="column is-one-fifth">
|
<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 %}
|
{% include 'snippets/rate_action.html' with user=request.user book=book %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|
|
@ -220,7 +220,7 @@
|
||||||
<h2 class="title is-4">{% trans "Cover" %}</h2>
|
<h2 class="title is-4">{% trans "Cover" %}</h2>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-3 is-cover">
|
<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>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="columns is-gapless mb-6">
|
<div class="columns is-gapless mb-6">
|
||||||
<div class="column is-cover">
|
<div class="column is-cover">
|
||||||
<a href="{{ book.local_path }}">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<a
|
<a
|
||||||
class="align to-b to-l"
|
class="align to-b to-l"
|
||||||
href="{{ book.local_path }}"
|
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 %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
<h3 class="title is-6">
|
<h3 class="title is-6">
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% if status.book or status.mention_books.exists %}
|
{% if status.book or status.mention_books.exists %}
|
||||||
{% load_book status as book %}
|
{% load_book status as book %}
|
||||||
<a href="{{ book.local_path }}">
|
<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>
|
</a>
|
||||||
|
|
||||||
<div class="block mt-2">
|
<div class="block mt-2">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="column is-cover">
|
<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">
|
<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 %}">
|
<select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
|
||||||
|
|
|
@ -132,7 +132,7 @@
|
||||||
<td>
|
<td>
|
||||||
{% if item.book %}
|
{% if item.book %}
|
||||||
<a href="{{ item.book.local_path }}">
|
<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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
href="{{ book.local_path }}"
|
href="{{ book.local_path }}"
|
||||||
aria-hidden="true"
|
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>
|
</a>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column ml-3">
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
>
|
>
|
||||||
<div class="column is-3-mobile is-2-tablet is-cover align to-t">
|
<div class="column is-3-mobile is-2-tablet is-cover align to-t">
|
||||||
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@
|
||||||
href="{{ book.local_path }}"
|
href="{{ book.local_path }}"
|
||||||
aria-hidden="true"
|
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>
|
</a>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column ml-3">
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="card-image columns is-mobile is-gapless is-clipped">
|
<div class="card-image columns is-mobile is-gapless is-clipped">
|
||||||
{% for book in list_books %}
|
{% for book in list_books %}
|
||||||
<a class="column is-cover" href="{{ book.book.local_path }}">
|
<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>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,41 +2,67 @@
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load imagekit %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
<figure
|
{% if book.cover %}
|
||||||
class="
|
<picture class="cover-container {{ cover_class }}">
|
||||||
cover-container
|
{% if external_path %}
|
||||||
{{ cover_class }}
|
<img
|
||||||
|
class="book-cover"
|
||||||
{% if not book.cover %}
|
src="{{ book.cover }}"
|
||||||
no-cover
|
itemprop="thumbnailUrl"
|
||||||
{% endif %}
|
alt="{{ book.alt_text|default:'' }}"
|
||||||
"
|
>
|
||||||
|
|
||||||
{% 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 %}
|
|
||||||
{% else %}
|
{% 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" %}"
|
src="{% static "images/no_cover.jpg" %}"
|
||||||
alt="{% trans "No cover" %}"
|
alt="{% trans "No cover" %}"
|
||||||
{% endif %}
|
>
|
||||||
>
|
<figcaption class="cover-caption">
|
||||||
|
|
||||||
{% if not book.cover and book.alt_text %}
|
|
||||||
<figcaption class="cover_caption">
|
|
||||||
<p>{{ book.alt_text }}</p>
|
<p>{{ book.alt_text }}</p>
|
||||||
</figcaption>
|
</figcaption>
|
||||||
{% endif %}
|
</figure>
|
||||||
</figure>
|
{% endif %}
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="columns is-mobile is-gapless">
|
<div class="columns is-mobile is-gapless">
|
||||||
<div class="column is-cover">
|
<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>
|
||||||
|
|
||||||
<div class="column is-10 ml-3">
|
<div class="column is-10 ml-3">
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="column is-cover">
|
<div class="column is-cover">
|
||||||
<div class="columns is-mobile is-gapless">
|
<div class="columns is-mobile is-gapless">
|
||||||
<div class="column is-cover">
|
<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 %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{% with book=status.book|default:status.mention_books.first %}
|
{% with book=status.book|default:status.mention_books.first %}
|
||||||
<div class="columns is-mobile is-gapless">
|
<div class="columns is-mobile is-gapless">
|
||||||
<a class="column is-cover is-narrow" href="{{ book.local_path }}">
|
<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>
|
</a>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column ml-3">
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
<tr class="book-preview">
|
<tr class="book-preview">
|
||||||
<td class="book-preview-top-row">
|
<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>
|
||||||
<td data-title="{% trans "Title" %}">
|
<td data-title="{% trans "Title" %}">
|
||||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
{% for book in shelf.books %}
|
{% for book in shelf.books %}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a href="{{ book.local_path }}">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.templatetags.static import static
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
@ -50,3 +51,13 @@ def truncatepath(value, arg):
|
||||||
except ValueError: # invalid literal for int()
|
except ValueError: # invalid literal for int()
|
||||||
return path_list[-1] # Fail silently.
|
return path_list[-1] # Fail silently.
|
||||||
return "%s/…%s" % (path_list[0], path_list[-1][-length:])
|
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)
|
populate_suggestions)
|
||||||
runweb python manage.py populate_suggestions
|
runweb python manage.py populate_suggestions
|
||||||
;;
|
;;
|
||||||
|
generate_thumbnails)
|
||||||
|
runweb python manage.py generateimages
|
||||||
|
;;
|
||||||
generate_preview_images)
|
generate_preview_images)
|
||||||
runweb python manage.py generate_preview_images $@
|
runweb python manage.py generate_preview_images $@
|
||||||
;;
|
;;
|
||||||
|
@ -171,6 +174,7 @@ case "$CMD" in
|
||||||
echo " black"
|
echo " black"
|
||||||
echo " populate_streams [--stream=<stream name>]"
|
echo " populate_streams [--stream=<stream name>]"
|
||||||
echo " populate_suggestions"
|
echo " populate_suggestions"
|
||||||
|
echo " generate_thumbnails"
|
||||||
echo " generate_preview_images [--all]"
|
echo " generate_preview_images [--all]"
|
||||||
echo " copy_media_to_s3"
|
echo " copy_media_to_s3"
|
||||||
echo " set_cors_to_s3 [cors file]"
|
echo " set_cors_to_s3 [cors file]"
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
celery==4.4.2
|
celery==4.4.2
|
||||||
colorthief==0.2.1
|
colorthief==0.2.1
|
||||||
Django==3.2.4
|
Django==3.2.4
|
||||||
|
django-imagekit==4.0.2
|
||||||
django-model-utils==4.0.0
|
django-model-utils==4.0.0
|
||||||
environs==7.2.0
|
environs==7.2.0
|
||||||
flower==0.9.4
|
flower==0.9.4
|
||||||
Markdown==3.3.3
|
Markdown==3.3.3
|
||||||
Pillow>=7.1.0
|
Pillow>=8.2.0
|
||||||
psycopg2==2.8.4
|
psycopg2==2.8.4
|
||||||
pycryptodome==3.9.4
|
pycryptodome==3.9.4
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
|
|
Loading…
Reference in a new issue