diff --git a/filer/__init__.py b/filer/__init__.py index 548842206..ebb66f92f 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- # version string following pep-0440 -__version__ = '0.9.112' # pragma: nocover +__version__ = '0.9.116' # pragma: nocover default_app_config = 'filer.apps.FilerConfig' diff --git a/filer/management/commands/fixfoldertrees.py b/filer/management/commands/fixfoldertrees.py index 45f4976ab..fa164a954 100644 --- a/filer/management/commands/fixfoldertrees.py +++ b/filer/management/commands/fixfoldertrees.py @@ -14,6 +14,11 @@ def add_arguments(self, parser): default=False, help='Performs only a check for corrupted folder trees. ' 'Prints all the corruptions it finds.') + parser.add_argument('--force-rebuild', + action='store_true', + dest='force_full_rebuild', + default=False, + help='Force a full tree rebuild.') def handle(self, *args, **options): checker = TreeChecker() @@ -39,4 +44,20 @@ def handle(self, *args, **options): else: self.stdout.write("There are no corruptions\n") else: - checker.rebuild() + if options['force_full_rebuild']: + self.stdout.write("\nPerforming full rebuild...") + checker.manager.rebuild() + else: + self.stdout.write("Checking folder trees before fixing...\n") + checker.check_corruptions() + if checker.full_rebuild: + self.stdout.write("\nPerforming full rebuild...") + checker.manager.rebuild() + elif checker.corrupted_folders: + self.stdout.write("\nPerforming corrupted folders rebuild...") + for folder in checker.get_corrupted_root_nodes(): + self.stdout.write(f"\n\tPerforming partial rebuild for folder {folder.name}") + checker.manager.partial_rebuild(folder.tree_id) + else: + self.stdout.write("There are no corruptions, nothing to be done.\n") + self.stdout.write("\nRebuild Done.") diff --git a/filer/migrations/0006_default_caption_text_size.py b/filer/migrations/0006_default_caption_text_size.py new file mode 100644 index 000000000..32f60395c --- /dev/null +++ b/filer/migrations/0006_default_caption_text_size.py @@ -0,0 +1,15 @@ +from django.db import migrations, models + +class Migration(migrations.Migration): + + dependencies = [ + ('filer', '0005_default_alt_text_size'), + ] + + operations = [ + migrations.AlterField( + model_name='Image', + name='default_caption', + field=models.TextField(null=True), # Use TextField to allow unlimited length + ), + ] \ No newline at end of file diff --git a/filer/models/imagemodels.py b/filer/models/imagemodels.py index c6d39f6a9..66c3ac052 100644 --- a/filer/models/imagemodels.py +++ b/filer/models/imagemodels.py @@ -9,7 +9,6 @@ except ImportError: raise ImportError("The Python Imaging Library was not found.") from datetime import datetime -from django.urls import reverse from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -17,7 +16,6 @@ from django.core.exceptions import ValidationError from filer import settings as filer_settings from filer.models.filemodels import File -from filer.utils.filer_easy_thumbnails import FilerThumbnailer from filer.utils.pil_exif import get_exif_for_file import os @@ -36,7 +34,7 @@ class Image(File): } file_type = 'Image' _icon = "image" - _filename_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.ico'] + _filename_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.ico', '.svg', '.webp'] _height = models.IntegerField(null=True, blank=True) _width = models.IntegerField(null=True, blank=True) @@ -44,17 +42,17 @@ class Image(File): date_taken = models.DateTimeField(_('date taken'), null=True, blank=True, editable=False) - default_alt_text = models.CharField( - _('default alt text'), max_length=255, blank=True, null=True, + default_alt_text = models.TextField( + _('default alt text'), blank=True, null=True, help_text=_('Describes the essence of the image for users who have ' 'images turned off in their browser, or are visually ' 'impaired and using a screen reader; and it is used to ' 'identify images to search engines.')) - default_caption = models.CharField( - _('default caption'), max_length=255, blank=True, null=True, + default_caption = models.TextField( + _('default caption'), blank=True, null=True, help_text=_('Caption text is displayed directly below an image ' - 'plugin to add context; there is a 140-character limit,' - ' including spaces; for images fewer than 200 pixels ' + 'plugin to add context; there is no character limit;' + 'for images fewer than 200 pixels ' 'wide, the caption text is only displayed on hover.')) default_credit = models.CharField( _('default credit text'), max_length=255, blank=True, null=True, @@ -95,10 +93,6 @@ def clean(self): raise ValidationError( "Ensure default credit text has at most 30 characters (" "%s characters found)." % len(self.default_credit)) - if int(len(self.default_caption or '')) > 140: - raise ValidationError( - "Ensure default caption text has at most 140 characters (" - "%s characters found)." % len(self.default_caption)) super(Image, self).clean() def save(self, *args, **kwargs): diff --git a/filer/utils/checktrees.py b/filer/utils/checktrees.py index 836cae099..1a0b4ee0e 100644 --- a/filer/utils/checktrees.py +++ b/filer/utils/checktrees.py @@ -6,7 +6,6 @@ class TreeCorruption(Exception): class TreeChecker(object): - ordering = ['tree_id', 'lft', 'rght'] def __init__(self, folder_manager=None): @@ -19,14 +18,13 @@ def __init__(self, folder_manager=None): else: self.manager = folder_manager - def find_corruptions(self): self.check_corruptions() if (self.full_rebuild or self.corrupted_folders): raise TreeCorruption() def _build_diff_msg(self, expected, actual): - attr_idx = {'lft':0, 'rght':1, 'level':2, 'tree_id':3} + attr_idx = {'lft': 0, 'rght': 1, 'level': 2, 'tree_id': 3} expected, actual = list(expected), list(actual) diff = [] for attr, idx in list(attr_idx.items()): @@ -50,29 +48,31 @@ def get_corrupted_root_nodes(self): if not self.corruption_check_done: self.check_corruptions() corrupted_trees = self.manager.filter( - pk__in=list(self.corrupted_folders.keys())).\ + pk__in=list(self.corrupted_folders.keys())). \ values_list('tree_id', flat=True).distinct() return self.manager.filter( - parent__isnull=True, tree_id__in=corrupted_trees) + parent__isnull=True, deleted_at__isnull=True, tree_id__in=corrupted_trees) def check_tree(self, pk, lft, tree_id, level=0): """ * checks if a certain folder tree is corrupted or not. * uses the same logic as django-mptt's rebuild tree + * ignores deleted folders, same as django-mptt's rebuild """ rght = lft + 1 - child_ids = self.manager.filter(parent__pk=pk).\ + child_ids = self.manager.filter(parent__pk=pk). \ order_by(*self.ordering).values_list('pk', flat=True) for child_id in child_ids: rght = self.check_tree(child_id, rght, tree_id, level + 1) folder = self.manager.get(pk=pk) - expected = (lft, rght, level, tree_id) - actual = (folder.lft, folder.rght, folder.level, folder.tree_id) - if expected != actual: - self.corrupted_folders.setdefault( - pk, self._build_diff_msg(expected, actual)) + if folder.deleted_at is None: + expected = (lft, rght, level, tree_id) + actual = (folder.lft, folder.rght, folder.level, folder.tree_id) + if expected != actual: + self.corrupted_folders.setdefault( + pk, self._build_diff_msg(expected, actual)) return rght + 1 def check_corruptions(self): @@ -82,15 +82,15 @@ def check_corruptions(self): * checks if there are multiple root folders with the same tree id(fixing this will require a full rebuild) """ - tree_duplicates = self.manager.filter(parent=None).\ - values_list('tree_id').\ + tree_duplicates = self.manager.filter(parent=None). \ + values_list('tree_id'). \ annotate(count=Count('id')).filter(count__gt=1) if len(tree_duplicates) > 0: self.full_rebuild = True self.corruption_check_done = True return - pks = self.manager.filter(parent=None).\ + pks = self.manager.filter(parent=None). \ order_by(*self.ordering).values_list('pk', flat=True) idx = 0 for pk in pks: @@ -107,7 +107,4 @@ def rebuild(self): self.manager.rebuild() elif self.corrupted_folders: for folder in self.get_corrupted_root_nodes(): - # since we use an older version of djang-mptt and method - # partial_rebuild does not exist ... - self.manager._rebuild_helper( - folder.pk, 1, folder.tree_id) + self.manager.partial_rebuild(folder.tree_id) diff --git a/setup.py b/setup.py index 6b218a198..c26d0f6ed 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ -from setuptools import setup, find_packages import os +from setuptools import setup, find_packages + try: from setuptest import test except ImportError: @@ -25,8 +26,9 @@ def read(fname): author_email='stefan.foulis@gmail.com', packages=find_packages(), install_requires=( + 'django-js-asset==2.0.0', 'django>=1.11,<3.0', - 'django-mptt>=0.6,<1.0', # the exact version depends on Django + 'django-mptt==0.11.1', # the exact version depends on Django 'django_polymorphic>=0.7,<2.2', 'easy-thumbnails>=2,<3.0', 'Unidecode>=0.04,<1.2',