Skip to content

Commit d6026fc

Browse files
authored
chore: Bundle js files, remove jQuery, and static libs (#1556)
* feat: Bundle js files, remove jQuery, and static libs * chore: Remove inline js * Update the change forms. * fix: Colors and border radii * fix: frontend test * chore: fix pre-commit * fix: JS CI * fix: Replace HTML injection * fix test js lint * chore: Update test matrix * chore: Remove py3.14 tests for Dj4.2 and Dj5.1 * chore: Prepare release 3.4.0 * fix: line length
1 parent 9c9d0d0 commit d6026fc

Some content is hidden

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

60 files changed

+2403
-1630
lines changed

.github/workflows/frontend.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ jobs:
1717
node-version: ${{ matrix.node-version }}
1818
- run: python -m pip install -r tests/requirements/frontend.txt
1919
- run: npm install
20-
- run: npm install -g gulp@4.0.2
20+
- run: npm install -g gulp
2121
- run: gulp ci

.github/workflows/test.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,31 @@ jobs:
88
strategy:
99
fail-fast: false
1010
matrix:
11-
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
11+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
1212
requirements-file: [
1313
django-4.2.txt,
14-
django-5.0.txt,
1514
django-5.1.txt,
1615
django-5.2.txt,
16+
django-6.0.txt,
1717
django-main.txt,
1818
]
1919
custom-image-model: [false, true]
2020
os: [
2121
ubuntu-latest,
2222
]
2323
exclude:
24-
- requirements-file: django-5.0.txt
25-
python-version: 3.9
26-
- requirements-file: django-5.1.txt
27-
python-version: 3.9
28-
- requirements-file: django-5.2.txt
29-
python-version: 3.9
30-
- requirements-file: django-main.txt
31-
python-version: 3.9
3224
- requirements-file: django-main.txt
3325
python-version: 3.10
3426
- requirements-file: django-main.txt
3527
python-version: 3.11
28+
- requirements-file: django-6.0.txt
29+
python-version: 3.10
30+
- requirements-file: django-6.0.txt
31+
python-version: 3.11
32+
- requirements-file: django-4.2.txt
33+
python-version: 3.14
34+
- requirements-file: django-5.1.txt
35+
python-version: 3.14
3636

3737
steps:
3838
- uses: actions/checkout@v1

.pre-commit-config.yaml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,12 @@ repos:
2020
# - id: django-upgrade
2121
# args: [--target-version, "2.2"]
2222

23-
- repo: https://github.com/PyCQA/flake8
24-
rev: 7.0.0
23+
- repo: https://github.com/astral-sh/ruff-pre-commit
24+
rev: v0.8.3
2525
hooks:
26-
- id: flake8
27-
entry: pflake8
28-
additional_dependencies: [pyproject-flake8]
29-
- repo: https://github.com/asottile/yesqa
30-
rev: v1.5.0
31-
hooks:
32-
- id: yesqa
26+
- id: ruff
27+
args: [--fix]
28+
- id: ruff
3329

3430
- repo: https://github.com/pre-commit/pre-commit-hooks
3531
rev: v6.0.0

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
CHANGELOG
33
=========
44

5+
3.4.0 (2025-11-21)
6+
==================
7+
8+
* feat: Add Django 6.0 support
9+
* feat: Add CSP support by collecting all JS code in bundles
10+
* fix: preserve "limit search to folder" state in pagination links #1553 by @benzkji in https://github.com/django-cms/django-filer/pull/1555
11+
12+
513
3.3.3 / 2025-11-07
614
==================
715

eslint.config.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
export default [
2+
{
3+
ignores: [
4+
'**/dist/**',
5+
'**/node_modules/**',
6+
'**/*.min.js',
7+
'**/filer/static/filer/js/dist/**'
8+
]
9+
},
10+
{
11+
files: ['**/*.js'],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
sourceType: 'module',
15+
globals: {
16+
// Browser globals
17+
window: 'readonly',
18+
document: 'readonly',
19+
console: 'readonly',
20+
alert: 'readonly',
21+
Event: 'readonly',
22+
CustomEvent: 'readonly',
23+
MouseEvent: 'readonly',
24+
MutationObserver: 'readonly',
25+
setTimeout: 'readonly',
26+
clearTimeout: 'readonly',
27+
setInterval: 'readonly',
28+
clearInterval: 'readonly',
29+
NodeList: 'readonly',
30+
HTMLCollection: 'readonly',
31+
URL: 'readonly',
32+
navigator: 'readonly',
33+
// Node.js globals
34+
require: 'readonly',
35+
module: 'readonly',
36+
__dirname: 'readonly',
37+
process: 'readonly',
38+
// Django globals
39+
django: 'readonly',
40+
// Third-party library globals
41+
Dropzone: 'readonly',
42+
Mediator: 'readonly',
43+
Class: 'readonly',
44+
opener: 'readonly',
45+
// Custom globals
46+
Cl: 'readonly',
47+
// jQuery (for legacy code/tests)
48+
$: 'readonly',
49+
jQuery: 'readonly',
50+
// Test globals
51+
jasmine: 'readonly',
52+
describe: 'readonly',
53+
it: 'readonly',
54+
expect: 'readonly',
55+
beforeEach: 'readonly',
56+
afterEach: 'readonly'
57+
}
58+
},
59+
rules: {
60+
// Code quality
61+
'eqeqeq': ['error', 'always'],
62+
'curly': ['error', 'all'],
63+
'no-unused-vars': ['error', { 'vars': 'all', 'args': 'after-used' }],
64+
'no-undef': 'error',
65+
'no-bitwise': 'error',
66+
'no-caller': 'error',
67+
68+
// Style
69+
'quotes': ['error', 'single', { 'avoidEscape': true }],
70+
'indent': ['error', 4, { 'SwitchCase': 1 }],
71+
'semi': ['error', 'always'],
72+
'no-trailing-spaces': 'error',
73+
'no-multiple-empty-lines': ['error', { 'max': 2 }],
74+
'max-len': ['error', { 'code': 120, 'ignoreUrls': true, 'ignoreRegExpLiterals': true }],
75+
76+
// Best practices
77+
'strict': 'off', // Not needed for ES6 modules
78+
'no-implied-eval': 'error',
79+
'no-new-func': 'error'
80+
}
81+
},
82+
{
83+
files: ['gulpfile.js', '**/karma.conf.js'],
84+
languageOptions: {
85+
ecmaVersion: 2020,
86+
sourceType: 'script',
87+
globals: {
88+
require: 'readonly',
89+
module: 'readonly',
90+
__dirname: 'readonly',
91+
process: 'readonly',
92+
console: 'readonly'
93+
}
94+
},
95+
rules: {
96+
'strict': ['error', 'global']
97+
}
98+
}
99+
];

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.3.3'
16+
__version__ = '3.4.0'

filer/fields/file.py

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import warnings
33

44
from django import forms
5-
from django.conf import settings
65
from django.contrib.admin.sites import site
76
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
87
from django.core.exceptions import ObjectDoesNotExist
@@ -27,52 +26,56 @@ class AdminFileWidget(ForeignKeyRawIdWidget):
2726

2827
def render(self, name, value, attrs=None, renderer=None):
2928
obj = self.obj_for_value(value)
30-
css_id = attrs.get('id', 'id_image_x')
29+
css_id = attrs.get("id", "id_image_x")
3130
related_url = None
32-
change_url = ''
31+
change_url = ""
3332
if value:
3433
try:
3534
file_obj = File.objects.get(pk=value)
36-
related_url = file_obj.logical_folder.get_admin_directory_listing_url_path()
35+
related_url = (
36+
file_obj.logical_folder.get_admin_directory_listing_url_path()
37+
)
3738
change_url = file_obj.get_admin_change_url()
3839
except Exception as e:
3940
# catch exception and manage it. We can re-raise it for debugging
4041
# purposes and/or just logging it, provided user configured
4142
# proper logging configuration
4243
if filer_settings.FILER_ENABLE_LOGGING:
43-
logger.error('Error while rendering file widget: %s', e)
44+
logger.error("Error while rendering file widget: %s", e)
4445
if filer_settings.FILER_DEBUG:
4546
raise
4647
if not related_url:
47-
related_url = reverse('admin:filer-directory_listing-last')
48+
related_url = reverse("admin:filer-directory_listing-last")
4849
params = self.url_parameters()
49-
params['_pick'] = 'file'
50+
params["_pick"] = "file"
5051
if params:
51-
lookup_url = '?' + urlencode(sorted(params.items()))
52+
lookup_url = "?" + urlencode(sorted(params.items()))
5253
else:
53-
lookup_url = ''
54-
if 'class' not in attrs:
54+
lookup_url = ""
55+
if "class" not in attrs:
5556
# The JavaScript looks for this hook.
56-
attrs['class'] = 'vForeignKeyRawIdAdminField'
57+
attrs["class"] = "vForeignKeyRawIdAdminField"
5758
# rendering the super for ForeignKeyRawIdWidget on purpose here because
5859
# we only need the input and none of the other stuff that
5960
# ForeignKeyRawIdWidget adds
60-
hidden_input = super(ForeignKeyRawIdWidget, self).render(name, value, attrs) # grandparent super
61+
hidden_input = super(ForeignKeyRawIdWidget, self).render(
62+
name, value, attrs
63+
) # grandparent super
6164
context = {
62-
'hidden_input': hidden_input,
63-
'lookup_url': f'{related_url}{lookup_url}',
64-
'change_url': change_url,
65-
'object': obj,
66-
'lookup_name': name,
67-
'id': css_id,
68-
'admin_icon_delete': ('admin/img/icon-deletelink.svg'),
65+
"hidden_input": hidden_input,
66+
"lookup_url": f"{related_url}{lookup_url}",
67+
"change_url": change_url,
68+
"object": obj,
69+
"lookup_name": name,
70+
"id": css_id,
71+
"admin_icon_delete": ("admin/img/icon-deletelink.svg"),
6972
}
70-
html = render_to_string('admin/filer/widgets/admin_file.html', context)
73+
html = render_to_string("admin/filer/widgets/admin_file.html", context)
7174
return mark_safe(html)
7275

7376
def label_for_value(self, value):
7477
obj = self.obj_for_value(value)
75-
return '&nbsp;<strong>%s</strong>' % truncate_words(obj, 14)
78+
return "&nbsp;<strong>%s</strong>" % truncate_words(obj, 14)
7679

7780
def obj_for_value(self, value):
7881
if value:
@@ -87,20 +90,10 @@ def obj_for_value(self, value):
8790
return obj
8891

8992
class Media:
90-
extra = '' if settings.DEBUG else '.min'
9193
css = {
92-
'all': (
93-
'filer/css/admin_filer.css',
94-
) + ICON_CSS_LIB,
94+
"all": ("filer/css/admin_filer.css",) + ICON_CSS_LIB,
9595
}
96-
js = (
97-
'admin/js/vendor/jquery/jquery%s.js' % extra,
98-
'admin/js/jquery.init.js',
99-
'filer/js/libs/dropzone.min.js',
100-
'filer/js/addons/dropzone.init.js',
101-
'filer/js/addons/popup_handling.js',
102-
'filer/js/addons/widget.js',
103-
)
96+
js = ("filer/js/dist/admin-file-widget.bundle.js",)
10497

10598

10699
class AdminFileFormField(forms.ModelChoiceField):
@@ -112,7 +105,7 @@ def __init__(self, rel, queryset, to_field_name, *args, **kwargs):
112105
self.to_field_name = to_field_name
113106
self.max_value = None
114107
self.min_value = None
115-
kwargs.pop('widget', None)
108+
kwargs.pop("widget", None)
116109
super().__init__(queryset, widget=self.widget(rel, site), *args, **kwargs)
117110

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

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

136129
def formfield(self, **kwargs):
137130
defaults = {
138-
'form_class': self.default_form_class,
139-
'rel': self.remote_field,
131+
"form_class": self.default_form_class,
132+
"rel": self.remote_field,
140133
}
141134
defaults.update(kwargs)
142135
return super().formfield(**defaults)

0 commit comments

Comments
 (0)