diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 022670216..30cb3b650 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,13 +15,16 @@ jobs: 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/CHANGELOG.rst b/CHANGELOG.rst index 4f3da7048..9d64545b8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ CHANGELOG ========= +Unreleased +========== +* Add Django 4.2 support +* fix tests + 2.2.6 (2023-07-03) ================== diff --git a/README.rst b/README.rst index 8e22ed370..7b495328d 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,9 @@ Django Filer **django Filer** is a file management application for django that makes handling of files and images a breeze. +.. warning:: + django-filer 2.x is susceptible to SVG XSS attacks and we strongly recommend upgrading it to 3.x. + .. note:: This project is endorsed by the `django CMS Association `_. diff --git a/aldryn_config.py b/aldryn_config.py index 145fe1ded..3572d4ec2 100644 --- a/aldryn_config.py +++ b/aldryn_config.py @@ -47,7 +47,12 @@ def to_settings(self, data, settings): # If the DEFAULT_FILE_STORAGE has been set to a value known by # aldryn-django, then use that as THUMBNAIL_DEFAULT_STORAGE as well. for storage_backend in storage.SCHEMES.values(): - if storage_backend == settings['DEFAULT_FILE_STORAGE']: + # Process before django 4.2 + if storage_backend == settings.get('DEFAULT_FILE_STORAGE', None): + settings['THUMBNAIL_DEFAULT_STORAGE'] = storage_backend + break + # Process django 4.2 and after + if storage_backend == settings.get('STORAGES', {}).get('default', {}).get('BACKEND', None): settings['THUMBNAIL_DEFAULT_STORAGE'] = storage_backend break return settings diff --git a/filer/__init__.py b/filer/__init__.py index 0401c529e..ed363ac3b 100644 --- a/filer/__init__.py +++ b/filer/__init__.py @@ -14,5 +14,3 @@ """ __version__ = '2.2.6' - -default_app_config = 'filer.apps.FilerConfig' diff --git a/filer/admin/clipboardadmin.py b/filer/admin/clipboardadmin.py index 3a727a4a5..9c2d23bb3 100644 --- a/filer/admin/clipboardadmin.py +++ b/filer/admin/clipboardadmin.py @@ -1,7 +1,7 @@ from django.contrib import admin 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.views.decorators.csrf import csrf_exempt from .. import settings as filer_settings @@ -35,21 +35,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): @@ -123,7 +123,7 @@ def ajax_upload(request, folder_id=None): 'file_id': file_obj.pk, } # prepare preview thumbnail - if type(file_obj) == Image: + if isinstance(file_obj, Image): thumbnail_180_options = { 'size': (180, 180), 'crop': True, diff --git a/filer/admin/folderadmin.py b/filer/admin/folderadmin.py index 5e93cc62b..08dfba06a 100644 --- a/filer/admin/folderadmin.py +++ b/filer/admin/folderadmin.py @@ -15,7 +15,7 @@ from django.db import models, router 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 @@ -200,35 +200,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 diff --git a/filer/utils/files.py b/filer/utils/files.py index 8bbffbf3d..fe360d7f1 100644 --- a/filer/utils/files.py +++ b/filer/utils/files.py @@ -22,7 +22,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 diff --git a/setup.py b/setup.py index 2601510d8..0fee49cd2 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ 'Framework :: Django :: 3.2', 'Framework :: Django :: 4.0', 'Framework :: Django :: 4.1', + 'Framework :: Django :: 4.2', 'Framework :: Django CMS', 'Framework :: Django CMS :: 3.6', 'Framework :: Django CMS :: 3.7', diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt index 9a0be2f19..2ff68a7dc 100644 --- a/tests/requirements/base.txt +++ b/tests/requirements/base.txt @@ -6,3 +6,4 @@ django-mptt coverage isort flake8 +packaging \ No newline at end of file diff --git a/tests/requirements/django-4.2.txt b/tests/requirements/django-4.2.txt new file mode 100644 index 000000000..192e81265 --- /dev/null +++ b/tests/requirements/django-4.2.txt @@ -0,0 +1,8 @@ +-r base.txt + +django-app-helper>=3.3.1 + +django>=4.2,<5 +django_polymorphic>=3.1 +django-app-helper +#https://github.com/jrief/django-app-helper/archive/refs/heads/develop.zip \ No newline at end of file diff --git a/tests/utils/custom_image/migrations/0004_alter_image_file_ptr.py b/tests/utils/custom_image/migrations/0004_alter_image_file_ptr.py new file mode 100644 index 000000000..eca5e335a --- /dev/null +++ b/tests/utils/custom_image/migrations/0004_alter_image_file_ptr.py @@ -0,0 +1,20 @@ +# Generated by Django 4.1.9 on 2023-06-13 20:22 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('filer', '0015_alter_file_owner_alter_file_polymorphic_ctype_and_more'), + ('custom_image', '0003_auto_20180414_2059'), + ] + + operations = [ + migrations.AlterField( + model_name='image', + name='file_ptr', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='%(app_label)s_%(class)s_file', serialize=False, to='filer.file'), + ), + ] diff --git a/tests/utils/test_app/admin.py b/tests/utils/test_app/admin.py index f1c73ed51..2b6b6b44d 100644 --- a/tests/utils/test_app/admin.py +++ b/tests/utils/test_app/admin.py @@ -3,8 +3,6 @@ from .models import MyModel +@admin.register(MyModel) class MyModelAdmin(admin.ModelAdmin): model = MyModel - - -admin.site.register(MyModel, MyModelAdmin) diff --git a/tests/utils/urls.py b/tests/utils/urls.py index 882eca68d..888e0b671 100644 --- a/tests/utils/urls.py +++ b/tests/utils/urls.py @@ -1,6 +1,6 @@ from django.conf import settings from django.contrib import admin -from django.urls import include, re_path +from django.urls import include, path, re_path from django.views.static import serve @@ -11,6 +11,6 @@ re_path(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), re_path(r'^admin/', admin_urls), - re_path(r'^', include('filer.server.urls')), - re_path(r'^filer/', include('filer.urls')), + path('', include('filer.server.urls')), + path('filer/', include('filer.urls')), ] diff --git a/tox.ini b/tox.ini index 27f4bc2ff..1d868b739 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ envlist = 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}-dj{40,41,42}-{swap,noswap} [gh-actions] python = @@ -18,20 +19,26 @@ python = 3.8: py38 3.9: py39 3.10: py310 + 3.11: py311 skip_missing_interpreters=True [testenv] deps = - dj22: -r tests/requirements/django-2.2.txt +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 + dj42: -r tests/requirements/django-4.2.txt commands = {envpython} --version {env:COMMAND:coverage} erase {env:COMMAND:coverage} run setup.py test {env:COMMAND:coverage} report +allowlist_externals = + {env:COMMAND:coverage} setenv = swap: CUSTOM_IMAGE=custom_image.Image