Merge pull request #1138 from joachimesque/images-django-imagekit

Generate thumnails for books
This commit is contained in:
Mouse Reeve 2021-08-18 19:53:12 -06:00 committed by GitHub
commit 0829696add
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 296 additions and 51 deletions

View file

@ -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

View file

@ -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

View file

@ -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
View 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)

View file

@ -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"""

View file

@ -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/

View file

@ -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

View file

@ -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)

View file

@ -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">

View file

@ -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">

View file

@ -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>

View file

@ -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">

View file

@ -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">

View file

@ -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 %}">

View file

@ -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>

View file

@ -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">

View file

@ -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">

View file

@ -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>

View file

@ -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 %}

View file

@ -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">

View file

@ -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 %}

View file

@ -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">

View file

@ -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>

View file

@ -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 %}

View file

@ -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
View file

@ -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]"

View 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