From b6c0267e2852f61f26bfc5c9bfb3895a5608dcd7 Mon Sep 17 00:00:00 2001 From: r0668522 Date: Sun, 2 Aug 2020 13:41:47 +0200 Subject: [PATCH 1/5] celery support --- photologue/tasks.py | 0 photologue/utils/zipfile.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 photologue/tasks.py create mode 100644 photologue/utils/zipfile.py diff --git a/photologue/tasks.py b/photologue/tasks.py new file mode 100644 index 00000000..e69de29b diff --git a/photologue/utils/zipfile.py b/photologue/utils/zipfile.py new file mode 100644 index 00000000..e69de29b From 2e07871bc7959aea60023567d8af58614c28553e Mon Sep 17 00:00:00 2001 From: r0668522 Date: Sun, 2 Aug 2020 14:30:49 +0200 Subject: [PATCH 2/5] celery support --- 6.0.0 | 178 +++++++++++++++++++ photologue/forms.py | 142 ++------------- photologue/migrations/0012_zipuploadmodel.py | 27 +++ photologue/models.py | 38 +++- photologue/tasks.py | 18 ++ photologue/utils/zipfile.py | 114 ++++++++++++ 6 files changed, 389 insertions(+), 128 deletions(-) create mode 100644 6.0.0 create mode 100644 photologue/migrations/0012_zipuploadmodel.py diff --git a/6.0.0 b/6.0.0 new file mode 100644 index 00000000..6ffab27a --- /dev/null +++ b/6.0.0 @@ -0,0 +1,178 @@ +Collecting Pillow + Using cached https://files.pythonhosted.org/packages/3e/02/b09732ca4b14405ff159c470a612979acfc6e8645dc32f83ea0129709f7a/Pillow-7.2.0.tar.gz +Installing collected packages: Pillow + Running setup.py install for Pillow: started + Running setup.py install for Pillow: finished with status 'error' + Complete output from command C:\Users\Andreas\Documents\contrib\django-photologue\venv\Scripts\python.exe -u -c "import setuptools, tokenize;__file__='C:\\Users\\Andreas\\AppData\\Local\\Temp\\pip-install-zi1ztzx5\\Pillow\\setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record C:\Users\Andreas\AppData\Local\Temp\pip-record-4ezzh894\install-record.txt --single-version-externally-managed --compile --install-headers C:\Users\Andreas\Documents\contrib\django-photologue\venv\include\site\python3.8\Pillow: + running install + running build + running build_py + creating build + creating build\lib.win32-3.8 + creating build\lib.win32-3.8\PIL + copying src\PIL\BdfFontFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\BlpImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\BmpImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\BufrStubImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\ContainerIO.py -> build\lib.win32-3.8\PIL + copying src\PIL\CurImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\DcxImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\DdsImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\EpsImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\ExifTags.py -> build\lib.win32-3.8\PIL + copying src\PIL\features.py -> build\lib.win32-3.8\PIL + copying src\PIL\FitsStubImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\FliImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\FontFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\FpxImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\FtexImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\GbrImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\GdImageFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\GifImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\GimpGradientFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\GimpPaletteFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\GribStubImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\Hdf5StubImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\IcnsImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\IcoImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\Image.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageChops.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageCms.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageColor.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageDraw.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageDraw2.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageEnhance.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageFilter.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageFont.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageGrab.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageMath.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageMode.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageMorph.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageOps.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImagePalette.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImagePath.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageQt.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageSequence.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageShow.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageStat.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageTk.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageTransform.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImageWin.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\ImtImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\IptcImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\Jpeg2KImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\JpegImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\JpegPresets.py -> build\lib.win32-3.8\PIL + copying src\PIL\McIdasImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\MicImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\MpegImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\MpoImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\MspImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PaletteFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\PalmImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PcdImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PcfFontFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\PcxImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PdfImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PdfParser.py -> build\lib.win32-3.8\PIL + copying src\PIL\PixarImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PngImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PpmImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PsdImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\PSDraw.py -> build\lib.win32-3.8\PIL + copying src\PIL\PyAccess.py -> build\lib.win32-3.8\PIL + copying src\PIL\SgiImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\SpiderImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\SunImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\TarIO.py -> build\lib.win32-3.8\PIL + copying src\PIL\TgaImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\TiffImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\TiffTags.py -> build\lib.win32-3.8\PIL + copying src\PIL\WalImageFile.py -> build\lib.win32-3.8\PIL + copying src\PIL\WebPImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\WmfImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\XbmImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\XpmImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\XVThumbImagePlugin.py -> build\lib.win32-3.8\PIL + copying src\PIL\_binary.py -> build\lib.win32-3.8\PIL + copying src\PIL\_tkinter_finder.py -> build\lib.win32-3.8\PIL + copying src\PIL\_util.py -> build\lib.win32-3.8\PIL + copying src\PIL\_version.py -> build\lib.win32-3.8\PIL + copying src\PIL\__init__.py -> build\lib.win32-3.8\PIL + copying src\PIL\__main__.py -> build\lib.win32-3.8\PIL + running egg_info + writing src\Pillow.egg-info\PKG-INFO + writing dependency_links to src\Pillow.egg-info\dependency_links.txt + writing top-level names to src\Pillow.egg-info\top_level.txt + reading manifest file 'src\Pillow.egg-info\SOURCES.txt' + reading manifest template 'MANIFEST.in' + warning: no files found matching '*.c' + warning: no files found matching '*.h' + warning: no files found matching '*.sh' + warning: no previously-included files found matching '.appveyor.yml' + warning: no previously-included files found matching '.coveragerc' + warning: no previously-included files found matching '.editorconfig' + warning: no previously-included files found matching '.readthedocs.yml' + warning: no previously-included files found matching 'codecov.yml' + warning: no previously-included files matching '.git*' found anywhere in distribution + warning: no previously-included files matching '*.pyc' found anywhere in distribution + warning: no previously-included files matching '*.so' found anywhere in distribution + no previously-included directories found matching '.ci' + writing manifest file 'src\Pillow.egg-info\SOURCES.txt' + running build_ext + + + The headers or library files could not be found for zlib, + a required dependency when compiling Pillow from source. + + Please see the install instructions at: + https://pillow.readthedocs.io/en/latest/installation.html + + Traceback (most recent call last): + File "C:\Users\Andreas\AppData\Local\Temp\pip-install-zi1ztzx5\Pillow\setup.py", line 864, in + setup( + File "C:\Users\Andreas\Documents\contrib\django-photologue\venv\lib\site-packages\setuptools-40.8.0-py3.8.egg\setuptools\__init__.py", line 145, in setup + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\core.py", line 148, in setup + dist.run_commands() + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 966, in run_commands + self.run_command(cmd) + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 985, in run_command + cmd_obj.run() + File "C:\Users\Andreas\Documents\contrib\django-photologue\venv\lib\site-packages\setuptools-40.8.0-py3.8.egg\setuptools\command\install.py", line 61, in run + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\command\install.py", line 545, in run + self.run_command('build') + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\cmd.py", line 313, in run_command + self.distribution.run_command(command) + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 985, in run_command + cmd_obj.run() + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\command\build.py", line 135, in run + self.run_command(cmd_name) + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\cmd.py", line 313, in run_command + self.distribution.run_command(command) + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 985, in run_command + cmd_obj.run() + File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\command\build_ext.py", line 340, in run + self.build_extensions() + File "C:\Users\Andreas\AppData\Local\Temp\pip-install-zi1ztzx5\Pillow\setup.py", line 694, in build_extensions + raise RequiredDependencyException(f) + __main__.RequiredDependencyException: zlib + + During handling of the above exception, another exception occurred: + + Traceback (most recent call last): + File "", line 1, in + File "C:\Users\Andreas\AppData\Local\Temp\pip-install-zi1ztzx5\Pillow\setup.py", line 918, in + raise RequiredDependencyException(msg) + __main__.RequiredDependencyException: + + The headers or library files could not be found for zlib, + a required dependency when compiling Pillow from source. + + Please see the install instructions at: + https://pillow.readthedocs.io/en/latest/installation.html + + + + ---------------------------------------- diff --git a/photologue/forms.py b/photologue/forms.py index 9c2bbba0..240837d0 100644 --- a/photologue/forms.py +++ b/photologue/forms.py @@ -1,53 +1,23 @@ import zipfile from zipfile import BadZipFile import logging -import os -from io import BytesIO - -from PIL import Image - - from django import forms from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages -from django.contrib.sites.models import Site from django.conf import settings -from django.utils.encoding import force_text -from django.template.defaultfilters import slugify -from django.core.files.base import ContentFile - -from .models import Gallery, Photo +from .tasks import parse_zip as parse_zip_task +from .models import Gallery, ZipUploadModel +from photologue.utils.zipfile import parse_zip logger = logging.getLogger('photologue.forms') +# Use celery to speed up page loading after uploading photos? +USE_CELERY = getattr(settings, 'PHOTOLOGUE_USE_CELERY', False) -class UploadZipForm(forms.Form): - zip_file = forms.FileField() - title = forms.CharField(label=_('Title'), - max_length=250, - required=False, - help_text=_('All uploaded photos will be given a title made up of this title + a ' - 'sequential number.
This field is required if creating a new ' - 'gallery, but is optional when adding to an existing gallery - if ' - 'not supplied, the photo titles will be creating from the existing ' - 'gallery name.')) - gallery = forms.ModelChoiceField(Gallery.objects.all(), - label=_('Gallery'), - required=False, - help_text=_('Select a gallery to add these images to. Leave this empty to ' - 'create a new gallery from the supplied title.')) - caption = forms.CharField(label=_('Caption'), - required=False, - help_text=_('Caption will be added to all photos.')) - description = forms.CharField(label=_('Description'), - required=False, - help_text=_('A description of this Gallery. Only required for new galleries.')) - is_public = forms.BooleanField(label=_('Is public'), - initial=True, - required=False, - help_text=_('Uncheck this to make the uploaded ' - 'gallery and included photographs private.')) +class UploadZipForm(forms.ModelForm): + class Meta: + model = ZipUploadModel + exclude = () def clean_zip_file(self): """Open the zip file a first time, to check that it is a valid zip archive. @@ -85,90 +55,12 @@ def clean(self): def save(self, request=None, zip_file=None): if not zip_file: zip_file = self.cleaned_data['zip_file'] - zip = zipfile.ZipFile(zip_file) - count = 1 - current_site = Site.objects.get(id=settings.SITE_ID) - if self.cleaned_data['gallery']: - logger.debug('Using pre-existing gallery.') - gallery = self.cleaned_data['gallery'] + if USE_CELERY: + instance = super().save(commit=False) + instance.zip_file = zip_file + instance.save() # We need to save before making task to get an id + parse_zip_task.delay(instance.id) else: - logger.debug( - force_text('Creating new gallery "{0}".').format(self.cleaned_data['title'])) - gallery = Gallery.objects.create(title=self.cleaned_data['title'], - slug=slugify(self.cleaned_data['title']), - description=self.cleaned_data['description'], - is_public=self.cleaned_data['is_public']) - gallery.sites.add(current_site) - for filename in sorted(zip.namelist()): - - logger.debug('Reading file "{}".'.format(filename)) - - if filename.startswith('__') or filename.startswith('.'): - logger.debug('Ignoring file "{}".'.format(filename)) - continue - - if os.path.dirname(filename): - logger.warning('Ignoring file "{}" as it is in a subfolder; all images should be in the top ' - 'folder of the zip.'.format(filename)) - if request: - messages.warning(request, - _('Ignoring file "{filename}" as it is in a subfolder; all images should ' - 'be in the top folder of the zip.').format(filename=filename), - fail_silently=True) - continue - - data = zip.read(filename) - - if not len(data): - logger.debug('File "{}" is empty.'.format(filename)) - continue - - photo_title_root = self.cleaned_data['title'] if self.cleaned_data['title'] else gallery.title - - # A photo might already exist with the same slug. So it's somewhat inefficient, - # but we loop until we find a slug that's available. - while True: - photo_title = ' '.join([photo_title_root, str(count)]) - slug = slugify(photo_title) - if Photo.objects.filter(slug=slug).exists(): - count += 1 - continue - break - - photo = Photo(title=photo_title, - slug=slug, - caption=self.cleaned_data['caption'], - is_public=self.cleaned_data['is_public']) - - # Basic check that we have a valid image. - try: - file = BytesIO(data) - opened = Image.open(file) - opened.verify() - except Exception: - # Pillow doesn't recognize it as an image. - # If a "bad" file is found we just skip it. - # But we do flag this both in the logs and to the user. - logger.error('Could not process file "{}" in the .zip archive.'.format( - filename)) - if request: - messages.warning(request, - _('Could not process file "{0}" in the .zip archive.').format( - filename), - fail_silently=True) - continue - - contentfile = ContentFile(data) - photo.image.save(filename, contentfile) - photo.save() - photo.sites.add(current_site) - gallery.photos.add(photo) - count += 1 - - zip.close() - - if request: - messages.success(request, - _('The photos have been added to gallery "{0}".').format( - gallery.title), - fail_silently=True) + parse_zip(zip_file, self.cleaned_data['gallery'], self.cleaned_data['title'], + self.cleaned_data['description'], self.cleaned_data['caption'], self.cleaned_data['is_public'], + request=request) diff --git a/photologue/migrations/0012_zipuploadmodel.py b/photologue/migrations/0012_zipuploadmodel.py new file mode 100644 index 00000000..dc532dd0 --- /dev/null +++ b/photologue/migrations/0012_zipuploadmodel.py @@ -0,0 +1,27 @@ +# Generated by Django 3.0.3 on 2020-08-02 11:29 + +import django.core.files.storage +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('photologue', '0011_auto_20190223_2138'), + ] + + operations = [ + migrations.CreateModel( + name='ZipUploadModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('zip_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to='gallery-zip-files/')), + ('title', models.CharField(blank=True, help_text='All uploaded photos will be given a title made up of this title + a sequential number.
This field is required if creating a new gallery, but is optional when adding to an existing gallery - if not supplied, the photo titles will be creating from the existing gallery name.', max_length=255, null=True)), + ('caption', models.TextField(blank=True, help_text='Caption will be added to all photos.', null=True)), + ('description', models.TextField(blank=True, help_text='A description of this Gallery. Only required for new galleries.', null=True)), + ('is_public', models.BooleanField(default=True, help_text='Uncheck this to make the uploaded gallery and included photographs private.')), + ('gallery', models.ForeignKey(blank=True, help_text='Select a gallery to add these images to. Leave this empty to create a new gallery from the supplied title.', null=True, on_delete=django.db.models.deletion.CASCADE, to='photologue.Gallery')), + ], + ), + ] diff --git a/photologue/models.py b/photologue/models.py index 845bad32..73018494 100644 --- a/photologue/models.py +++ b/photologue/models.py @@ -13,7 +13,7 @@ from django.contrib.sites.models import Site from django.core.exceptions import ValidationError from django.core.files.base import ContentFile -from django.core.files.storage import default_storage +from django.core.files.storage import default_storage, FileSystemStorage from django.core.validators import RegexValidator from django.db import models from django.db.models.signals import post_save @@ -51,6 +51,14 @@ # Photologue image path relative to media root PHOTOLOGUE_DIR = getattr(settings, 'PHOTOLOGUE_DIR', 'photologue') +# Use celery to speed up page loading after uploading photos? +USE_CELERY = getattr(settings, 'PHOTOLOGUE_USE_CELERY', False) + +# When using celery, where do we want to temporarily store our zip-file after upload? +TEMP_ZIP_STORAGE = getattr(settings, 'PHOTOLOGUE_TEMP_ZIP_STORAGE', FileSystemStorage()) +if USE_CELERY: + from photologue import tasks + # Look for user function to define file paths PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None) if PHOTOLOGUE_PATH is not None: @@ -464,7 +472,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._old_image = self.image - def save(self, *args, **kwargs): + def save(self, *args, running_in_task=False, **kwargs): image_has_changed = False if self._get_pk_val() and (self._old_image != self.image): image_has_changed = True @@ -490,7 +498,12 @@ def save(self, *args, **kwargs): except: logger.error('Failed to read EXIF DateTimeOriginal', exc_info=True) super().save(*args, **kwargs) - self.pre_cache() + if not USE_CELERY or running_in_task: + self.pre_cache() + else: + # If we upload lots of photos at once (in a zip), there will already be a task made for opening the zip + # this means we are now probably already 'running in a task' and there is no reason to add additional tasks + tasks.pre_cache.delay(self.id) def delete(self): assert self._get_pk_val() is not None, \ @@ -899,5 +912,24 @@ def add_default_site(instance, created, **kwargs): instance.sites.add(Site.objects.get_current()) +class ZipUploadModel(models.Model): + zip_file = models.FileField(upload_to='gallery-zip-files/', storage=TEMP_ZIP_STORAGE) + title = models.CharField(max_length=255, null=True, blank=True, + help_text=_('All uploaded photos will be given a title made up of this title + a ' + 'sequential number.
This field is required if creating a new ' + 'gallery, but is optional when adding to an existing gallery - if ' + 'not supplied, the photo titles will be creating from the existing ' + 'gallery name.')) + gallery = models.ForeignKey(Gallery, null=True, blank=True, on_delete=models.CASCADE, + help_text=_('Select a gallery to add these images to. Leave this empty to ' + 'create a new gallery from the supplied title.')) + caption = models.TextField(null=True, blank=True, + help_text=_('Caption will be added to all photos.')) + description = models.TextField(null=True, blank=True, + help_text=_('A description of this Gallery. Only required for new galleries.')) + is_public = models.BooleanField(default=True, help_text=_( + 'Uncheck this to make the uploaded gallery and included photographs private.')) + + post_save.connect(add_default_site, sender=Gallery) post_save.connect(add_default_site, sender=Photo) diff --git a/photologue/tasks.py b/photologue/tasks.py index e69de29b..8d8b2a84 100644 --- a/photologue/tasks.py +++ b/photologue/tasks.py @@ -0,0 +1,18 @@ +from celery import shared_task +from .models import ZipUploadModel, Photo +from .utils.zipfile import parse_zip as perform_parse_zip + + +@shared_task(name='galleries.tasks.pre_cache', max_retries=5) +def pre_cache(photo_id): + image = Photo.objects.get(id=photo_id) + image.pre_cache() + + +@shared_task(name='galleries.tasks.parse_zip', max_retries=5) +def parse_zip(zip_file_id): + instance = ZipUploadModel.objects.get(id=zip_file_id) + perform_parse_zip(instance.zip_file, instance.gallery, instance.title, instance.description, instance.caption, + instance.is_public) + instance.zip_file.delete(False) + instance.delete() diff --git a/photologue/utils/zipfile.py b/photologue/utils/zipfile.py index e69de29b..2515f3bc 100644 --- a/photologue/utils/zipfile.py +++ b/photologue/utils/zipfile.py @@ -0,0 +1,114 @@ +import os +from io import BytesIO +from django.conf import settings +from django.template.defaultfilters import slugify +from django.core.files.base import ContentFile +from PIL import Image +from django.contrib.sites.models import Site +import zipfile +from photologue.models import Photo, Gallery +import logging +from django.contrib import messages +from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import force_text + +logger = logging.getLogger('photologue.zip') + +# Use celery to speed up page loading after uploading photos? +USE_CELERY = getattr(settings, 'PHOTOLOGUE_USE_CELERY', False) + + +def parse_zip(zip_file, gallery, title, description, caption, is_public, request=None): + zip = zipfile.ZipFile(zip_file) + count = 1 + current_site = Site.objects.get(id=settings.SITE_ID) + if gallery: + logger.debug('Using pre-existing gallery.') + else: + logger.debug( + force_text('Creating new gallery "{0}".').format(title)) + gallery = Gallery.objects.create(title=title, + slug=slugify(title), + description=description, + is_public=is_public) + gallery.sites.add(current_site) + + for filename in sorted(zip.namelist()): + + logger.debug('Reading file "{}".'.format(filename)) + + if filename.startswith('__') or filename.startswith('.'): + logger.debug('Ignoring file "{}".'.format(filename)) + continue + + if os.path.dirname(filename): + logger.warning('Ignoring file "{}" as it is in a subfolder; all images should be in the top ' + 'folder of the zip.'.format(filename)) + if request: + messages.warning(request, + _('Ignoring file "{filename}" as it is in a subfolder; all images should ' + 'be in the top folder of the zip.').format(filename=filename), + fail_silently=True) + continue + + data = zip.read(filename) + + if not len(data): + logger.debug('File "{}" is empty.'.format(filename)) + continue + + photo_title_root = title if title else gallery.title + + # A photo might already exist with the same slug. So it's somewhat inefficient, + # but we loop until we find a slug that's available. + while True: + photo_title = ' '.join([photo_title_root, str(count)]) + slug = slugify(photo_title) + if Photo.objects.filter(slug=slug).exists(): + count += 1 + continue + break + + photo = Photo(title=photo_title, + slug=slug, + caption=caption, + is_public=is_public) + + # Basic check that we have a valid image. + try: + file = BytesIO(data) + opened = Image.open(file) + opened.verify() + except Exception: + # Pillow doesn't recognize it as an image. + # If a "bad" file is found we just skip it. + # But we do flag this both in the logs and to the user. + logger.error('Could not process file "{}" in the .zip archive.'.format( + filename)) + if request: + messages.warning(request, + _('Could not process file "{0}" in the .zip archive.').format( + filename), + fail_silently=True) + continue + + contentfile = ContentFile(data) + photo.image.save(filename, contentfile) + + if USE_CELERY: + # This if-statement isn't really needed, but makes it more obvious what's happening + photo.save(running_in_task=True) + else: + photo.save() + + photo.sites.add(current_site) + gallery.photos.add(photo) + count += 1 + + zip.close() + + if request: + messages.success(request, + _('The photos have been added to gallery "{0}".').format( + gallery.title), + fail_silently=True) From c9900efa58f249643e0a1df38c86a5ccf2e77035 Mon Sep 17 00:00:00 2001 From: Andreas Milants <43340764+AndreasMilants@users.noreply.github.com> Date: Fri, 4 Sep 2020 10:36:22 +0200 Subject: [PATCH 3/5] Delete 6.0.0 --- 6.0.0 | 178 ---------------------------------------------------------- 1 file changed, 178 deletions(-) delete mode 100644 6.0.0 diff --git a/6.0.0 b/6.0.0 deleted file mode 100644 index 6ffab27a..00000000 --- a/6.0.0 +++ /dev/null @@ -1,178 +0,0 @@ -Collecting Pillow - Using cached https://files.pythonhosted.org/packages/3e/02/b09732ca4b14405ff159c470a612979acfc6e8645dc32f83ea0129709f7a/Pillow-7.2.0.tar.gz -Installing collected packages: Pillow - Running setup.py install for Pillow: started - Running setup.py install for Pillow: finished with status 'error' - Complete output from command C:\Users\Andreas\Documents\contrib\django-photologue\venv\Scripts\python.exe -u -c "import setuptools, tokenize;__file__='C:\\Users\\Andreas\\AppData\\Local\\Temp\\pip-install-zi1ztzx5\\Pillow\\setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record C:\Users\Andreas\AppData\Local\Temp\pip-record-4ezzh894\install-record.txt --single-version-externally-managed --compile --install-headers C:\Users\Andreas\Documents\contrib\django-photologue\venv\include\site\python3.8\Pillow: - running install - running build - running build_py - creating build - creating build\lib.win32-3.8 - creating build\lib.win32-3.8\PIL - copying src\PIL\BdfFontFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\BlpImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\BmpImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\BufrStubImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\ContainerIO.py -> build\lib.win32-3.8\PIL - copying src\PIL\CurImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\DcxImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\DdsImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\EpsImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\ExifTags.py -> build\lib.win32-3.8\PIL - copying src\PIL\features.py -> build\lib.win32-3.8\PIL - copying src\PIL\FitsStubImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\FliImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\FontFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\FpxImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\FtexImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\GbrImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\GdImageFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\GifImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\GimpGradientFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\GimpPaletteFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\GribStubImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\Hdf5StubImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\IcnsImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\IcoImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\Image.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageChops.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageCms.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageColor.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageDraw.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageDraw2.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageEnhance.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageFilter.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageFont.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageGrab.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageMath.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageMode.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageMorph.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageOps.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImagePalette.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImagePath.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageQt.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageSequence.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageShow.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageStat.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageTk.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageTransform.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImageWin.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\ImtImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\IptcImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\Jpeg2KImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\JpegImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\JpegPresets.py -> build\lib.win32-3.8\PIL - copying src\PIL\McIdasImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\MicImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\MpegImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\MpoImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\MspImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PaletteFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\PalmImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PcdImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PcfFontFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\PcxImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PdfImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PdfParser.py -> build\lib.win32-3.8\PIL - copying src\PIL\PixarImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PngImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PpmImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PsdImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\PSDraw.py -> build\lib.win32-3.8\PIL - copying src\PIL\PyAccess.py -> build\lib.win32-3.8\PIL - copying src\PIL\SgiImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\SpiderImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\SunImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\TarIO.py -> build\lib.win32-3.8\PIL - copying src\PIL\TgaImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\TiffImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\TiffTags.py -> build\lib.win32-3.8\PIL - copying src\PIL\WalImageFile.py -> build\lib.win32-3.8\PIL - copying src\PIL\WebPImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\WmfImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\XbmImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\XpmImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\XVThumbImagePlugin.py -> build\lib.win32-3.8\PIL - copying src\PIL\_binary.py -> build\lib.win32-3.8\PIL - copying src\PIL\_tkinter_finder.py -> build\lib.win32-3.8\PIL - copying src\PIL\_util.py -> build\lib.win32-3.8\PIL - copying src\PIL\_version.py -> build\lib.win32-3.8\PIL - copying src\PIL\__init__.py -> build\lib.win32-3.8\PIL - copying src\PIL\__main__.py -> build\lib.win32-3.8\PIL - running egg_info - writing src\Pillow.egg-info\PKG-INFO - writing dependency_links to src\Pillow.egg-info\dependency_links.txt - writing top-level names to src\Pillow.egg-info\top_level.txt - reading manifest file 'src\Pillow.egg-info\SOURCES.txt' - reading manifest template 'MANIFEST.in' - warning: no files found matching '*.c' - warning: no files found matching '*.h' - warning: no files found matching '*.sh' - warning: no previously-included files found matching '.appveyor.yml' - warning: no previously-included files found matching '.coveragerc' - warning: no previously-included files found matching '.editorconfig' - warning: no previously-included files found matching '.readthedocs.yml' - warning: no previously-included files found matching 'codecov.yml' - warning: no previously-included files matching '.git*' found anywhere in distribution - warning: no previously-included files matching '*.pyc' found anywhere in distribution - warning: no previously-included files matching '*.so' found anywhere in distribution - no previously-included directories found matching '.ci' - writing manifest file 'src\Pillow.egg-info\SOURCES.txt' - running build_ext - - - The headers or library files could not be found for zlib, - a required dependency when compiling Pillow from source. - - Please see the install instructions at: - https://pillow.readthedocs.io/en/latest/installation.html - - Traceback (most recent call last): - File "C:\Users\Andreas\AppData\Local\Temp\pip-install-zi1ztzx5\Pillow\setup.py", line 864, in - setup( - File "C:\Users\Andreas\Documents\contrib\django-photologue\venv\lib\site-packages\setuptools-40.8.0-py3.8.egg\setuptools\__init__.py", line 145, in setup - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\core.py", line 148, in setup - dist.run_commands() - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 966, in run_commands - self.run_command(cmd) - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 985, in run_command - cmd_obj.run() - File "C:\Users\Andreas\Documents\contrib\django-photologue\venv\lib\site-packages\setuptools-40.8.0-py3.8.egg\setuptools\command\install.py", line 61, in run - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\command\install.py", line 545, in run - self.run_command('build') - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\cmd.py", line 313, in run_command - self.distribution.run_command(command) - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 985, in run_command - cmd_obj.run() - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\command\build.py", line 135, in run - self.run_command(cmd_name) - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\cmd.py", line 313, in run_command - self.distribution.run_command(command) - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\dist.py", line 985, in run_command - cmd_obj.run() - File "C:\Users\Andreas\AppData\Local\Programs\Python\Python38-32\lib\distutils\command\build_ext.py", line 340, in run - self.build_extensions() - File "C:\Users\Andreas\AppData\Local\Temp\pip-install-zi1ztzx5\Pillow\setup.py", line 694, in build_extensions - raise RequiredDependencyException(f) - __main__.RequiredDependencyException: zlib - - During handling of the above exception, another exception occurred: - - Traceback (most recent call last): - File "", line 1, in - File "C:\Users\Andreas\AppData\Local\Temp\pip-install-zi1ztzx5\Pillow\setup.py", line 918, in - raise RequiredDependencyException(msg) - __main__.RequiredDependencyException: - - The headers or library files could not be found for zlib, - a required dependency when compiling Pillow from source. - - Please see the install instructions at: - https://pillow.readthedocs.io/en/latest/installation.html - - - - ---------------------------------------- From f8fe14661cbc5be952a60517765368cda9203dd0 Mon Sep 17 00:00:00 2001 From: andreasmilants Date: Fri, 4 Sep 2020 13:08:06 +0200 Subject: [PATCH 4/5] changed comments on pr --- photologue/forms.py | 35 ++++++++++++++++++++++------------- photologue/models.py | 17 ++++++++--------- photologue/tasks.py | 5 ++--- photologue/utils/zipfile.py | 17 +++++++++-------- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/photologue/forms.py b/photologue/forms.py index 240837d0..021791c3 100644 --- a/photologue/forms.py +++ b/photologue/forms.py @@ -4,14 +4,28 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from .tasks import parse_zip as parse_zip_task from .models import Gallery, ZipUploadModel -from photologue.utils.zipfile import parse_zip +from photologue.utils.zipfile import handle_zip logger = logging.getLogger('photologue.forms') -# Use celery to speed up page loading after uploading photos? -USE_CELERY = getattr(settings, 'PHOTOLOGUE_USE_CELERY', False) +RUN_ASYNC = getattr(settings, 'PHOTOLOGUE_PROCESSING_MODE', 'sync') == 'async' + +if RUN_ASYNC: + ASYNC_IMPLEMENTATION = getattr(settings, 'PHOTOLOGUE_ASYNC_SERVICE', 'celery') + if ASYNC_IMPLEMENTATION == 'celery': + from .tasks import parse_zip as parse_zip_task + + + def zip_handler(instance, request): + instance.save() + parse_zip_task.delay(instance.id) + else: + raise NotImplementedError('Django-photologue only supports celery at this moment') + +else: + def zip_handler(instance, request): + handle_zip(instance, request=request) class UploadZipForm(forms.ModelForm): @@ -55,12 +69,7 @@ def clean(self): def save(self, request=None, zip_file=None): if not zip_file: zip_file = self.cleaned_data['zip_file'] - if USE_CELERY: - instance = super().save(commit=False) - instance.zip_file = zip_file - instance.save() # We need to save before making task to get an id - parse_zip_task.delay(instance.id) - else: - parse_zip(zip_file, self.cleaned_data['gallery'], self.cleaned_data['title'], - self.cleaned_data['description'], self.cleaned_data['caption'], self.cleaned_data['is_public'], - request=request) + + instance = super().save(commit=False) + instance.zip_file = zip_file + zip_handler(instance, request) diff --git a/photologue/models.py b/photologue/models.py index 73018494..6160f09a 100644 --- a/photologue/models.py +++ b/photologue/models.py @@ -51,13 +51,11 @@ # Photologue image path relative to media root PHOTOLOGUE_DIR = getattr(settings, 'PHOTOLOGUE_DIR', 'photologue') -# Use celery to speed up page loading after uploading photos? -USE_CELERY = getattr(settings, 'PHOTOLOGUE_USE_CELERY', False) +# Use tasks to speed up page loading after uploading photos? +RUN_ASYNC = getattr(settings, 'PHOTOLOGUE_PROCESSING_MODE', 'sync') == 'async' # When using celery, where do we want to temporarily store our zip-file after upload? -TEMP_ZIP_STORAGE = getattr(settings, 'PHOTOLOGUE_TEMP_ZIP_STORAGE', FileSystemStorage()) -if USE_CELERY: - from photologue import tasks +TEMP_ZIP_STORAGE = getattr(settings, 'PHOTOLOGUE_TEMP_ZIP_STORAGE', default_storage) # Look for user function to define file paths PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None) @@ -498,11 +496,12 @@ def save(self, *args, running_in_task=False, **kwargs): except: logger.error('Failed to read EXIF DateTimeOriginal', exc_info=True) super().save(*args, **kwargs) - if not USE_CELERY or running_in_task: - self.pre_cache() - else: + if (not RUN_ASYNC) or running_in_task: # If we upload lots of photos at once (in a zip), there will already be a task made for opening the zip # this means we are now probably already 'running in a task' and there is no reason to add additional tasks + self.pre_cache() + else: + from photologue import tasks # Imported here to avoid circular dependency tasks.pre_cache.delay(self.id) def delete(self): @@ -916,7 +915,7 @@ class ZipUploadModel(models.Model): zip_file = models.FileField(upload_to='gallery-zip-files/', storage=TEMP_ZIP_STORAGE) title = models.CharField(max_length=255, null=True, blank=True, help_text=_('All uploaded photos will be given a title made up of this title + a ' - 'sequential number.
This field is required if creating a new ' + 'sequential number. This field is required if creating a new ' 'gallery, but is optional when adding to an existing gallery - if ' 'not supplied, the photo titles will be creating from the existing ' 'gallery name.')) diff --git a/photologue/tasks.py b/photologue/tasks.py index 8d8b2a84..d04d7534 100644 --- a/photologue/tasks.py +++ b/photologue/tasks.py @@ -1,6 +1,6 @@ from celery import shared_task from .models import ZipUploadModel, Photo -from .utils.zipfile import parse_zip as perform_parse_zip +from .utils.zipfile import handle_zip @shared_task(name='galleries.tasks.pre_cache', max_retries=5) @@ -12,7 +12,6 @@ def pre_cache(photo_id): @shared_task(name='galleries.tasks.parse_zip', max_retries=5) def parse_zip(zip_file_id): instance = ZipUploadModel.objects.get(id=zip_file_id) - perform_parse_zip(instance.zip_file, instance.gallery, instance.title, instance.description, instance.caption, - instance.is_public) + handle_zip(instance) instance.zip_file.delete(False) instance.delete() diff --git a/photologue/utils/zipfile.py b/photologue/utils/zipfile.py index 2515f3bc..aa7938d7 100644 --- a/photologue/utils/zipfile.py +++ b/photologue/utils/zipfile.py @@ -14,11 +14,14 @@ logger = logging.getLogger('photologue.zip') -# Use celery to speed up page loading after uploading photos? -USE_CELERY = getattr(settings, 'PHOTOLOGUE_USE_CELERY', False) +def handle_zip(zip_upload_model, request=None): + _parse_zip(zip_upload_model.zip_file, zip_upload_model.gallery, zip_upload_model.title, zip_upload_model.description, + zip_upload_model.caption, + zip_upload_model.is_public, request=request) -def parse_zip(zip_file, gallery, title, description, caption, is_public, request=None): + +def _parse_zip(zip_file, gallery, title, description, caption, is_public, request=None): zip = zipfile.ZipFile(zip_file) count = 1 current_site = Site.objects.get(id=settings.SITE_ID) @@ -95,11 +98,9 @@ def parse_zip(zip_file, gallery, title, description, caption, is_public, request contentfile = ContentFile(data) photo.image.save(filename, contentfile) - if USE_CELERY: - # This if-statement isn't really needed, but makes it more obvious what's happening - photo.save(running_in_task=True) - else: - photo.save() + # running_in_task is ignored if not in async mode. + # This function shouldn't care about async or not + photo.save(running_in_task=True) photo.sites.add(current_site) gallery.photos.add(photo) From 5c5d659f2902e1bd08555e43fa6bcdb7f1175a74 Mon Sep 17 00:00:00 2001 From: r0668522 Date: Sun, 27 Sep 2020 20:23:00 +0200 Subject: [PATCH 5/5] some documentation --- docs/pages/customising/settings.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/pages/customising/settings.rst b/docs/pages/customising/settings.rst index 2d79bca7..1a23e727 100644 --- a/docs/pages/customising/settings.rst +++ b/docs/pages/customising/settings.rst @@ -104,3 +104,19 @@ If the setting is ``True``, the admin interface is slightly changed: fix this would be welcome! .. _Django's site framework: http://django.readthedocs.org/en/latest/ref/contrib/sites.html + + +PHOTOLOGUE_RUN_ASYNC +-------------------- + + Default: ``False`` + +Whether or not Photologue will start processing uploaded files in an async manner. + +PHOTOLOGUE_ASYNC_SERVICE +------------------------ + + Default: ``'celery'`` + +Which async task service Photologue should use. At this moment, only celery is supported. +Setting up a celery worker is out of the scope of this documentation.