forked from mirrors/bookwyrm
Add site preview task
This commit is contained in:
parent
101ca0ff81
commit
34caa36ab7
3 changed files with 79 additions and 16 deletions
|
@ -86,7 +86,7 @@ class Book(BookDataModel):
|
||||||
upload_to="covers/", blank=True, null=True, alt_field="alt_text"
|
upload_to="covers/", blank=True, null=True, alt_field="alt_text"
|
||||||
)
|
)
|
||||||
preview_image = models.ImageField(
|
preview_image = models.ImageField(
|
||||||
upload_to="cover_previews/", blank=True, null=True
|
upload_to="previews/covers/", blank=True, null=True
|
||||||
)
|
)
|
||||||
first_published_date = fields.DateTimeField(blank=True, null=True)
|
first_published_date = fields.DateTimeField(blank=True, null=True)
|
||||||
published_date = fields.DateTimeField(blank=True, null=True)
|
published_date = fields.DateTimeField(blank=True, null=True)
|
||||||
|
|
|
@ -4,9 +4,12 @@ import datetime
|
||||||
|
|
||||||
from Crypto import Random
|
from Crypto import Random
|
||||||
from django.db import models, IntegrityError
|
from django.db import models, IntegrityError
|
||||||
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from bookwyrm.preview_images import generate_site_preview_image_task
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
|
from bookwyrm.tasks import app
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
@ -35,6 +38,7 @@ class SiteSettings(models.Model):
|
||||||
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
|
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||||
logo_small = models.ImageField(upload_to="logos/", null=True, blank=True)
|
logo_small = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||||
favicon = models.ImageField(upload_to="logos/", null=True, blank=True)
|
favicon = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||||
|
preview_image = models.ImageField(upload_to="previews/logos/", null=True, blank=True)
|
||||||
|
|
||||||
# footer
|
# footer
|
||||||
support_link = models.CharField(max_length=255, null=True, blank=True)
|
support_link = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
@ -119,3 +123,12 @@ class PasswordReset(models.Model):
|
||||||
def link(self):
|
def link(self):
|
||||||
"""formats the invite link"""
|
"""formats the invite link"""
|
||||||
return "https://{}/password-reset/{}".format(DOMAIN, self.code)
|
return "https://{}/password-reset/{}".format(DOMAIN, self.code)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_save, sender=SiteSettings)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def preview_image(instance, *args, **kwargs):
|
||||||
|
updated_fields = kwargs["update_fields"]
|
||||||
|
|
||||||
|
if not updated_fields or "preview_image" not in updated_fields:
|
||||||
|
generate_site_preview_image_task.delay()
|
||||||
|
|
|
@ -14,6 +14,7 @@ from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||||
from django.db.models import Avg
|
from django.db.models import Avg
|
||||||
|
|
||||||
from bookwyrm import models, settings
|
from bookwyrm import models, settings
|
||||||
|
from bookwyrm.settings import DOMAIN
|
||||||
from bookwyrm.tasks import app
|
from bookwyrm.tasks import app
|
||||||
|
|
||||||
# dev
|
# dev
|
||||||
|
@ -182,7 +183,7 @@ def generate_default_cover():
|
||||||
return default_cover
|
return default_cover
|
||||||
|
|
||||||
|
|
||||||
def generate_preview_image(book, texts={}, picture=None, rating=None):
|
def generate_preview_image(texts={}, picture=None, rating=None, show_instance_layer=True):
|
||||||
# Cover
|
# Cover
|
||||||
try:
|
try:
|
||||||
cover_img_layer = Image.open(picture)
|
cover_img_layer = Image.open(picture)
|
||||||
|
@ -217,29 +218,33 @@ def generate_preview_image(book, texts={}, picture=None, rating=None):
|
||||||
content_x = margin + cover_img_layer.width + gutter
|
content_x = margin + cover_img_layer.width + gutter
|
||||||
content_width = IMG_WIDTH - content_x - margin
|
content_width = IMG_WIDTH - content_x - margin
|
||||||
|
|
||||||
instance_layer = generate_instance_layer(content_width)
|
|
||||||
texts_layer = generate_texts_layer(texts, content_width)
|
|
||||||
|
|
||||||
contents_layer = Image.new(
|
contents_layer = Image.new(
|
||||||
"RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR
|
"RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR
|
||||||
)
|
)
|
||||||
contents_composite_y = 0
|
contents_composite_y = 0
|
||||||
|
|
||||||
|
if show_instance_layer:
|
||||||
|
instance_layer = generate_instance_layer(content_width)
|
||||||
contents_layer.alpha_composite(instance_layer, (0, contents_composite_y))
|
contents_layer.alpha_composite(instance_layer, (0, contents_composite_y))
|
||||||
contents_composite_y = contents_composite_y + instance_layer.height + gutter
|
contents_composite_y = contents_composite_y + instance_layer.height + gutter
|
||||||
|
|
||||||
|
texts_layer = generate_texts_layer(texts, content_width)
|
||||||
contents_layer.alpha_composite(texts_layer, (0, contents_composite_y))
|
contents_layer.alpha_composite(texts_layer, (0, contents_composite_y))
|
||||||
contents_composite_y = contents_composite_y + texts_layer.height + 30
|
contents_composite_y = contents_composite_y + texts_layer.height + gutter
|
||||||
|
|
||||||
if rating:
|
if rating:
|
||||||
# Add some more margin
|
# Add some more margin
|
||||||
contents_composite_y = contents_composite_y + 30
|
contents_composite_y = contents_composite_y + gutter
|
||||||
rating_layer = generate_rating_layer(rating, content_width)
|
rating_layer = generate_rating_layer(rating, content_width)
|
||||||
contents_layer.alpha_composite(rating_layer, (0, contents_composite_y))
|
contents_layer.alpha_composite(rating_layer, (0, contents_composite_y))
|
||||||
contents_composite_y = contents_composite_y + rating_layer.height + 30
|
contents_composite_y = contents_composite_y + rating_layer.height + gutter
|
||||||
|
|
||||||
contents_layer_box = contents_layer.getbbox()
|
contents_layer_box = contents_layer.getbbox()
|
||||||
contents_layer_height = contents_layer_box[3] - contents_layer_box[1]
|
contents_layer_height = contents_layer_box[3] - contents_layer_box[1]
|
||||||
|
|
||||||
contents_y = math.floor((IMG_HEIGHT - contents_layer_height) / 2)
|
contents_y = math.floor((IMG_HEIGHT - contents_layer_height) / 2)
|
||||||
|
|
||||||
|
if show_instance_layer:
|
||||||
# Remove Instance Layer from centering calculations
|
# Remove Instance Layer from centering calculations
|
||||||
contents_y = contents_y - math.floor((instance_layer.height + gutter) / 2)
|
contents_y = contents_y - math.floor((instance_layer.height + gutter) / 2)
|
||||||
|
|
||||||
|
@ -255,9 +260,56 @@ def generate_preview_image(book, texts={}, picture=None, rating=None):
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def generate_site_preview_image_task():
|
||||||
|
"""generate preview_image for the website"""
|
||||||
|
site = models.SiteSettings.objects.get()
|
||||||
|
|
||||||
|
if site.logo:
|
||||||
|
logo = site.logo
|
||||||
|
else:
|
||||||
|
logo = path.joinpath("static/images/logo-small.png")
|
||||||
|
|
||||||
|
texts = {
|
||||||
|
'text_zero': DOMAIN,
|
||||||
|
'text_one': site.name,
|
||||||
|
'text_three': site.instance_tagline,
|
||||||
|
}
|
||||||
|
|
||||||
|
img = generate_preview_image(texts=texts,
|
||||||
|
picture=logo,
|
||||||
|
show_instance_layer=False)
|
||||||
|
|
||||||
|
file_name = "%s.png" % str(uuid4())
|
||||||
|
image_buffer = BytesIO()
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
old_path = site.preview_image.path
|
||||||
|
except ValueError:
|
||||||
|
old_path = ""
|
||||||
|
|
||||||
|
# Save
|
||||||
|
img.save(image_buffer, format="png")
|
||||||
|
site.preview_image = InMemoryUploadedFile(
|
||||||
|
ContentFile(image_buffer.getvalue()),
|
||||||
|
"preview_image",
|
||||||
|
file_name,
|
||||||
|
"image/png",
|
||||||
|
image_buffer.tell(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
site.save(update_fields=["preview_image"])
|
||||||
|
|
||||||
|
# Clean up old file after saving
|
||||||
|
if os.path.exists(old_path):
|
||||||
|
os.remove(old_path)
|
||||||
|
finally:
|
||||||
|
image_buffer.close()
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def generate_edition_preview_image_task(book_id):
|
def generate_edition_preview_image_task(book_id):
|
||||||
"""generate preview_image"""
|
"""generate preview_image for a book"""
|
||||||
book = models.Book.objects.select_subclasses().get(id=book_id)
|
book = models.Book.objects.select_subclasses().get(id=book_id)
|
||||||
|
|
||||||
rating = models.Review.objects.filter(
|
rating = models.Review.objects.filter(
|
||||||
|
@ -267,14 +319,12 @@ def generate_edition_preview_image_task(book_id):
|
||||||
).aggregate(Avg("rating"))["rating__avg"]
|
).aggregate(Avg("rating"))["rating__avg"]
|
||||||
|
|
||||||
texts = {
|
texts = {
|
||||||
'text_zero': "ADDED A REVIEW",
|
|
||||||
'text_one': book.title,
|
'text_one': book.title,
|
||||||
'text_two': book.subtitle,
|
'text_two': book.subtitle,
|
||||||
'text_three': book.author_text
|
'text_three': book.author_text
|
||||||
}
|
}
|
||||||
|
|
||||||
img = generate_preview_image(book=book,
|
img = generate_preview_image(texts=texts,
|
||||||
texts=texts,
|
|
||||||
picture=book.cover,
|
picture=book.cover,
|
||||||
rating=rating)
|
rating=rating)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue