diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 022670216..caa639119 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,20 +8,21 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] + python-version: ['3.8', '3.9', '3.10', '3.11' ] requirements-file: [ django-2.2.txt, - django-3.0.txt, - django-3.1.txt, django-3.2.txt, django-4.0.txt, - django-4.1.txt + django-4.1.txt, + django-4.2.txt, ] exclude: - python-version: 3.7 requirements-file: django-4.0.txt - python-version: 3.7 requirements-file: django-4.1.txt + - python-version: 3.7 + requirements-file: django-4.2.txt - python-version: 3.9 requirements-file: django-2.2.txt - python-version: 3.10 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 52eff302a..4e00f9975 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - id: flake8 - repo: https://github.com/asottile/yesqa - rev: v1.4.0 + rev: v1.5.0 hooks: - id: yesqa diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e798a9db5..623976ec5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,9 +2,13 @@ CHANGELOG ========= -unreleased -========== +Unpublished +=========== +* Refactored directory list view for significant performance increses +* Remove thumbnail generation from the directory list view request response cylce +* Add Django 4.2 support +* Add thumbnail view for faster visual management of image libraries * Fix File.objects.only() query required for deleting user who own files. 2.2.5 (2023-06-11) diff --git a/addon.json b/addon.json index 1319b4cbb..ae6f35615 100644 --- a/addon.json +++ b/addon.json @@ -3,7 +3,6 @@ "installed-apps": [ "filer", "easy_thumbnails", - "mptt", "polymorphic" ] } diff --git a/docs/installation.rst b/docs/installation.rst index 55358a765..74abc059e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -15,7 +15,6 @@ Dependencies ------------ * `Django`_ >= 2.2 -* `django-mptt`_ >=0.6 * `easy_thumbnails`_ >= 2.0 * `django-polymorphic`_ >= 0.7 * `Pillow`_ >=2.3.0 (with JPEG and ZLIB support, `PIL`_ may work but is not supported) @@ -41,7 +40,6 @@ Add ``"filer"`` and related apps to your project's ``INSTALLED_APPS`` setting an ... 'easy_thumbnails', 'filer', - 'mptt', ... ] @@ -147,7 +145,6 @@ generation errors, two options are provided to help when working with ``django- .. _django-polymorphic: https://github.com/bconstantin/django_polymorphic .. _easy_thumbnails: https://github.com/SmileyChris/easy-thumbnails .. _sorl.thumbnail: http://thumbnail.sorl.net/ -.. _django-mptt: https://github.com/django-mptt/django-mptt/ .. _Pillow: http://pypi.python.org/pypi/Pillow/ .. _Pillow doc: https://pillow.readthedocs.io/en/latest/installation.html .. _PIL: http://www.pythonware.com/products/pil/ diff --git a/docs/settings.rst b/docs/settings.rst index 4ac30668e..14ff93860 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -6,7 +6,7 @@ Settings ``FILER_ENABLE_PERMISSIONS`` ---------------------------- -Activate the or not the Permission check on the files and folders before +Activate the or not the Permission check on the files and folders before displaying them in the admin. When set to ``False`` it gives all the authorization to staff members based on standard Django model permissions. @@ -73,7 +73,7 @@ Public storage uses ``DEFAULT_FILE_STORAGE`` as default storage backend. ``UPLOAD_TO`` is the function to generate the path relative to the storage root. The default generates a random path like ``1d/a5/1da50fee-5003-46a1-a191-b547125053a8/filename.jpg``. This -will be applied whenever a file is uploaded or moved between public (without permission checks) and +will be applied whenever a file is uploaded or moved between public (without permission checks) and private (with permission checks) storages. Defaults to ``'filer.utils.generate_filename.randomized'``. Overriding single keys is possible, for example just set your custom ``UPLOAD_TO``:: @@ -117,12 +117,12 @@ Defaults to using the DefaultServer (doh)! This will serve the files with the dj The number of items (Folders, Files) that should be displayed per page in admin. -Defaults to ``20`` +Defaults to ``100`` ``FILER_SUBJECT_LOCATION_IMAGE_DEBUG`` -------------------------------------- -Draws a red circle around to point in the image that was used to do the +Draws a red circle around to point in the image that was used to do the subject location aware image cropping. Defaults to ``False`` @@ -153,6 +153,15 @@ Defines the path element common to all canonical file URLs. Defaults to ``'canonical/'`` +``FILER_UPLOADER_MAX_FILES`` +---------------------------- + +Limit of files to upload by one drag and drop event. This is to avoid +extensive accidental uploads, e.g. by dragging to root direcory onto an +upload field. + +Defaults to ``100``. + ``FILER_UPLOADER_CONNECTIONS`` ------------------------------ @@ -161,3 +170,10 @@ Number of simultaneous AJAX uploads. Defaults to 3. If your database backend is SQLite it would be set to 1 by default. This allows to avoid ``database is locked`` errors on SQLite during multiple simultaneous file uploads. + +``FILER_UPLOADER_MAX_FILE_SIZE`` +-------------------------------- + +Limits the maximal file size if set. Takes an integer (file size in MB). + +Defaults to ``None``. diff --git a/filer/admin/fileadmin.py b/filer/admin/fileadmin.py index 737566d23..2b5bf2d9c 100644 --- a/filer/admin/fileadmin.py +++ b/filer/admin/fileadmin.py @@ -1,12 +1,17 @@ from django import forms from django.contrib.admin.utils import unquote -from django.http import HttpResponseRedirect -from django.urls import reverse +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.urls import path, reverse from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ +from easy_thumbnails.files import get_thumbnailer +from easy_thumbnails.options import ThumbnailOptions + from .. import settings -from ..models import File +from ..models import BaseImage, File +from ..settings import DEFERRED_THUMBNAIL_SIZES from .permissions import PrimitivePermissionAwareModelAdmin from .tools import AdminContext, admin_url_params_encoded, popup_status @@ -147,5 +152,25 @@ def display_canonical(self, instance): display_canonical.allow_tags = True display_canonical.short_description = _('canonical URL') + def get_urls(self): + return super().get_urls() + [ + path("icon//", + self.admin_site.admin_view(self.icon_view), + name="filer_file_fileicon") + ] + + def icon_view(self, request, file_id: int, size: int) -> HttpResponse: + if size not in DEFERRED_THUMBNAIL_SIZES: + # Only allow icon sizes relevant for the admin + raise Http404 + file = get_object_or_404(File, pk=file_id) + if not isinstance(file, BaseImage): + raise Http404() + + thumbnailer = get_thumbnailer(file) + thumbnail_options = ThumbnailOptions({'size': (size, size), "crop": True}) + thumbnail = thumbnailer.get_thumbnail(thumbnail_options, generate=True) + return HttpResponseRedirect(thumbnail.url) + FileAdmin.fieldsets = FileAdmin.build_fieldsets() diff --git a/filer/admin/folderadmin.py b/filer/admin/folderadmin.py index de9ce1fab..4b375e373 100644 --- a/filer/admin/folderadmin.py +++ b/filer/admin/folderadmin.py @@ -13,6 +13,7 @@ from django.core.exceptions import PermissionDenied, ValidationError from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db import models, router +from django.db.models import OuterRef, Subquery from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import re_path, reverse @@ -22,9 +23,11 @@ from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy +from easy_thumbnails.models import Thumbnail + from .. import settings from ..models import File, Folder, FolderPermission, FolderRoot, ImagesWithMissingData, UnsortedImages, tools -from ..settings import FILER_IMAGE_MODEL, FILER_PAGINATE_BY +from ..settings import FILER_IMAGE_MODEL, FILER_PAGINATE_BY, TABLE_LIST_TYPE from ..thumbnail_processors import normalize_subject_location from ..utils.compatibility import get_delete_permission from ..utils.filer_easy_thumbnails import FilerActionThumbnailer @@ -54,7 +57,7 @@ class Meta: class FolderAdmin(PrimitivePermissionAwareModelAdmin): list_display = ('name',) exclude = ('parent',) - list_per_page = 20 + list_per_page = 100 list_filter = ('owner',) search_fields = ['name'] autocomplete_fields = ['owner'] @@ -258,6 +261,14 @@ def directory_listing(self, request, folder_id=None, viewtype=None): folder = get_object_or_404(self.get_queryset(request), id=folder_id) request.session['filer_last_folder_id'] = folder_id + list_type = get_directory_listing_type(request) or settings.FILER_FOLDER_ADMIN_DEFAULT_LIST_TYPE + if list_type == TABLE_LIST_TYPE: + size = "40x40" # Prefetch thumbnails for listing + size_x2 = "80x80" + else: + size = "160x160" # Prefetch thumbnails for thumbnail view + size_x2 = "320x320" + # Check actions to see if any are available on this changelist actions = self.get_actions(request) @@ -302,17 +313,16 @@ def directory_listing(self, request, folder_id=None, viewtype=None): file_qs = folder.files.all() show_result_count = False - folder_qs = folder_qs.order_by('name') + folder_qs = folder_qs.order_by('name').select_related("owner") order_by = request.GET.get('order_by', None) - if order_by is not None: - order_by = order_by.split(',') - order_by = [field for field in order_by - if re.sub(r'^-', '', field) in self.order_by_file_fields] - if len(order_by) > 0: - file_qs = file_qs.order_by(*order_by) - - folder_children = [] - folder_files = [] + if order_by is None: + order_by = "file" + order_by = order_by.split(',') + order_by = [field for field in order_by + if re.sub(r'^-', '', field) in self.order_by_file_fields] + if len(order_by) > 0: + file_qs = file_qs.order_by(*order_by) + if folder.is_root and not search_mode: virtual_items = folder.virtual_folders else: @@ -332,8 +342,20 @@ def directory_listing(self, request, folder_id=None, viewtype=None): if folder.is_root: folder_qs = folder_qs.exclude(**root_exclude_kwargs) - folder_children += folder_qs - folder_files += file_qs + # Annotate thumbnail status + thumbnail_qs = ( + Thumbnail.objects + .filter( + source__name=OuterRef("file"), + modified__gte=OuterRef("modified_at"), + ) + .exclude(name__contains="upscale") # TODO: Check WHY not used by directory listing + .order_by("-modified") + ) + file_qs = file_qs.annotate( + thumbnail_name=Subquery(thumbnail_qs.filter(name__contains=f"__{size}_").values_list("name")[:1]), + thumbnailx2_name=Subquery(thumbnail_qs.filter(name__contains=f"__{size_x2}_").values_list("name")[:1]) + ).select_related("owner") try: permissions = { @@ -345,16 +367,13 @@ def directory_listing(self, request, folder_id=None, viewtype=None): except: # noqa permissions = {} - if order_by is None or len(order_by) == 0: - folder_files.sort() - - items = folder_children + folder_files + items = list(itertools.chain(folder_qs, file_qs)) paginator = Paginator(items, FILER_PAGINATE_BY) # Are we moving to clipboard? if request.method == 'POST' and '_save' not in request.POST: # TODO: Refactor/remove clipboard parts - for f in folder_files: + for f in folder_qs: if "move-to-clipboard-%d" % (f.id,) in request.POST: clipboard = tools.get_user_clipboard(request.user) if f.has_edit_permission(request): @@ -409,7 +428,6 @@ def directory_listing(self, request, folder_id=None, viewtype=None): except EmptyPage: paginated_items = paginator.page(paginator.num_pages) - list_type = get_directory_listing_type(request) or settings.FILER_FOLDER_ADMIN_DEFAULT_LIST_TYPE context = self.admin_site.each_context(request) context.update({ 'folder': folder, @@ -420,6 +438,8 @@ def directory_listing(self, request, folder_id=None, viewtype=None): 'paginated_items': paginated_items, 'virtual_items': virtual_items, 'uploader_connections': settings.FILER_UPLOADER_CONNECTIONS, + 'max_files': settings.FILER_UPLOADER_MAX_FILES, + 'max_filesize': settings.FILER_UPLOADER_MAX_FILE_SIZE, 'permissions': permissions, 'permstest': userperms_for_request(folder, request), 'current_url': request.path, @@ -427,8 +447,8 @@ def directory_listing(self, request, folder_id=None, viewtype=None): 'search_string': ' '.join(search_terms), 'q': urlquote(q), 'show_result_count': show_result_count, - 'folder_children': folder_children, - 'folder_files': folder_files, + 'folder_children': folder_qs, + 'folder_files': file_qs, 'limit_search_to_folder': limit_search_to_folder, 'is_popup': popup_status(request), 'filer_admin_context': AdminContext(request), @@ -744,8 +764,7 @@ def delete_files_or_folders(self, request, files_queryset, folders_queryset): folder_ids = set() for folder in folders_queryset: folder_ids.add(folder.id) - folder_ids.update( - folder.get_descendants().values_list('id', flat=True)) + folder_ids.update(folder.get_descendants_ids()) for f in File.objects.filter(folder__in=folder_ids): self.log_deletion(request, f, force_str(f)) f.delete() @@ -869,12 +888,8 @@ def _list_all_destination_folders(self, request, folders_queryset, current_folde return list(self._list_all_destination_folders_recursive(request, folders_queryset, current_folder, root_folders, allow_self, 0)) def _move_files_and_folders_impl(self, files_queryset, folders_queryset, destination): - for f in files_queryset: - f.folder = destination - f.save() - for f in folders_queryset: - f.move_to(destination, 'last-child') - f.save() + files_queryset.update(folder=destination) + folders_queryset.update(parent=destination) def move_files_and_folders(self, request, files_queryset, folders_queryset): opts = self.model._meta diff --git a/filer/migrations/0016_alter_folder_index_together_remove_folder_level_and_more.py b/filer/migrations/0016_alter_folder_index_together_remove_folder_level_and_more.py new file mode 100644 index 000000000..1ba266912 --- /dev/null +++ b/filer/migrations/0016_alter_folder_index_together_remove_folder_level_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.dev20230606055133 on 2023-06-07 14:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('filer', '0015_alter_file_owner_alter_file_polymorphic_ctype_and_more'), + ] + + operations = [ + migrations.AlterIndexTogether( + name='folder', + index_together=set(), + ), + migrations.RemoveField( + model_name='folder', + name='level', + ), + migrations.RemoveField( + model_name='folder', + name='lft', + ), + migrations.RemoveField( + model_name='folder', + name='rght', + ), + migrations.RemoveField( + model_name='folder', + name='tree_id', + ), + ] diff --git a/filer/models/filemodels.py b/filer/models/filemodels.py index c703f2a1d..fd80f3e8c 100644 --- a/filer/models/filemodels.py +++ b/filer/models/filemodels.py @@ -1,20 +1,19 @@ import hashlib import mimetypes import os -from datetime import datetime +from datetime import datetime, timezone from django.conf import settings from django.core.exceptions import ValidationError from django.core.files.base import ContentFile from django.db import models from django.urls import NoReverseMatch, reverse -from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from polymorphic.managers import PolymorphicManager -from polymorphic.query import PolymorphicQuerySet from polymorphic.models import PolymorphicModel +from polymorphic.query import PolymorphicQuerySet from .. import settings as filer_settings from ..fields.multistorage_file import MultiStorageFileField @@ -418,7 +417,7 @@ def logical_path(self): """ folder_path = [] if self.folder: - folder_path.extend(self.folder.get_ancestors()) + folder_path.extend(self.folder.logical_path) folder_path.append(self.logical_folder) return folder_path diff --git a/filer/models/foldermodels.py b/filer/models/foldermodels.py index 904b23498..54fa9c5a4 100644 --- a/filer/models/foldermodels.py +++ b/filer/models/foldermodels.py @@ -1,6 +1,3 @@ -import warnings -from urllib.parse import quote as urlquote - from django.conf import settings from django.contrib.auth import models as auth_models from django.core.exceptions import ValidationError @@ -11,8 +8,6 @@ from django.utils.html import format_html, format_html_join from django.utils.translation import gettext_lazy as _ -import mptt - from .. import settings as filer_settings from . import mixins @@ -42,8 +37,8 @@ def __get_id_list(self, user, attr): deny_list = set() group_ids = user.groups.all().values_list('id', flat=True) q = Q(user=user) | Q(group__in=group_ids) | Q(everybody=True) - perms = self.filter(q).order_by('folder__tree_id', 'folder__level', - 'folder__lft') + perms = self.filter(q) + for perm in perms: p = getattr(perm, attr) @@ -70,9 +65,9 @@ def __get_id_list(self, user, attr): if perm.type in [FolderPermission.ALL, FolderPermission.CHILDREN]: if p == FolderPermission.ALLOW: - allow_list.update(perm.folder.get_descendants().values_list('id', flat=True)) + allow_list.update(perm.folder.get_descendants_ids()) else: - deny_list.update(perm.folder.get_descendants().values_list('id', flat=True)) + deny_list.update(perm.folder.get_descendants_ids()) # Deny has precedence over allow return allow_list - deny_list @@ -93,16 +88,9 @@ class Folder(models.Model, mixins.IconsMixin): can_have_subfolders = True _icon = 'plainfolder' - # explicitly define MPTT fields which would otherwise change - # and create a migration, depending on django-mptt version - # (see: https://github.com/django-mptt/django-mptt/pull/578) - level = models.PositiveIntegerField(editable=False) - lft = models.PositiveIntegerField(editable=False) - rght = models.PositiveIntegerField(editable=False) - parent = models.ForeignKey( 'self', - verbose_name=('parent'), + verbose_name=_('parent'), null=True, blank=True, related_name='children', @@ -139,8 +127,6 @@ class Folder(models.Model, mixins.IconsMixin): ) class Meta: - # see: https://github.com/django-mptt/django-mptt/pull/577 - index_together = (('tree_id', 'lft'),) unique_together = (('parent', 'name'),) ordering = ('name',) permissions = (("can_use_directory_listing", @@ -183,22 +169,21 @@ def logical_path(self): """ folder_path = [] if self.parent: - folder_path.extend(self.parent.get_ancestors()) + folder_path.extend(self.parent.logical_path) folder_path.append(self.parent) return folder_path + def get_descendants_ids(self): + desc = [] + for child in self.children.all(): + desc.append(child.id) + desc.extend(child.get_descendants_ids()) + return desc + @property def pretty_logical_path(self): return format_html('/{}', format_html_join('/', '{0}', ((f.name,) for f in self.logical_path + [self]))) - @property - def quoted_logical_path(self): - warnings.warn( - 'Method filer.foldermodels.Folder.quoted_logical_path is deprecated and will be removed', - DeprecationWarning, stacklevel=2, - ) - return urlquote(self.pretty_logical_path) - def has_edit_permission(self, request): return request.user.has_perm("filer.change_folder") and self.has_generic_permission(request, 'edit') @@ -262,13 +247,6 @@ def contains_folder(self, folder_name): return False -# MPTT registration -try: - mptt.register(Folder) -except mptt.AlreadyRegistered: - pass - - class FolderPermission(models.Model): ALL = 0 THIS = 1 diff --git a/filer/settings.py b/filer/settings.py index 7a077630b..54ab28e63 100644 --- a/filer/settings.py +++ b/filer/settings.py @@ -36,7 +36,7 @@ FILER_ALLOW_REGULAR_USERS_TO_ADD_ROOT_FOLDERS = getattr(settings, 'FILER_ALLOW_REGULAR_USERS_TO_ADD_ROOT_FOLDERS', False) FILER_IS_PUBLIC_DEFAULT = getattr(settings, 'FILER_IS_PUBLIC_DEFAULT', True) -FILER_PAGINATE_BY = getattr(settings, 'FILER_PAGINATE_BY', 20) +FILER_PAGINATE_BY = getattr(settings, 'FILER_PAGINATE_BY', 100) _ICON_SIZES = getattr(settings, 'FILER_ADMIN_ICON_SIZES', ('16', '32', '48', '64')) if not _ICON_SIZES: @@ -247,6 +247,11 @@ def update_server_settings(settings, defaults, s, t): _uploader_connections = 3 FILER_UPLOADER_CONNECTIONS = getattr( settings, 'FILER_UPLOADER_CONNECTIONS', _uploader_connections) +FILER_UPLOADER_MAX_FILES = getattr( + settings, 'FILER_UPLOADER_MAX_FILES', 100) +FILER_UPLOADER_MAX_FILE_SIZE = getattr( + settings, 'FILER_UPLOADER_MAX_FILE_SIZE', None) + FILER_DUMP_PAYLOAD = getattr(settings, 'FILER_DUMP_PAYLOAD', False) # Whether the filer shall dump the files payload @@ -274,3 +279,5 @@ def update_server_settings(settings, defaults, s, t): 'template': 'admin/filer/folder/directory_thumbnail_list.html', }, } + +DEFERRED_THUMBNAIL_SIZES = (40, 80, 160) diff --git a/filer/static/filer/js/addons/dropzone.init.js b/filer/static/filer/js/addons/dropzone.init.js index bb0f5f6d6..13e27a528 100644 --- a/filer/static/filer/js/addons/dropzone.init.js +++ b/filer/static/filer/js/addons/dropzone.init.js @@ -72,8 +72,7 @@ djQuery(function ($) { url: dropzoneUrl, paramName: 'file', maxFiles: 1, - // for now disabled as we don't have the correct file size limit - // maxFilesize: dropzone.data(dataMaxFileSize) || 20, // MB + maxFilesize: this.dataset.maxFilesize, previewTemplate: $(dropzoneTemplateSelector).html(), clickable: false, addRemoveLinks: false, diff --git a/filer/static/filer/js/addons/table-dropzone.js b/filer/static/filer/js/addons/table-dropzone.js index a556130ef..35dab8c91 100644 --- a/filer/static/filer/js/addons/table-dropzone.js +++ b/filer/static/filer/js/addons/table-dropzone.js @@ -65,7 +65,12 @@ if (django.jQuery) { baseUrl = dropzoneBase.data('url'); baseFolderTitle = dropzoneBase.data('folder-name'); - $('body').data('url', baseUrl).data('folder-name', baseFolderTitle).addClass('js-filer-dropzone'); + $('body') + .data('url', baseUrl) + .data('folder-name', baseFolderTitle) + .data('max-files', dropzoneBase.data('max-files')) + .data('max-filesize', dropzoneBase.data('max-files')) + .addClass('js-filer-dropzone'); } Cl.mediator.subscribe('filer-upload-in-progress', destroyDropzones); @@ -80,9 +85,8 @@ if (django.jQuery) { var dropzoneInstance = new Dropzone(this, { url: dropzoneUrl, paramName: 'file', - maxFiles: 100, - // for now disabled as we don't have the correct file size limit - // maxFilesize: dropzone.data(dataMaxFileSize) || 20, // MB + maxFiles: parseInt(dropzone.data('max-files')) || 100, + maxFilesize: parseInt(dropzone.data('max-filesize')), // no default previewTemplate: '
', clickable: false, addRemoveLinks: false, diff --git a/filer/templates/admin/filer/folder/change_form.html b/filer/templates/admin/filer/folder/change_form.html index 70d09c62d..840edd41c 100644 --- a/filer/templates/admin/filer/folder/change_form.html +++ b/filer/templates/admin/filer/folder/change_form.html @@ -6,7 +6,7 @@ {% trans "Home" %} {% trans "Filer" %}{% trans "root"|title %} - {% for ancestor_folder in original.get_ancestors %} + {% for ancestor_folder in original.logical_path %} › {{ ancestor_folder.name }} {% endfor %} › {{ original.name }} diff --git a/filer/templates/admin/filer/folder/directory_listing.html b/filer/templates/admin/filer/folder/directory_listing.html index 789b3f887..5da8a535c 100644 --- a/filer/templates/admin/filer/folder/directory_listing.html +++ b/filer/templates/admin/filer/folder/directory_listing.html @@ -211,14 +211,20 @@

 

+ data-max-uploader-connections="{{ uploader_connections }}" + data-max-files="{{ max_files|safe }}" + {% if max_filesize %}}data-max-filesize="{{ max_filesize|safe }}"{% endif %} + > {% trans "Upload Files" %} {% elif folder.is_unsorted_uploads %} + data-max-uploader-connections="{{ uploader_connections }}" + data-max-files="{{ max_files|safe }}" + {% if max_filesize %}}data-max-filesize="{{ max_filesize|safe }}"{% endif %} + > {% trans "Upload Files" %} {% endif %} diff --git a/filer/templates/admin/filer/folder/directory_table_list.html b/filer/templates/admin/filer/folder/directory_table_list.html index b1b3656fb..0e780132d 100644 --- a/filer/templates/admin/filer/folder/directory_table_list.html +++ b/filer/templates/admin/filer/folder/directory_table_list.html @@ -1,7 +1,10 @@ {% load i18n l10n admin_list filer_tags filer_admin_tags static thumbnail %}