diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index caa639119..8acb93ca0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,33 +10,11 @@ jobs: matrix: python-version: ['3.8', '3.9', '3.10', '3.11' ] requirements-file: [ - django-2.2.txt, django-3.2.txt, django-4.0.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 - requirements-file: django-2.2.txt - - python-version: 3.10 - requirements-file: django-3.0.txt - - python-version: 3.10 - requirements-file: django-3.1.txt - - python-version: 3.11 - requirements-file: django-2.2.txt - - python-version: 3.11 - requirements-file: django-3.0.txt - - python-version: 3.11 - requirements-file: django-3.1.txt os: [ ubuntu-20.04, ] diff --git a/README.rst b/README.rst index 8e22ed370..6770fb8d9 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Django Filer ============ -|pypi| |build| |python| |django| |coverage| +|pypi| |python| |django| |coverage| **django Filer** is a file management application for django that makes handling of files and images a breeze. @@ -52,12 +52,9 @@ for all the details on how to install, configure and use django-filer. .. |pypi| image:: https://badge.fury.io/py/django-filer.svg :target: http://badge.fury.io/py/django-filer -.. |build| image:: https://travis-ci.org/django-cms/django-filer.svg?branch=master - :target: https://travis-ci.org/django-cms/django-filer .. |coverage| image:: https://codecov.io/gh/django-cms/django-filer/branch/master/graph/badge.svg :target: https://codecov.io/gh/django-cms/django-filer - -.. |python| image:: https://img.shields.io/badge/python-3.7+-blue.svg +.. |python| image:: https://img.shields.io/badge/python-3.8+-blue.svg :target: https://pypi.org/project/django-filer/ -.. |django| image:: https://img.shields.io/badge/django-2.2,%203.2,%204.1-blue.svg +.. |django| image:: https://img.shields.io/badge/django-3.2+-blue.svg :target: https://www.djangoproject.com/ diff --git a/filer/__init__.py b/filer/__init__.py index 46216d690..d9734f004 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -14,5 +14,3 @@ """ __version__ = '2.3rc1' - -default_app_config = 'filer.apps.FilerConfig' diff --git a/filer/admin/clipboardadmin.py b/filer/admin/clipboardadmin.py index 76428ff35..70e59b5fe 100644 --- a/filer/admin/clipboardadmin.py +++ b/filer/admin/clipboardadmin.py @@ -1,7 +1,7 @@ from django.contrib import admin, messages from django.forms.models import modelform_factory from django.http import JsonResponse -from django.urls import re_path +from django.urls import path from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt @@ -37,21 +37,21 @@ class ClipboardAdmin(admin.ModelAdmin): def get_urls(self): return [ - re_path(r'^operations/paste_clipboard_to_folder/$', - self.admin_site.admin_view(views.paste_clipboard_to_folder), - name='filer-paste_clipboard_to_folder'), - re_path(r'^operations/discard_clipboard/$', - self.admin_site.admin_view(views.discard_clipboard), - name='filer-discard_clipboard'), - re_path(r'^operations/delete_clipboard/$', - self.admin_site.admin_view(views.delete_clipboard), - name='filer-delete_clipboard'), - re_path(r'^operations/upload/(?P[0-9]+)/$', - ajax_upload, - name='filer-ajax_upload'), - re_path(r'^operations/upload/no_folder/$', - ajax_upload, - name='filer-ajax_upload'), + path('operations/paste_clipboard_to_folder/', + self.admin_site.admin_view(views.paste_clipboard_to_folder), + name='filer-paste_clipboard_to_folder'), + path('operations/discard_clipboard/', + self.admin_site.admin_view(views.discard_clipboard), + name='filer-discard_clipboard'), + path('operations/delete_clipboard/', + self.admin_site.admin_view(views.delete_clipboard), + name='filer-delete_clipboard'), + path('operations/upload//', + ajax_upload, + name='filer-ajax_upload'), + path('operations/upload/no_folder/', + ajax_upload, + name='filer-ajax_upload'), ] + super().get_urls() def get_model_perms(self, *args, **kwargs): @@ -144,7 +144,7 @@ def ajax_upload(request, folder_id=None): data['original_image'] = file_obj.url return JsonResponse(data) else: - form_errors = '; '.join(['%s: %s' % ( + form_errors = '; '.join(['{}: {}'.format( field, ', '.join(errors)) for field, errors in list( uploadform.errors.items()) diff --git a/filer/admin/fileadmin.py b/filer/admin/fileadmin.py index a4e5251a5..da04c593a 100644 --- a/filer/admin/fileadmin.py +++ b/filer/admin/fileadmin.py @@ -80,7 +80,7 @@ def response_change(self, request, obj): else: url = reverse( 'admin:filer-directory_listing-unfiled_images') - url = "{0}{1}".format( + url = "{}{}".format( url, admin_url_params_encoded(request), ) @@ -130,7 +130,7 @@ def delete_view(self, request, object_id, extra_context=None): kwargs={'folder_id': parent_folder.id}) else: url = reverse('admin:filer-directory_listing-unfiled_images') - url = "{0}{1}".format( + url = "{}{}".format( url, admin_url_params_encoded(request) ) @@ -153,7 +153,7 @@ def get_model_perms(self, request): def display_canonical(self, instance): canonical = instance.canonical_url if canonical: - return mark_safe('%s' % (canonical, canonical)) + return mark_safe('{}'.format(canonical, canonical)) else: return '-' display_canonical.allow_tags = True diff --git a/filer/admin/folderadmin.py b/filer/admin/folderadmin.py index 172104413..0399c2679 100644 --- a/filer/admin/folderadmin.py +++ b/filer/admin/folderadmin.py @@ -16,7 +16,7 @@ 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 +from django.urls import path, reverse from django.utils.encoding import force_str from django.utils.html import escape, format_html from django.utils.safestring import mark_safe @@ -131,7 +131,7 @@ def response_change(self, request, obj): kwargs={'folder_id': obj.parent.id}) else: url = reverse('admin:filer-directory_listing-root') - url = "{0}{1}".format( + url = "{}{}".format( url, admin_url_params_encoded(request), ) @@ -184,7 +184,7 @@ def delete_view(self, request, object_id, extra_context=None): kwargs={'folder_id': parent_folder.id}) else: url = reverse('admin:filer-directory_listing-root') - url = "{0}{1}".format( + url = "{}{}".format( url, admin_url_params_encoded(request), ) @@ -204,35 +204,35 @@ def get_urls(self): return [ # we override the default list view with our own directory listing # of the root directories - re_path(r'^$', - self.admin_site.admin_view(self.directory_listing), - name='filer-directory_listing-root'), - - re_path(r'^last/$', - self.admin_site.admin_view(self.directory_listing), - {'viewtype': 'last'}, - name='filer-directory_listing-last'), - - re_path(r'^(?P\d+)/list/$', - self.admin_site.admin_view(self.directory_listing), - name='filer-directory_listing'), - - re_path(r'^(?P\d+)/make_folder/$', - self.admin_site.admin_view(views.make_folder), - name='filer-directory_listing-make_folder'), - re_path(r'^make_folder/$', - self.admin_site.admin_view(views.make_folder), - name='filer-directory_listing-make_root_folder'), - - re_path(r'^images_with_missing_data/$', - self.admin_site.admin_view(self.directory_listing), - {'viewtype': 'images_with_missing_data'}, - name='filer-directory_listing-images_with_missing_data'), - - re_path(r'^unfiled_images/$', - self.admin_site.admin_view(self.directory_listing), - {'viewtype': 'unfiled_images'}, - name='filer-directory_listing-unfiled_images'), + path('', + self.admin_site.admin_view(self.directory_listing), + name='filer-directory_listing-root'), + + path('last/', + self.admin_site.admin_view(self.directory_listing), + {'viewtype': 'last'}, + name='filer-directory_listing-last'), + + path('/list/', + self.admin_site.admin_view(self.directory_listing), + name='filer-directory_listing'), + + path('/make_folder/', + self.admin_site.admin_view(views.make_folder), + name='filer-directory_listing-make_folder'), + path('make_folder/', + self.admin_site.admin_view(views.make_folder), + name='filer-directory_listing-make_root_folder'), + + path('images_with_missing_data/', + self.admin_site.admin_view(self.directory_listing), + {'viewtype': 'images_with_missing_data'}, + name='filer-directory_listing-images_with_missing_data'), + + path('unfiled_images/', + self.admin_site.admin_view(self.directory_listing), + {'viewtype': 'unfiled_images'}, + name='filer-directory_listing-unfiled_images'), ] + super().get_urls() # custom views @@ -252,10 +252,10 @@ def directory_listing(self, request, folder_id=None, viewtype=None): self.get_queryset(request).get(id=last_folder_id) except self.model.DoesNotExist: url = reverse('admin:filer-directory_listing-root') - url = "%s%s" % (url, admin_url_params_encoded(request)) + url = "{}{}".format(url, admin_url_params_encoded(request)) else: url = reverse('admin:filer-directory_listing', kwargs={'folder_id': last_folder_id}) - url = "%s%s" % (url, admin_url_params_encoded(request)) + url = "{}{}".format(url, admin_url_params_encoded(request)) return HttpResponseRedirect(url) elif folder_id is None: folder = FolderRoot() @@ -518,7 +518,7 @@ def owner_search_fields(self): def get_owner_filter_lookups(self): return [ - 'owner__{field}__icontains'.format(field=field) + f'owner__{field}__icontains' for field in self.owner_search_fields ] @@ -828,7 +828,7 @@ def _format_callback(self, obj, user, admin_site, perms_needed): else: # Don't display link to edit, because it either has no # admin or is edited inline. - return '%s: %s' % (capfirst(opts.verbose_name), force_str(obj)) + return '{}: {}'.format(capfirst(opts.verbose_name), force_str(obj)) def _check_copy_perms(self, request, files_queryset, folders_queryset): try: @@ -882,8 +882,7 @@ def _list_all_destination_folders_recursive(self, request, folders_queryset, cur # We do not allow copying/moving back to the folder itself enabled = (allow_self or fo != current_folder) and fo.has_add_children_permission(request) yield (fo, (mark_safe(("  " * level) + force_str(fo)), enabled)) - for c in self._list_all_destination_folders_recursive(request, folders_queryset, current_folder, fo.children.all(), allow_self, level + 1): - yield c + yield from self._list_all_destination_folders_recursive(request, folders_queryset, current_folder, fo.children.all(), allow_self, level + 1) def _list_all_destination_folders(self, request, folders_queryset, current_folder, allow_self): root_folders = self.get_queryset(request).filter(parent__isnull=True).order_by('name') @@ -1062,7 +1061,7 @@ def _get_available_name(self, destination, name): count = itertools.count(1) original = name while destination.contains_folder(name): - name = "%s_%s" % (original, next(count)) + name = "{}_{}".format(original, next(count)) return name def _copy_folder(self, folder, destination, suffix, overwrite): diff --git a/filer/admin/permissionadmin.py b/filer/admin/permissionadmin.py index d4f582115..eb42d66f6 100644 --- a/filer/admin/permissionadmin.py +++ b/filer/admin/permissionadmin.py @@ -19,7 +19,7 @@ class Media: css = {'all': ['filer/css/admin_folderpermissions.css']} def get_queryset(self, request): - qs = super(PermissionAdmin, self).get_queryset(request) + qs = super().get_queryset(request) return qs.prefetch_related("group", "folder") def get_model_perms(self, request): diff --git a/filer/admin/tools.py b/filer/admin/tools.py index f56fc2f61..ff12840f6 100644 --- a/filer/admin/tools.py +++ b/filer/admin/tools.py @@ -103,7 +103,7 @@ def admin_url_params_encoded(request, first_separator='?', params=None): ) if not params: return '' - return '{0}{1}'.format(first_separator, params) + return f'{first_separator}{params}' class AdminContext(dict): diff --git a/filer/admin/views.py b/filer/admin/views.py index af612fe31..a9731cdb0 100644 --- a/filer/admin/views.py +++ b/filer/admin/views.py @@ -90,7 +90,7 @@ def paste_clipboard_to_folder(request): if not redirect: redirect = request.POST.get('redirect_to', '') return HttpResponseRedirect( - '{0}?order_by=-modified_at{1}'.format( + '{}?order_by=-modified_at{}'.format( redirect, admin_url_params_encoded(request, first_separator='&'), ) @@ -107,7 +107,7 @@ def discard_clipboard(request): clipboard = Clipboard.objects.get(id=request.POST.get('clipboard_id')) tools.discard_clipboard(clipboard) return HttpResponseRedirect( - '{0}{1}'.format( + '{}{}'.format( request.POST.get('redirect_to', ''), admin_url_params_encoded(request, first_separator='&'), ) @@ -124,7 +124,7 @@ def delete_clipboard(request): clipboard = Clipboard.objects.get(id=request.POST.get('clipboard_id')) tools.delete_clipboard(clipboard) return HttpResponseRedirect( - '{0}{1}'.format( + '{}{}'.format( request.POST.get('redirect_to', ''), admin_url_params_encoded(request, first_separator='&'), ) diff --git a/filer/fields/file.py b/filer/fields/file.py index a5f588404..a07d28da8 100644 --- a/filer/fields/file.py +++ b/filer/fields/file.py @@ -59,7 +59,7 @@ def render(self, name, value, attrs=None, renderer=None): hidden_input = super(ForeignKeyRawIdWidget, self).render(name, value, attrs) # grandparent super context = { 'hidden_input': hidden_input, - 'lookup_url': '%s%s' % (related_url, lookup_url), + 'lookup_url': '{}{}'.format(related_url, lookup_url), 'change_url': change_url, 'object': obj, 'lookup_name': name, diff --git a/filer/fields/folder.py b/filer/fields/folder.py index ec8a04f00..ddc6f816d 100644 --- a/filer/fields/folder.py +++ b/filer/fields/folder.py @@ -53,7 +53,7 @@ def render(self, name, value, attrs=None, renderer=None): # API to determine the ID dynamically. context = { 'hidden_input': hidden_input, - 'lookup_url': '%s%s' % (related_url, url), + 'lookup_url': '{}{}'.format(related_url, url), 'lookup_name': name, 'span_id': css_id_description_txt, 'object': obj, @@ -115,7 +115,7 @@ def __init__(self, **kwargs): if "to" in kwargs.keys(): # pragma: no cover old_to = get_model_label(kwargs.pop("to")) if old_to.lower() != dfl.lower(): - msg = "%s can only be a ForeignKey to %s; %s passed" % ( + msg = "{} can only be a ForeignKey to {}; {} passed".format( self.__class__.__name__, dfl, old_to ) warnings.warn(msg, SyntaxWarning) diff --git a/filer/fields/multistorage_file.py b/filer/fields/multistorage_file.py index 6158087fc..853f5ef07 100644 --- a/filer/fields/multistorage_file.py +++ b/filer/fields/multistorage_file.py @@ -60,7 +60,7 @@ def __set__(self, instance, value): # To prevent recalculating upon reassignment of the same file, update only if value is # different than the previous one. if prev_assigned and value != previous_file: - callback_attr = '{}_data_changed'.format(self.field.name) + callback_attr = f'{self.field.name}_data_changed' if hasattr(instance, callback_attr): getattr(instance, callback_attr)() @@ -158,8 +158,8 @@ def value_to_string(self, obj): payload_file.seek(0) encoded_string = base64.b64encode(payload_file.read()).decode('utf-8') return value, encoded_string - except IOError: - warnings.warn('The payload for "%s" is missing. No such file on disk: %s!' % (obj.original_filename, self.storage.location)) + except OSError: + warnings.warn('The payload for "{}" is missing. No such file on disk: {}!'.format(obj.original_filename, self.storage.location)) return value def to_python(self, value): diff --git a/filer/models/abstract.py b/filer/models/abstract.py index f07c7fcc1..b0faabf89 100644 --- a/filer/models/abstract.py +++ b/filer/models/abstract.py @@ -184,12 +184,14 @@ def _generate_thumbnails(self, required_thumbnails): @property def icons(self): - required_thumbnails = dict( - (size, {'size': (int(size), int(size)), - 'crop': True, - 'upscale': True, - 'subject_location': self.subject_location}) - for size in filer_settings.FILER_ADMIN_ICON_SIZES) + required_thumbnails = { + size: { + 'size': (int(size), int(size)), + 'crop': True, + 'upscale': True, + 'subject_location': self.subject_location, + } + for size in filer_settings.FILER_ADMIN_ICON_SIZES} return self._generate_thumbnails(required_thumbnails) @property diff --git a/filer/models/clipboardmodels.py b/filer/models/clipboardmodels.py index 6fa0e900f..25daebaff 100644 --- a/filer/models/clipboardmodels.py +++ b/filer/models/clipboardmodels.py @@ -35,7 +35,7 @@ def append_file(self, file_obj): return True def __str__(self): - return "Clipboard %s of %s" % (self.id, self.user) + return "Clipboard {} of {}".format(self.id, self.user) class ClipboardItem(models.Model): diff --git a/filer/models/filemodels.py b/filer/models/filemodels.py index fd80f3e8c..86246e190 100644 --- a/filer/models/filemodels.py +++ b/filer/models/filemodels.py @@ -158,9 +158,9 @@ class Meta: def __str__(self): if self.name in ('', None): - text = "%s" % (self.original_filename,) + text = "{}".format(self.original_filename) else: - text = "%s" % (self.name,) + text = "{}".format(self.name) return text @classmethod @@ -302,7 +302,7 @@ def label(self): text = self.original_filename or 'unnamed file' else: text = self.name - text = "%s" % (text,) + text = "{}".format(text) return text def __lt__(self, other): @@ -336,7 +336,7 @@ def has_generic_permission(self, request, permission_type): def get_admin_change_url(self): return reverse( - 'admin:{0}_{1}_change'.format( + 'admin:{}_{}_change'.format( self._meta.app_label, self._meta.model_name, ), @@ -345,7 +345,7 @@ def get_admin_change_url(self): def get_admin_delete_url(self): return reverse( - 'admin:{0}_{1}_delete'.format(self._meta.app_label, self._meta.model_name), + f'admin:{self._meta.app_label}_{self._meta.model_name}_delete', args=(self.pk,)) @property diff --git a/filer/models/mixins.py b/filer/models/mixins.py index 52800e55c..40ccd8e27 100644 --- a/filer/models/mixins.py +++ b/filer/models/mixins.py @@ -18,7 +18,7 @@ def icons(self): if getattr(self, '_icon', False): for size in FILER_ADMIN_ICON_SIZES: try: - r[size] = static("filer/icons/%s_%sx%s.png" % ( + r[size] = static("filer/icons/{}_{}x{}.png".format( self._icon, size, size)) except ValueError: # Do not raise an exception while trying to call static() diff --git a/filer/models/thumbnailoptionmodels.py b/filer/models/thumbnailoptionmodels.py index 385054fd2..aee18c08e 100644 --- a/filer/models/thumbnailoptionmodels.py +++ b/filer/models/thumbnailoptionmodels.py @@ -38,7 +38,7 @@ class Meta: verbose_name_plural = _("thumbnail options") def __str__(self): - return '%s -- %s x %s' % (self.name, self.width, self.height) + return '{} -- {} x {}'.format(self.name, self.width, self.height) @property def as_dict(self): diff --git a/filer/templatetags/filer_admin_tags.py b/filer/templatetags/filer_admin_tags.py index f96cbd7aa..ceb3d0983 100644 --- a/filer/templatetags/filer_admin_tags.py +++ b/filer/templatetags/filer_admin_tags.py @@ -80,7 +80,7 @@ def filer_has_permission(context, item, action): permission to do the given action on the given item. """ - permission_method_name = 'has_{action}_permission'.format(action=action) + permission_method_name = f'has_{action}_permission' permission_method = getattr(item, permission_method_name, None) request = context.get('request') @@ -143,9 +143,9 @@ def file_icon_context(file, detail, width, height): thumbnail_options['size'] = 2 * width, 2 * height context['highres_url'] = thumbnailer.get_thumbnail(thumbnail_options).url elif mime_maintype in ['audio', 'font', 'video']: - icon_url = staticfiles_storage.url('filer/icons/file-{}.svg'.format(mime_maintype)) + icon_url = staticfiles_storage.url(f'filer/icons/file-{mime_maintype}.svg') elif mime_maintype == 'application' and mime_subtype in ['zip', 'pdf']: - icon_url = staticfiles_storage.url('filer/icons/file-{}.svg'.format(mime_subtype)) + icon_url = staticfiles_storage.url(f'filer/icons/file-{mime_subtype}.svg') else: icon_url = staticfiles_storage.url('filer/icons/file-unknown.svg') context.update(width=width, height=height, icon_url=icon_url) diff --git a/filer/templatetags/filer_tags.py b/filer/templatetags/filer_tags.py index 2d40c9402..3ceef60a6 100644 --- a/filer/templatetags/filer_tags.py +++ b/filer/templatetags/filer_tags.py @@ -84,12 +84,12 @@ def filesize(bytes, format='auto1024'): unit = filesize_long_formats.get(unit, '') if base == 1024 and unit: unit = '%sbi' % unit[:2] - unit = '%sbyte%s' % (unit, bytes != '1' and 's' or '') + unit = '{}byte{}'.format(unit, bytes != '1' and 's' or '') else: - unit = '%s%s' % (base == 1024 and unit.upper() or unit, + unit = '{}{}'.format(base == 1024 and unit.upper() or unit, base == 1024 and 'iB' or 'B') - return '%s %s' % (bytes, unit) + return '{} {}'.format(bytes, unit) if bytes == 0: return bytes diff --git a/filer/thumbnail_processors.py b/filer/thumbnail_processors.py index 2b7571217..68eac8dbb 100644 --- a/filer/thumbnail_processors.py +++ b/filer/thumbnail_processors.py @@ -54,8 +54,8 @@ def scale_and_crop_with_subject_location(im, size, subject_location=False, # --snip-- this is a copy and paste of the first few # lines of ``scale_and_crop`` - source_x, source_y = [float(v) for v in im.size] - target_x, target_y = [float(v) for v in size] + source_x, source_y = (float(v) for v in im.size) + target_x, target_y = (float(v) for v in size) if crop or not target_x or not target_y: scale = max(target_x / source_x, target_y / source_y) @@ -83,7 +83,7 @@ def scale_and_crop_with_subject_location(im, size, subject_location=False, # subject location aware cropping # =============================== # res_x, res_y: the resolution of the possibly already resized image - res_x, res_y = [float(v) for v in im.size] + res_x, res_y = (float(v) for v in im.size) # subj_x, subj_y: the position of the subject (maybe already re-scaled) subj_x = res_x * float(subject_location[0]) / source_x diff --git a/filer/utils/compatibility.py b/filer/utils/compatibility.py index 9a7cfbcc2..0c7fd5290 100644 --- a/filer/utils/compatibility.py +++ b/filer/utils/compatibility.py @@ -17,7 +17,7 @@ def truncate_words(s, num, end_text='...'): def get_delete_permission(opts): from django.contrib.auth import get_permission_codename - return '%s.%s' % (opts.app_label, get_permission_codename('delete', opts)) + return '{}.{}'.format(opts.app_label, get_permission_codename('delete', opts)) try: diff --git a/filer/utils/filer_easy_thumbnails.py b/filer/utils/filer_easy_thumbnails.py index 8bb5153bf..00b2f4b43 100644 --- a/filer/utils/filer_easy_thumbnails.py +++ b/filer/utils/filer_easy_thumbnails.py @@ -43,17 +43,17 @@ def get_thumbnail_name(self, thumbnail_options, transparent=False): thumbnail_options = thumbnail_options.copy() size = tuple(thumbnail_options.pop('size')) - initial_opts = ['{0}x{1}'.format(*size)] + initial_opts = ['{}x{}'.format(*size)] quality = thumbnail_options.pop('quality', self.thumbnail_quality) if extension == 'jpg': - initial_opts.append('q{}'.format(quality)) + initial_opts.append(f'q{quality}') elif extension == 'svg': thumbnail_options.pop('subsampling', None) thumbnail_options.pop('upscale', None) opts = list(thumbnail_options.items()) opts.sort() # Sort the options so the file name is consistent. - opts = ['{}'.format(v is not True and '{}-{}'.format(k, v) or k) + opts = ['{}'.format(v is not True and f'{k}-{v}' or k) for k, v in opts if v] all_opts = '_'.join(initial_opts + opts) @@ -62,7 +62,7 @@ def get_thumbnail_name(self, thumbnail_options, transparent=False): # make sure our magic delimiter is not used in all_opts all_opts = all_opts.replace('__', '_') - filename = '{}__{}.{}'.format(source_filename, all_opts, extension) + filename = f'{source_filename}__{all_opts}.{extension}' return os.path.join(basedir, path, subdir, filename) diff --git a/filer/utils/files.py b/filer/utils/files.py index 11d4b9272..c9732843d 100644 --- a/filer/utils/files.py +++ b/filer/utils/files.py @@ -20,7 +20,7 @@ def handle_upload(request): filename = request.GET.get('qqfile', False) or request.GET.get('filename', False) or '' try: - content_length = int(request.META['CONTENT_LENGTH']) + content_length = int(request.headers['content-length']) except (IndexError, TypeError, ValueError): content_length = None @@ -131,6 +131,6 @@ def get_valid_filename(s): filename = slugify(filename) ext = slugify(ext) if ext: - return "%s.%s" % (filename, ext) + return "{}.{}".format(filename, ext) else: - return "%s" % (filename,) + return "{}".format(filename) diff --git a/filer/utils/model_label.py b/filer/utils/model_label.py index 23d7fd2eb..93cc66809 100644 --- a/filer/utils/model_label.py +++ b/filer/utils/model_label.py @@ -1,4 +1,3 @@ - def get_model_label(model): """ Take a model class or model label and return its model label. @@ -12,7 +11,7 @@ def get_model_label(model): if isinstance(model, str): return model else: - return "%s.%s" % ( + return "{}.{}".format( model._meta.app_label, model.__name__ ) diff --git a/tests/test_migrations.py b/tests/test_migrations.py index 29c4a94ba..7a65b508e 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -19,7 +19,7 @@ def test_for_missing_migrations(self): } try: - call_command('makemigrations', **options) + call_command('makemigrations', 'filer', **options) except SystemExit as e: status_code = str(e) else: diff --git a/tox.ini b/tox.ini index a84927fcb..1d7d7e714 100644 --- a/tox.ini +++ b/tox.ini @@ -4,19 +4,11 @@ envlist = isort docs frontend - py{36,37,38}-dj{22}-swap - py{36,37,38}-dj{22}-noswap - py{36,37,38,39}-dj{30,31}-swap - py{36,37,38,39}-dj{30,31}-noswap - py{36,37,38,39,310}-dj32-swap - py{36,37,38,39,310}-dj32-noswap - py{38,39,310,311}-dj42-swap - py{38,39,310,311}-dj42-noswap + py{38,39,310}-dj32-{swap,noswap} + py{38,39,310,311}-{dj40,dj41,dj42}-{swap,noswap} [gh-actions] python = - 3.6: py36 - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 @@ -26,9 +18,6 @@ skip_missing_interpreters=True [testenv] deps = - dj22: -r tests/requirements/django-2.2.txt - dj30: -r tests/requirements/django-3.0.txt - dj31: -r tests/requirements/django-3.1.txt dj32: -r tests/requirements/django-3.2.txt dj40: -r tests/requirements/django-4.0.txt dj41: -r tests/requirements/django-4.1.txt @@ -54,13 +43,20 @@ skip_install = true changedir = docs deps = sphinx + sphinx-autobuild + sphinxcontrib-spelling + sphinx-copybutton + sphinxext-opengraph + sphinxcontrib-images + sphinxcontrib-inlinesyntaxhighlight + furo commands = - sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html + sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html # sphinx-build -W -b html -d build/doctrees . build/html skip_install = true [testenv:frontend] -whitelist_externals = +allowlist_externals = nvm npm gulp