Skip to content

Commit 0a534de

Browse files
authored
Merge branch 'django-cms:master' into master
2 parents 9ed4a06 + b0f39ab commit 0a534de

File tree

96 files changed

+4403
-2924
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+4403
-2924
lines changed

.github/workflows/publish-to-live-pypi.yml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ jobs:
99
build-n-publish:
1010
name: Build and publish Python 🐍 distributions 📦 to pypi
1111
runs-on: ubuntu-latest
12+
environment:
13+
name: pypi
14+
url: https://pypi.org/p/django-filer
15+
permissions:
16+
id-token: write
1217
steps:
13-
- uses: actions/checkout@master
14-
- name: Set up Python 3.9
15-
uses: actions/setup-python@v1
18+
- uses: actions/checkout@v4
19+
- name: Set up Python
20+
uses: actions/setup-python@v4
1621
with:
17-
python-version: 3.9
22+
python-version: '3.12'
1823

1924
- name: Install pypa/build
2025
run: >-
@@ -33,7 +38,4 @@ jobs:
3338
3439
- name: Publish distribution 📦 to PyPI
3540
if: startsWith(github.ref, 'refs/tags')
36-
uses: pypa/gh-action-pypi-publish@master
37-
with:
38-
user: __token__
39-
password: ${{ secrets.PYPI_API_TOKEN }}
41+
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/publish-to-test-pypi.yml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ jobs:
99
build-n-publish:
1010
name: Build and publish Python 🐍 distributions 📦 to TestPyPI
1111
runs-on: ubuntu-latest
12+
environment:
13+
name: test
14+
url: https://test.pypi.org/p/django-filer
15+
permissions:
16+
id-token: write
1217
steps:
13-
- uses: actions/checkout@master
14-
- name: Set up Python 3.9
15-
uses: actions/setup-python@v1
18+
- uses: actions/checkout@v4
19+
- name: Set up Python
20+
uses: actions/setup-python@v4
1621
with:
17-
python-version: 3.9
22+
python-version: '3.12'
1823

1924
- name: Install pypa/build
2025
run: >-
@@ -32,9 +37,7 @@ jobs:
3237
.
3338
3439
- name: Publish distribution 📦 to Test PyPI
35-
uses: pypa/gh-action-pypi-publish@master
40+
uses: pypa/gh-action-pypi-publish@release/v1
3641
with:
37-
user: __token__
38-
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
39-
repository_url: https://test.pypi.org/legacy/
40-
skip_existing: true
42+
repository-url: https://test.pypi.org/legacy/
43+
skip-existing: true

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ repos:
3131
- id: yesqa
3232

3333
- repo: https://github.com/pre-commit/pre-commit-hooks
34-
rev: v4.4.0
34+
rev: v4.5.0
3535
hooks:
3636
- id: check-merge-conflict
3737
- id: mixed-line-ending

CHANGELOG.rst

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,31 @@
22
CHANGELOG
33
=========
44

5-
unreleased
6-
==========
5+
3.1.1 (2023-11-18)
6+
==================
7+
8+
* fix: Added compatibility code in aldryn_config go support setting THUMBNAIL_DEFAULT_STORAGE in django 4.2
9+
* fix: address failing gulp ci jobs
10+
* feat: Image dimensions update management command
11+
* ci: pre-commit autoupdate
12+
13+
3.1.0 (2023-10-01)
14+
==================
715

16+
* feat: limit uploaded image area (width x height) to prevent decompression
17+
bombs
18+
* feat: Canonical URL action button now copies canonical URL to the user's
19+
clipboard
20+
* fix: Run validators on updated files in file change view
21+
* fix: Update mime type if uploading file in file change view
22+
* fix: Do not allow to remove the file field from an uplaoded file in
23+
the admin interface
24+
* fix: refactor upload checks into running validators in the admin
25+
and adding clean methods for file and (abstract) image models.
826
* Fixed two more instances of javascript int overflow issue (#1335)
927
* fix: ensure uniqueness of icon admin url names
28+
* fix: Crash with django-storage if filer file does not have a
29+
storage file attached
1030

1131
3.0.6 (2023-09-08)
1232
==================

aldryn_config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ def to_settings(self, data, settings):
4747
# If the DEFAULT_FILE_STORAGE has been set to a value known by
4848
# aldryn-django, then use that as THUMBNAIL_DEFAULT_STORAGE as well.
4949
for storage_backend in storage.SCHEMES.values():
50-
if storage_backend == settings['DEFAULT_FILE_STORAGE']:
50+
# Process before django 4.2
51+
if storage_backend == settings.get('DEFAULT_FILE_STORAGE', None):
52+
settings['THUMBNAIL_DEFAULT_STORAGE'] = storage_backend
53+
break
54+
# Process django 4.2 and after
55+
if storage_backend == settings.get('STORAGES', {}).get('default', {}).get('BACKEND', None):
5156
settings['THUMBNAIL_DEFAULT_STORAGE'] = storage_backend
5257
break
5358
return settings

docs/settings.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,19 @@ Limits the maximal file size if set. Takes an integer (file size in MB).
178178

179179
Defaults to ``None``.
180180

181+
``FILER_MAX_IMAGE_PIXELS``
182+
--------------------------------
183+
184+
Limits the maximal pixel size of the image that can be uploaded to the Filer.
185+
It will also be lower than or equals to the MAX_IMAGE_PIXELS that Pillow's PIL allows.
186+
187+
188+
``MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3)``
189+
190+
Defaults to ``MAX_IMAGE_PIXELS``. But when set, should always be lower than the MAX_IMAGE_PIXELS limit set by Pillow.
191+
192+
This is useful setting to prevent decompression bomb DOS attack.
193+
181194

182195
``FILER_ADD_FILE_VALIDATORS``
183196
-----------------------------

filer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
8. Publish the release and it will automatically release to pypi
1414
"""
1515

16-
__version__ = '3.0.6'
16+
__version__ = '3.1.1'

filer/admin/clipboardadmin.py

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.contrib import admin, messages
2+
from django.core.exceptions import ValidationError
23
from django.forms.models import modelform_factory
34
from django.http import JsonResponse
45
from django.urls import path
@@ -9,7 +10,7 @@
910
from ..models import Clipboard, ClipboardItem, Folder
1011
from ..utils.files import handle_request_files_upload, handle_upload
1112
from ..utils.loader import load_model
12-
from ..validation import FileValidationError, validate_upload
13+
from ..validation import validate_upload
1314
from . import views
1415

1516

@@ -112,48 +113,53 @@ def ajax_upload(request, folder_id=None):
112113
break
113114
uploadform = FileForm({'original_filename': filename, 'owner': request.user.pk},
114115
{'file': upload})
116+
uploadform.request = request
115117
uploadform.instance.mime_type = mime_type
116118
if uploadform.is_valid():
117119
try:
118120
validate_upload(filename, upload, request.user, mime_type)
119-
except FileValidationError as error:
120-
from django.contrib.messages import ERROR, add_message
121-
message = str(error)
122-
add_message(request, ERROR, message)
123-
return JsonResponse({'error': message})
124-
file_obj = uploadform.save(commit=False)
125-
# Enforce the FILER_IS_PUBLIC_DEFAULT
126-
file_obj.is_public = filer_settings.FILER_IS_PUBLIC_DEFAULT
121+
file_obj = uploadform.save(commit=False)
122+
# Enforce the FILER_IS_PUBLIC_DEFAULT
123+
file_obj.is_public = filer_settings.FILER_IS_PUBLIC_DEFAULT
124+
except ValidationError as error:
125+
messages.error(request, str(error))
126+
return JsonResponse({'error': str(error)})
127127
file_obj.folder = folder
128128
file_obj.save()
129129
# TODO: Deprecated/refactor
130130
# clipboard_item = ClipboardItem(
131131
# clipboard=clipboard, file=file_obj)
132132
# clipboard_item.save()
133133

134-
thumbnail = None
135-
data = {
136-
'thumbnail': thumbnail,
137-
'alt_text': '',
138-
'label': str(file_obj),
139-
'file_id': file_obj.pk,
140-
}
141-
# prepare preview thumbnail
142-
if isinstance(file_obj, Image):
143-
thumbnail_180_options = {
144-
'size': (180, 180),
145-
'crop': True,
146-
'upscale': True,
134+
try:
135+
thumbnail = None
136+
data = {
137+
'thumbnail': thumbnail,
138+
'alt_text': '',
139+
'label': str(file_obj),
140+
'file_id': file_obj.pk,
147141
}
148-
thumbnail_180 = file_obj.file.get_thumbnail(
149-
thumbnail_180_options)
150-
data['thumbnail_180'] = thumbnail_180.url
151-
data['original_image'] = file_obj.url
152-
return JsonResponse(data)
142+
# prepare preview thumbnail
143+
if isinstance(file_obj, Image):
144+
thumbnail_180_options = {
145+
'size': (180, 180),
146+
'crop': True,
147+
'upscale': True,
148+
}
149+
thumbnail_180 = file_obj.file.get_thumbnail(
150+
thumbnail_180_options)
151+
data['thumbnail_180'] = thumbnail_180.url
152+
data['original_image'] = file_obj.url
153+
return JsonResponse(data)
154+
except Exception as error:
155+
messages.error(request, str(error))
156+
return JsonResponse({"error": str(error)})
153157
else:
154-
form_errors = '; '.join(['{}: {}'.format(
155-
field,
156-
', '.join(errors)) for field, errors in list(
157-
uploadform.errors.items())
158+
for key, error_list in uploadform.errors.items():
159+
for error in error_list:
160+
messages.error(request, error)
161+
162+
form_errors = '; '.join(['{}'.format(
163+
', '.join(errors)) for errors in list(uploadform.errors.values())
158164
])
159-
return JsonResponse({'message': str(form_errors)}, status=422)
165+
return JsonResponse({'error': str(form_errors)}, status=200)

filer/admin/fileadmin.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import mimetypes
2+
13
from django import forms
24
from django.contrib.admin.utils import unquote
35
from django.contrib.staticfiles.storage import staticfiles_storage
@@ -8,6 +10,7 @@
810
from django.utils.timezone import now
911
from django.utils.translation import gettext as _
1012

13+
from easy_thumbnails.engine import NoSourceGenerator
1114
from easy_thumbnails.exceptions import InvalidImageFormatError
1215
from easy_thumbnails.files import get_thumbnailer
1316
from easy_thumbnails.models import Thumbnail as EasyThumbnail
@@ -25,6 +28,27 @@ class Meta:
2528
model = File
2629
exclude = ()
2730

31+
def __init__(self, *args, **kwargs):
32+
super().__init__(*args, **kwargs)
33+
self.fields["file"].widget = forms.FileInput()
34+
35+
def clean(self):
36+
from ..validation import validate_upload
37+
cleaned_data = super().clean()
38+
if "file" in self.changed_data and cleaned_data["file"]:
39+
mime_type = mimetypes.guess_type(cleaned_data["file"].name)[0] or 'application/octet-stream'
40+
file = cleaned_data["file"]
41+
file.open("w+") # Allow for sanitizing upload
42+
file.seek(0)
43+
validate_upload(
44+
file_name=cleaned_data["file"].name,
45+
file=file.file,
46+
owner=cleaned_data["owner"],
47+
mime_type=mime_type,
48+
)
49+
file.open("r")
50+
return self.cleaned_data
51+
2852

2953
class FileAdmin(PrimitivePermissionAwareModelAdmin):
3054
list_display = ('label',)
@@ -185,7 +209,7 @@ def icon_view(self, request, file_id: int, size: int) -> HttpResponse:
185209
# Touch thumbnail to allow it to be prefetched for directory listing
186210
EasyThumbnail.objects.filter(name=thumbnail.name).update(modified=now())
187211
return HttpResponseRedirect(thumbnail.url)
188-
except InvalidImageFormatError:
212+
except (InvalidImageFormatError, NoSourceGenerator):
189213
return HttpResponseRedirect(staticfiles_storage.url('filer/icons/file-missing.svg'))
190214

191215

filer/admin/imageadmin.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
from django import forms
2+
from django.shortcuts import get_object_or_404, render
3+
from django.urls import path
24
from django.utils.translation import gettext as _
35
from django.utils.translation import gettext_lazy
46

57
from ..settings import FILER_IMAGE_MODEL
68
from ..thumbnail_processors import normalize_subject_location
79
from ..utils.compatibility import string_concat
810
from ..utils.loader import load_model
9-
from .fileadmin import FileAdmin
11+
from .fileadmin import FileAdmin, FileAdminChangeFrom
1012

1113

1214
Image = load_model(FILER_IMAGE_MODEL)
1315

1416

15-
class ImageAdminForm(forms.ModelForm):
17+
class ImageAdminForm(FileAdminChangeFrom):
1618
subject_location = forms.CharField(
1719
max_length=64, required=False,
1820
label=_('Subject location'),
@@ -58,8 +60,8 @@ def clean_subject_location(self):
5860
err_code = 'invalid_subject_format'
5961

6062
elif (
61-
coordinates[0] > self.instance.width
62-
or coordinates[1] > self.instance.height
63+
coordinates[0] > self.instance.width > 0
64+
or coordinates[1] > self.instance.height > 0
6365
):
6466
err_msg = gettext_lazy(
6567
'Subject location is outside of the image. ')
@@ -80,19 +82,24 @@ class Meta:
8082
model = Image
8183
exclude = ()
8284

83-
class Media:
84-
css = {
85-
# 'all': (settings.MEDIA_URL + 'filer/css/focal_point.css',)
86-
}
87-
js = (
88-
89-
)
90-
9185

9286
class ImageAdmin(FileAdmin):
9387
change_form_template = 'admin/filer/image/change_form.html'
9488
form = ImageAdminForm
9589

90+
def get_urls(self):
91+
return super().get_urls() + [
92+
path("expand/<int:file_id>",
93+
self.admin_site.admin_view(self.expand_view),
94+
name=f"filer_{self.model._meta.model_name}_expand_view")
95+
]
96+
97+
def expand_view(self, request, file_id):
98+
image = get_object_or_404(self.model, pk=file_id)
99+
return render(request, "admin/filer/image/expand.html", context={
100+
"original_url": image.url
101+
})
102+
96103

97104
if FILER_IMAGE_MODEL == 'filer.Image':
98105
extra_main_fields = ('author', 'default_alt_text', 'default_caption',)

0 commit comments

Comments
 (0)