Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: python -m pip install -r tests/requirements/frontend.txt
- run: npm install
- run: npm install -g gulp@4.0.2
- run: npm install -g gulp
- run: gulp ci
20 changes: 10 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
requirements-file: [
django-4.2.txt,
django-5.0.txt,
django-5.1.txt,
django-5.2.txt,
django-6.0.txt,
django-main.txt,
]
custom-image-model: [false, true]
os: [
ubuntu-latest,
]
exclude:
- requirements-file: django-5.0.txt
python-version: 3.9
- requirements-file: django-5.1.txt
python-version: 3.9
- requirements-file: django-5.2.txt
python-version: 3.9
- requirements-file: django-main.txt
python-version: 3.9
- requirements-file: django-main.txt
python-version: 3.10
- requirements-file: django-main.txt
python-version: 3.11
- requirements-file: django-6.0.txt
python-version: 3.10
- requirements-file: django-6.0.txt
python-version: 3.11
- requirements-file: django-4.2.txt
python-version: 3.14
- requirements-file: django-5.1.txt
python-version: 3.14

steps:
- uses: actions/checkout@v1
Expand Down
14 changes: 5 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,12 @@ repos:
# - id: django-upgrade
# args: [--target-version, "2.2"]

- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.3
hooks:
- id: flake8
entry: pflake8
additional_dependencies: [pyproject-flake8]
- repo: https://github.com/asottile/yesqa
rev: v1.5.0
hooks:
- id: yesqa
- id: ruff
args: [--fix]
- id: ruff

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
CHANGELOG
=========

3.4.0 (2025-11-21)
==================

* feat: Add Django 6.0 support
* feat: Add CSP support by collecting all JS code in bundles
* fix: preserve "limit search to folder" state in pagination links #1553 by @benzkji in https://github.com/django-cms/django-filer/pull/1555


3.3.3 / 2025-11-07
==================

Expand Down
99 changes: 99 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
export default [
{
ignores: [
'**/dist/**',
'**/node_modules/**',
'**/*.min.js',
'**/filer/static/filer/js/dist/**'
]
},
{
files: ['**/*.js'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
globals: {
// Browser globals
window: 'readonly',
document: 'readonly',
console: 'readonly',
alert: 'readonly',
Event: 'readonly',
CustomEvent: 'readonly',
MouseEvent: 'readonly',
MutationObserver: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
NodeList: 'readonly',
HTMLCollection: 'readonly',
URL: 'readonly',
navigator: 'readonly',
// Node.js globals
require: 'readonly',
module: 'readonly',
__dirname: 'readonly',
process: 'readonly',
// Django globals
django: 'readonly',
// Third-party library globals
Dropzone: 'readonly',
Mediator: 'readonly',
Class: 'readonly',
opener: 'readonly',
// Custom globals
Cl: 'readonly',
// jQuery (for legacy code/tests)
$: 'readonly',
jQuery: 'readonly',
// Test globals
jasmine: 'readonly',
describe: 'readonly',
it: 'readonly',
expect: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly'
}
},
rules: {
// Code quality
'eqeqeq': ['error', 'always'],
'curly': ['error', 'all'],
'no-unused-vars': ['error', { 'vars': 'all', 'args': 'after-used' }],
'no-undef': 'error',
'no-bitwise': 'error',
'no-caller': 'error',

// Style
'quotes': ['error', 'single', { 'avoidEscape': true }],
'indent': ['error', 4, { 'SwitchCase': 1 }],
'semi': ['error', 'always'],
'no-trailing-spaces': 'error',
'no-multiple-empty-lines': ['error', { 'max': 2 }],
'max-len': ['error', { 'code': 120, 'ignoreUrls': true, 'ignoreRegExpLiterals': true }],

// Best practices
'strict': 'off', // Not needed for ES6 modules
'no-implied-eval': 'error',
'no-new-func': 'error'
}
},
{
files: ['gulpfile.js', '**/karma.conf.js'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'script',
globals: {
require: 'readonly',
module: 'readonly',
__dirname: 'readonly',
process: 'readonly',
console: 'readonly'
}
},
rules: {
'strict': ['error', 'global']
}
}
];
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.3.3'
__version__ = '3.4.0'
69 changes: 31 additions & 38 deletions filer/fields/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import warnings

from django import forms
from django.conf import settings
from django.contrib.admin.sites import site
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
from django.core.exceptions import ObjectDoesNotExist
Expand All @@ -27,52 +26,56 @@ class AdminFileWidget(ForeignKeyRawIdWidget):

def render(self, name, value, attrs=None, renderer=None):
obj = self.obj_for_value(value)
css_id = attrs.get('id', 'id_image_x')
css_id = attrs.get("id", "id_image_x")
related_url = None
change_url = ''
change_url = ""
if value:
try:
file_obj = File.objects.get(pk=value)
related_url = file_obj.logical_folder.get_admin_directory_listing_url_path()
related_url = (
file_obj.logical_folder.get_admin_directory_listing_url_path()
)
change_url = file_obj.get_admin_change_url()
except Exception as e:
# catch exception and manage it. We can re-raise it for debugging
# purposes and/or just logging it, provided user configured
# proper logging configuration
if filer_settings.FILER_ENABLE_LOGGING:
logger.error('Error while rendering file widget: %s', e)
logger.error("Error while rendering file widget: %s", e)
if filer_settings.FILER_DEBUG:
raise
if not related_url:
related_url = reverse('admin:filer-directory_listing-last')
related_url = reverse("admin:filer-directory_listing-last")
params = self.url_parameters()
params['_pick'] = 'file'
params["_pick"] = "file"
if params:
lookup_url = '?' + urlencode(sorted(params.items()))
lookup_url = "?" + urlencode(sorted(params.items()))
else:
lookup_url = ''
if 'class' not in attrs:
lookup_url = ""
if "class" not in attrs:
# The JavaScript looks for this hook.
attrs['class'] = 'vForeignKeyRawIdAdminField'
attrs["class"] = "vForeignKeyRawIdAdminField"
# rendering the super for ForeignKeyRawIdWidget on purpose here because
# we only need the input and none of the other stuff that
# ForeignKeyRawIdWidget adds
hidden_input = super(ForeignKeyRawIdWidget, self).render(name, value, attrs) # grandparent super
hidden_input = super(ForeignKeyRawIdWidget, self).render(
name, value, attrs
) # grandparent super
context = {
'hidden_input': hidden_input,
'lookup_url': f'{related_url}{lookup_url}',
'change_url': change_url,
'object': obj,
'lookup_name': name,
'id': css_id,
'admin_icon_delete': ('admin/img/icon-deletelink.svg'),
"hidden_input": hidden_input,
"lookup_url": f"{related_url}{lookup_url}",
"change_url": change_url,
"object": obj,
"lookup_name": name,
"id": css_id,
"admin_icon_delete": ("admin/img/icon-deletelink.svg"),
}
html = render_to_string('admin/filer/widgets/admin_file.html', context)
html = render_to_string("admin/filer/widgets/admin_file.html", context)
return mark_safe(html)

def label_for_value(self, value):
obj = self.obj_for_value(value)
return '&nbsp;<strong>%s</strong>' % truncate_words(obj, 14)
return "&nbsp;<strong>%s</strong>" % truncate_words(obj, 14)

def obj_for_value(self, value):
if value:
Expand All @@ -87,20 +90,10 @@ def obj_for_value(self, value):
return obj

class Media:
extra = '' if settings.DEBUG else '.min'
css = {
'all': (
'filer/css/admin_filer.css',
) + ICON_CSS_LIB,
"all": ("filer/css/admin_filer.css",) + ICON_CSS_LIB,
}
js = (
'admin/js/vendor/jquery/jquery%s.js' % extra,
'admin/js/jquery.init.js',
'filer/js/libs/dropzone.min.js',
'filer/js/addons/dropzone.init.js',
'filer/js/addons/popup_handling.js',
'filer/js/addons/widget.js',
)
js = ("filer/js/dist/admin-file-widget.bundle.js",)


class AdminFileFormField(forms.ModelChoiceField):
Expand All @@ -112,7 +105,7 @@ def __init__(self, rel, queryset, to_field_name, *args, **kwargs):
self.to_field_name = to_field_name
self.max_value = None
self.min_value = None
kwargs.pop('widget', None)
kwargs.pop("widget", None)
super().__init__(queryset, widget=self.widget(rel, site), *args, **kwargs)

def widget_attrs(self, widget):
Expand All @@ -125,18 +118,18 @@ class FilerFileField(models.ForeignKey):
default_model_class = File

def __init__(self, **kwargs):
to = kwargs.pop('to', None)
to = kwargs.pop("to", None)
dfl = get_model_label(self.default_model_class)
if to and get_model_label(to).lower() != dfl.lower():
msg = "In {}: ForeignKey must point to {}; instead passed {}"
warnings.warn(msg.format(self.__class__.__name__, dfl, to), SyntaxWarning)
kwargs['to'] = dfl # hard-code `to` to model `filer.File`
kwargs["to"] = dfl # hard-code `to` to model `filer.File`
super().__init__(**kwargs)

def formfield(self, **kwargs):
defaults = {
'form_class': self.default_form_class,
'rel': self.remote_field,
"form_class": self.default_form_class,
"rel": self.remote_field,
}
defaults.update(kwargs)
return super().formfield(**defaults)
Loading
Loading