Skip to content

Commit

Permalink
Merge remote-tracking branch 'dCA/version-3.2' into poc-finder
Browse files Browse the repository at this point in the history
  • Loading branch information
jrief committed May 27, 2024
2 parents cc734ed + fc40171 commit eb6cb64
Show file tree
Hide file tree
Showing 38 changed files with 209 additions and 112 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/publish-to-live-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'

Expand All @@ -39,5 +39,5 @@ jobs:
- name: Publish distribution 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
repository_url: https://test.pypi.org/legacy/
skip_existing: true
16 changes: 6 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,13 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11' ]
python-version: ['3.10', '3.11', '3.12']
requirements-file: [
django-3.2.txt,
django-4.0.txt,
django-4.1.txt,
django-4.2.txt,
django-5.0.txt,
django-main.txt,
]
exclude:
- requirements-file: django-5.0.txt
python-version: 3.8
- requirements-file: django-5.0.txt
python-version: 3.9
custom-image-model: [false, true]
os: [
ubuntu-20.04,
]
Expand All @@ -41,8 +35,10 @@ jobs:
python -m pip install --upgrade pip
pip install -r tests/requirements/${{ matrix.requirements-file }}
python setup.py install
- name: Enable the custom image model
run: echo "CUSTOM_IMAGE=custom_image.Image" >> $GITHUB_ENV
if: ${{ matrix.custom-image-model }}
- name: Run coverage
run: coverage run setup.py test

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v1
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repos:
# args: [--target-version, "2.2"]

- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
rev: 7.0.0
hooks:
- id: flake8

Expand All @@ -31,7 +31,7 @@ repos:
- id: yesqa

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-merge-conflict
- id: mixed-line-ending
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@
CHANGELOG
=========

3.2 (2025-05-21)
================
* Fix: Reverse match for 'filer_image_expand_view' not found.
* Drop support for Python 3.8 and 3.9.
* Add support for Python 3.12.


3.1.3 (2025-05-17)
==================
* Fix: Folder select widget did not render correctly with standard Django admin
styles.

3.1.2 (2025-05-17)
==================

* Made the filer check command compatible with custom image models.
* Use the final image model's app label to determine the expand view URL.
* Prepare the image expand URL in the admin code and pass it through to the template via context.
* Fix #1377: Field `verbose_name` should use `gettext_lazy`.
* Fix: styles for django 4.2+ admin.
* Fix: Unintended scroll when clearing file widget.
* Fix: Method `ImageAdminForm.clean_subject_location()` not implemented correctly causing invokation
of `clean()` twice.


3.1.1 (2023-11-18)
==================

Expand Down
4 changes: 3 additions & 1 deletion docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ e.g::

Defaults to FileSystemStorage in ``<MEDIA_ROOT>/filer_public/`` and ``<MEDIA_ROOT>/filer_public_thumbnails/`` for public files and
``<MEDIA_ROOT>/../smedia/filer_private/`` and ``<MEDIA_ROOT>/../smedia/filer_private_thumbnails/`` for private files.
Public storage uses ``DEFAULT_FILE_STORAGE`` as default storage backend.
Public storage uses the default storage's backend. This is taken from Django's ``STORAGES``
setting if it exists or, if not, from the ``DEFAULT_FILE_STORAGE`` setting for compatibility
with earlier Django versions (5.0 or below).

``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
Expand Down
2 changes: 1 addition & 1 deletion filer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
8. Publish the release and it will automatically release to pypi
"""

__version__ = '3.1.1'
__version__ = '3.2'
27 changes: 19 additions & 8 deletions filer/admin/fileadmin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mimetypes

from django import forms
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.contrib.admin.utils import unquote
from django.contrib.staticfiles.storage import staticfiles_storage
from django.http import Http404, HttpResponse, HttpResponseRedirect
Expand All @@ -19,10 +20,14 @@
from .. import settings
from ..models import BaseImage, File
from ..settings import DEFERRED_THUMBNAIL_SIZES
from ..utils.loader import load_model
from .permissions import PrimitivePermissionAwareModelAdmin
from .tools import AdminContext, admin_url_params_encoded, popup_status


Image = load_model(settings.FILER_IMAGE_MODEL)


class FileAdminChangeFrom(forms.ModelForm):
class Meta:
model = File
Expand All @@ -43,7 +48,7 @@ def clean(self):
validate_upload(
file_name=cleaned_data["file"].name,
file=file.file,
owner=cleaned_data["owner"],
owner=cleaned_data.get("owner"),
mime_type=mime_type,
)
file.open("r")
Expand Down Expand Up @@ -123,12 +128,18 @@ def response_change(self, request, obj):

def render_change_form(self, request, context, add=False, change=False,
form_url='', obj=None):
info = self.model._meta.app_label, self.model._meta.model_name
extra_context = {'show_delete': True,
'history_url': 'admin:%s_%s_history' % info,
'is_popup': popup_status(request),
'filer_admin_context': AdminContext(request)}
context.update(extra_context)
context.update({
'show_delete': True,
'history_url': admin_urlname(self.opts, 'history'),
'expand_image_url': None,
'is_popup': popup_status(request),
'filer_admin_context': AdminContext(request),
})
if obj and obj.mime_maintype == 'image' and obj.file.exists():
if 'svg' in obj.mime_type:
context['expand_image_url'] = reverse(admin_urlname(Image._meta, 'expand'), args=(obj.pk,))
else:
context['expand_image_url'] = obj.file.url
return super().render_change_form(
request=request, context=context, add=add, change=change,
form_url=form_url, obj=obj)
Expand Down Expand Up @@ -181,7 +192,7 @@ def get_model_perms(self, request):
def display_canonical(self, instance):
canonical = instance.canonical_url
if canonical:
return mark_safe('<a href="{}">{}</a>'.format(canonical, canonical))
return mark_safe(f'<a href="{canonical}">{canonical}</a>')
else:
return '-'
display_canonical.allow_tags = True
Expand Down
23 changes: 12 additions & 11 deletions filer/admin/folderadmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
from django.db.models import Case, F, OuterRef, Subquery, When
from django.db.models.functions import Coalesce, Lower
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.encoding import force_str
from django.utils.html import escape, format_html
Expand Down Expand Up @@ -253,10 +254,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 = "{}{}".format(url, admin_url_params_encoded(request))
url = f"{url}{admin_url_params_encoded(request)}"
else:
url = reverse('admin:filer-directory_listing', kwargs={'folder_id': last_folder_id})
url = "{}{}".format(url, admin_url_params_encoded(request))
url = f"{url}{admin_url_params_encoded(request)}"
return HttpResponseRedirect(url)
elif folder_id is None:
folder = FolderRoot()
Expand Down Expand Up @@ -480,7 +481,7 @@ def directory_listing(self, request, folder_id=None, viewtype=None):
'enable_permissions': settings.FILER_ENABLE_PERMISSIONS,
'can_make_folder': request.user.is_superuser or (folder.is_root and settings.FILER_ALLOW_REGULAR_USERS_TO_ADD_ROOT_FOLDERS) or permissions.get("has_add_children_permission"),
})
return render(request, self.directory_listing_template, context)
return TemplateResponse(request, self.directory_listing_template, context)

def filter_folder(self, qs, terms=()):
# Source: https://github.com/django/django/blob/1.7.1/django/contrib/admin/options.py#L939-L947 flake8: noqa
Expand Down Expand Up @@ -814,7 +815,7 @@ def delete_files_or_folders(self, request, files_queryset, folders_queryset):
})

# Display the destination folder selection page
return render(
return TemplateResponse(
request,
"admin/filer/delete_selected_files_confirmation.html",
context
Expand All @@ -840,7 +841,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 '{}: {}'.format(capfirst(opts.verbose_name), force_str(obj))
return f'{capfirst(opts.verbose_name)}: {force_str(obj)}'

def _check_copy_perms(self, request, files_queryset, folders_queryset):
try:
Expand Down Expand Up @@ -954,7 +955,7 @@ def move_files_and_folders(self, request, files_queryset, folders_queryset):
})

# Display the destination folder selection page
return render(request, "admin/filer/folder/choose_move_destination.html", context)
return TemplateResponse(request, "admin/filer/folder/choose_move_destination.html", context)

move_files_and_folders.short_description = _("Move selected files and/or folders")

Expand Down Expand Up @@ -1037,7 +1038,7 @@ def rename_files(self, request, files_queryset, folders_queryset):
})

# Display the rename format selection page
return render(request, "admin/filer/folder/choose_rename_format.html", context)
return TemplateResponse(request, "admin/filer/folder/choose_rename_format.html", context)

rename_files.short_description = _("Rename files")

Expand Down Expand Up @@ -1073,7 +1074,7 @@ def _get_available_name(self, destination, name):
count = itertools.count(1)
original = name
while destination.contains_folder(name):
name = "{}_{}".format(original, next(count))
name = f"{original}_{next(count)}"
return name

def _copy_folder(self, folder, destination, suffix, overwrite):
Expand Down Expand Up @@ -1169,7 +1170,7 @@ def copy_files_and_folders(self, request, files_queryset, folders_queryset):
})

# Display the destination folder selection page
return render(request, "admin/filer/folder/choose_copy_destination.html", context)
return TemplateResponse(request, "admin/filer/folder/choose_copy_destination.html", context)

copy_files_and_folders.short_description = _("Copy selected files and/or folders")

Expand Down Expand Up @@ -1298,6 +1299,6 @@ def resize_images(self, request, files_queryset, folders_queryset):
})

# Display the resize options page
return render(request, "admin/filer/folder/choose_images_resize_options.html", context)
return TemplateResponse(request, "admin/filer/folder/choose_images_resize_options.html", context)

resize_images.short_description = _("Resize selected images")
20 changes: 12 additions & 8 deletions filer/admin/imageadmin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import forms
from django.shortcuts import get_object_or_404, render
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.urls import path
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
Expand Down Expand Up @@ -46,8 +47,7 @@ def clean_subject_location(self):
for subject location widget to receive valid coordinates on field
validation errors.
"""
cleaned_data = super().clean()
subject_location = cleaned_data['subject_location']
subject_location = self.cleaned_data['subject_location']
if not subject_location:
# if supplied subject location is empty, do not check it
return subject_location
Expand All @@ -69,7 +69,7 @@ def clean_subject_location(self):
else:
return subject_location

self._set_previous_subject_location(cleaned_data)
self._set_previous_subject_location(self.cleaned_data)
raise forms.ValidationError(
string_concat(
err_msg,
Expand All @@ -91,14 +91,18 @@ def get_urls(self):
return super().get_urls() + [
path("expand/<int:file_id>",
self.admin_site.admin_view(self.expand_view),
name=f"filer_{self.model._meta.model_name}_expand_view")
name=f"{self.opts.app_label}_{self.opts.model_name}_expand")
]

def expand_view(self, request, file_id):
image = get_object_or_404(self.model, pk=file_id)
return render(request, "admin/filer/image/expand.html", context={
"original_url": image.url
})
return TemplateResponse(
request,
"admin/filer/image/expand.html",
context={
"original_url": image.url
},
)


if FILER_IMAGE_MODEL == 'filer.Image':
Expand Down
6 changes: 3 additions & 3 deletions filer/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.http.response import HttpResponseBadRequest
from django.shortcuts import render
from django.template.response import TemplateResponse
from django.utils.translation import gettext_lazy as _

from .. import settings as filer_settings
Expand Down Expand Up @@ -58,7 +58,7 @@ def make_folder(request, folder_id=None):
new_folder.parent = folder
new_folder.owner = request.user
new_folder.save()
return render(request, 'admin/filer/dismiss_popup.html', context)
return TemplateResponse(request, 'admin/filer/dismiss_popup.html', context)
else:
new_folder_form = NewFolderForm()

Expand All @@ -69,7 +69,7 @@ def make_folder(request, folder_id=None):
'is_popup': popup_status(request),
'filer_admin_context': AdminContext(request),
})
return render(request, 'admin/filer/folder/new_folder_form.html', context)
return TemplateResponse(request, 'admin/filer/folder/new_folder_form.html', context)


@login_required
Expand Down
2 changes: 1 addition & 1 deletion filer/fields/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,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': '{}{}'.format(related_url, lookup_url),
'lookup_url': f'{related_url}{lookup_url}',
'change_url': change_url,
'object': obj,
'lookup_name': name,
Expand Down
2 changes: 1 addition & 1 deletion filer/fields/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def render(self, name, value, attrs=None, renderer=None):
# API to determine the ID dynamically.
context = {
'hidden_input': hidden_input,
'lookup_url': '{}{}'.format(related_url, url),
'lookup_url': f'{related_url}{url}',
'lookup_name': name,
'span_id': css_id_description_txt,
'object': obj,
Expand Down
2 changes: 1 addition & 1 deletion filer/fields/multistorage_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def value_to_string(self, obj):
encoded_string = base64.b64encode(payload_file.read()).decode('utf-8')
return value, encoded_string
except OSError:
warnings.warn('The payload for "{}" is missing. No such file on disk: {}!'.format(obj.original_filename, self.storage.location))
warnings.warn(f'The payload for "{obj.original_filename}" is missing. No such file on disk: {self.storage.location}!')
return value

def to_python(self, value):
Expand Down
Loading

0 comments on commit eb6cb64

Please sign in to comment.