diff --git a/src/pretix/helpers/__init__.py b/src/pretix/helpers/__init__.py index e69de29bb2..2ead6c5a02 100644 --- a/src/pretix/helpers/__init__.py +++ b/src/pretix/helpers/__init__.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class PretixHelpersConfig(AppConfig): + name = 'pretix.helpers' + label = 'pretixhelpers' + + +default_app_config = 'pretix.helpers.PretixHelpersConfig' diff --git a/src/pretix/helpers/migrations/0001_initial.py b/src/pretix/helpers/migrations/0001_initial.py new file mode 100644 index 0000000000..0915e0ae0a --- /dev/null +++ b/src/pretix/helpers/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-03-20 07:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Thumbnail', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('source', models.CharField(max_length=255)), + ('size', models.CharField(max_length=255)), + ('thumb', models.FileField(upload_to='')), + ], + ), + migrations.AlterUniqueTogether( + name='thumbnail', + unique_together=set([('source', 'size')]), + ), + ] diff --git a/src/pretix/helpers/migrations/__init__.py b/src/pretix/helpers/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pretix/helpers/models.py b/src/pretix/helpers/models.py new file mode 100644 index 0000000000..4e34b3179f --- /dev/null +++ b/src/pretix/helpers/models.py @@ -0,0 +1,10 @@ +from django.db import models + + +class Thumbnail(models.Model): + source = models.CharField(max_length=255) + size = models.CharField(max_length=255) + thumb = models.FileField(upload_to='pub/thumbs/') + + class Meta: + unique_together = (('source', 'size'),) diff --git a/src/pretix/helpers/templatetags/__init__.py b/src/pretix/helpers/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pretix/helpers/templatetags/thumb.py b/src/pretix/helpers/templatetags/thumb.py new file mode 100644 index 0000000000..ddc00d35df --- /dev/null +++ b/src/pretix/helpers/templatetags/thumb.py @@ -0,0 +1,18 @@ +import logging + +from django import template +from django.core.files.storage import default_storage + +from pretix.helpers.thumb import get_thumbnail + +register = template.Library() +logger = logging.getLogger(__name__) + + +@register.filter +def thumb(source, arg): + try: + return get_thumbnail(source, arg).thumb.url + except: + logger.exception('Failed to create thumbnail') + return default_storage.url(source) diff --git a/src/pretix/helpers/thumb.py b/src/pretix/helpers/thumb.py new file mode 100644 index 0000000000..48a85cacb0 --- /dev/null +++ b/src/pretix/helpers/thumb.py @@ -0,0 +1,75 @@ +import hashlib +from io import BytesIO + +from django.core.files.base import ContentFile +from django.core.files.storage import default_storage +from PIL import Image + +from pretix.helpers.models import Thumbnail + + +class ThumbnailError(Exception): + pass + + +def get_sizes(size, imgsize): + crop = False + if size.endswith('^'): + crop = True + size = size[:-1] + + if 'x' in size: + size = [int(p) for p in size.split('x')] + else: + size = [int(size), int(size)] + + if crop: + wfactor = min(1, size[0] / imgsize[0]) + hfactor = min(1, size[1] / imgsize[1]) + if wfactor > hfactor: + return (int(size[0]), int(imgsize[1] * hfactor)), \ + (0, int((imgsize[1] * wfactor - size[1]) / 2), size[0], int((imgsize[1] * wfactor + size[1]) / 2)) + else: + return (int(imgsize[0] * hfactor), int(size[1])), \ + (int((imgsize[0] * hfactor - size[0]) / 2), 0, int((imgsize[0] * hfactor + size[0]) / 2), size[1]) + else: + wfactor = min(1, size[0] / imgsize[0]) + hfactor = min(1, size[1] / imgsize[1]) + if wfactor < hfactor: + return (size[0], int(imgsize[1] * wfactor)), None + else: + return (int(imgsize[0] * hfactor), size[1]), None + + +def create_thumbnail(source, size): + if isinstance(source, str): + source = default_storage.open(source) + image = Image.open(BytesIO(source.read())) + try: + image.load() + except: + raise ThumbnailError('Could not load image') + + scale, crop = get_sizes(size, image.size) + image = image.resize(scale) + print(scale, crop) + if crop: + image = image.crop(crop) + + checksum = hashlib.md5(image.tobytes()).hexdigest() + name = checksum + '.' + size.replace('^', 'c') + '.png' + buffer = BytesIO() + image.save(fp=buffer, format='PNG') + imgfile = ContentFile(buffer.getvalue()) + + t = Thumbnail.objects.create(source=source, size=size) + t.thumb.save(name, imgfile) + return t + + +def get_thumbnail(source, size): + # Assumes files are immutable + try: + return Thumbnail.objects.get(source=source, size=size) + except Thumbnail.DoesNotExist: + return create_thumbnail(source, size) diff --git a/src/pretix/presale/templates/pretixpresale/event/base.html b/src/pretix/presale/templates/pretixpresale/event/base.html index 49e2a85b32..b1b97d0e80 100644 --- a/src/pretix/presale/templates/pretixpresale/event/base.html +++ b/src/pretix/presale/templates/pretixpresale/event/base.html @@ -1,7 +1,7 @@ {% extends "pretixpresale/base.html" %} {% load i18n %} {% load staticfiles %} -{% load thumbnail %} +{% load thumb %} {% load eventurl %} {% load safelink %} {% block thetitle %} @@ -27,7 +27,7 @@ {% if event_logo %} - + {% else %}

diff --git a/src/pretix/presale/templates/pretixpresale/event/index.html b/src/pretix/presale/templates/pretixpresale/event/index.html index 486b15502f..04b734718f 100644 --- a/src/pretix/presale/templates/pretixpresale/event/index.html +++ b/src/pretix/presale/templates/pretixpresale/event/index.html @@ -3,7 +3,7 @@ {% load l10n %} {% load eventurl %} {% load money %} -{% load thumbnail %} +{% load thumb %} {% load eventsignal %} {% load rich_text %} {% block title %}{% trans "Presale" %}{% endblock %} @@ -225,7 +225,7 @@ data-title="{{ item.name|force_escape|force_escape }}" {# Yes, double-escape to prevent XSS in lightbox #} data-lightbox="{{ item.id }}"> - {{ item.name }} {% endif %} @@ -348,7 +348,7 @@ data-title="{{ item.name|force_escape|force_escape }}" {# Yes, double-escape to prevent XSS in lightbox #} data-lightbox="{{ item.id }}"> - {{ item.name }} {% endif %} diff --git a/src/pretix/presale/templates/pretixpresale/event/voucher.html b/src/pretix/presale/templates/pretixpresale/event/voucher.html index 188c23422d..8d92771efd 100644 --- a/src/pretix/presale/templates/pretixpresale/event/voucher.html +++ b/src/pretix/presale/templates/pretixpresale/event/voucher.html @@ -4,7 +4,7 @@ {% load money %} {% load eventurl %} {% load eventsignal %} -{% load thumbnail %} +{% load thumb %} {% load rich_text %} {% block title %}{% trans "Voucher redemption" %}{% endblock %} @@ -42,7 +42,7 @@ data-title="{{ item.name|force_escape|force_escape }}" {# Yes, double-escape to prevent XSS in lightbox #} data-lightbox="{{ item.id }}"> - {{ item.name }} {% endif %} @@ -139,7 +139,7 @@ data-title="{{ item.name|force_escape|force_escape }}" {# Yes, double-escape to prevent XSS in lightbox #} data-lightbox="{{ item.id }}"> - {{ item.name }} {% endif %} diff --git a/src/pretix/presale/templates/pretixpresale/event/waitinglist.html b/src/pretix/presale/templates/pretixpresale/event/waitinglist.html index 64d0b2150a..dce37b0276 100644 --- a/src/pretix/presale/templates/pretixpresale/event/waitinglist.html +++ b/src/pretix/presale/templates/pretixpresale/event/waitinglist.html @@ -1,5 +1,5 @@ {% extends "pretixpresale/event/base.html" %} -{% load i18n eventurl thumbnail bootstrap3 %} +{% load i18n eventurl bootstrap3 %} {% block title %}{% trans "Waiting list" %}{% endblock %} {% block content %}

{% trans "Add me to the waiting list" %}

diff --git a/src/pretix/presale/templates/pretixpresale/organizers/base.html b/src/pretix/presale/templates/pretixpresale/organizers/base.html index 407f9c30b8..e335d8b2f2 100644 --- a/src/pretix/presale/templates/pretixpresale/organizers/base.html +++ b/src/pretix/presale/templates/pretixpresale/organizers/base.html @@ -1,7 +1,7 @@ {% extends "pretixpresale/base.html" %} {% load i18n %} {% load staticfiles %} -{% load thumbnail %} +{% load thumb %} {% load eventurl %} {% block thetitle %} {% block title %}{% endblock %}{% if url_name != "organizer.index" %} :: {% endif %}{{ organizer.name }} @@ -11,7 +11,8 @@
{% if organizer_logo %} - + {% else %}

{{ organizer.name }}

diff --git a/src/pretix/presale/views/widget.py b/src/pretix/presale/views/widget.py index a7d62eac5c..28580e112b 100644 --- a/src/pretix/presale/views/widget.py +++ b/src/pretix/presale/views/widget.py @@ -1,6 +1,5 @@ import hashlib import json -from urllib.parse import urljoin from django.conf import settings from django.contrib.staticfiles import finders @@ -19,7 +18,6 @@ from django.views.decorators.http import condition from django.views.i18n import ( get_formats, get_javascript_catalog, js_catalog_template, ) -from easy_thumbnails.files import get_thumbnailer from lxml import etree from pretix.base.i18n import language @@ -27,6 +25,7 @@ from pretix.base.models import CartPosition, Voucher from pretix.base.services.cart import error_messages from pretix.base.settings import GlobalSettingsObject from pretix.base.templatetags.rich_text import rich_text +from pretix.helpers.thumb import get_thumbnail from pretix.presale.views.cart import get_or_create_cart_id from pretix.presale.views.event import ( get_grouped_items, item_group_by_category, @@ -136,8 +135,7 @@ def price_dict(price): def get_picture(picture): - thumb = get_thumbnailer(picture)['productlist'] - return urljoin(settings.SITE_URL, thumb.url) + return get_thumbnail(picture.name, '60x60^').thumb.url class WidgetAPIProductList(View): diff --git a/src/pretix/settings.py b/src/pretix/settings.py index 108ea41b0f..51837c6f6c 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -225,6 +225,7 @@ INSTALLED_APPS = [ 'pretix.presale', 'pretix.multidomain', 'pretix.api', + 'pretix.helpers', 'rest_framework', 'django_filters', 'compressor', @@ -239,7 +240,6 @@ INSTALLED_APPS = [ 'pretix.plugins.reports', 'pretix.plugins.checkinlists', 'pretix.plugins.pretixdroid', - 'easy_thumbnails', 'django_markup', 'django_otp', 'django_otp.plugins.otp_totp', @@ -463,13 +463,6 @@ MESSAGE_TAGS = { } MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' -THUMBNAIL_ALIASES = { - '': { - 'productlist': {'size': (60, 60), 'crop': True}, - 'logo': {'size': (5000, 120), 'crop': False}, - }, -} - loglevel = 'DEBUG' if DEBUG else 'INFO' LOGGING = { diff --git a/src/requirements/production.txt b/src/requirements/production.txt index 5dd5b869f3..a7559ffe79 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -10,7 +10,7 @@ django-hierarkey==1.0.*,>=1.0.3 django-filter==1.0.* reportlab==3.4.* PyPDF2==1.26.* -easy-thumbnails==2.4.* +Pillow django-libsass libsass django-otp==0.3.* diff --git a/src/setup.py b/src/setup.py index 918c7f5d20..18d11f7483 100644 --- a/src/setup.py +++ b/src/setup.py @@ -74,7 +74,7 @@ setup( 'django-hierarkey==1.0.*,>=1.0.2', 'django-filter==1.0.*', 'reportlab==3.4.*', - 'easy-thumbnails==2.4.*', + 'Pillow', 'PyPDF2==1.26.*', 'django-libsass', 'libsass',