Skip to content

Conversation

@fsbraun
Copy link
Member

@fsbraun fsbraun commented Nov 19, 2025

Description

Convert JavaScript in django-filer from jQuery to ES modules and vanilla JS, bundle scripts with webpack, remove static legacy libraries, and modernize the build and lint workflows.

New Features:

  • Introduce webpack.config.js and Gulp “bundle” task to produce ES module bundles for filer and admin-file-widget scripts.

Enhancements:

  • Rewrite all JS addons and base scripts as ES modules using vanilla JavaScript instead of jQuery.
  • Refactor package.json to define build, watch, lint and test scripts and update Gulpfile to invoke webpack and ESLint.
  • Update Django admin widgets and templates to load the new bundled JS instead of individual static libs.

Build:

  • Integrate webpack bundling into the Gulp workflow, enabling source maps in debug mode and minification in production.

CI:

  • Replace Flake8 with Ruff for pre-commit linting and add ESLint in CI pipelines.

Chores:

  • Remove obsolete static JS libraries (jQuery, jquery-ui, mediator, dropzone, etc.) from the repo and templates.

Related resources

image
  • #...
  • #...

Checklist

  • I have opened this pull request against master
  • I have added or modified the tests when changing logic
  • I have followed the conventional commits guidelines to add meaningful information into the changelog
  • I have read the contribution guidelines and I have joined #workgroup-pr-review on
    Slack to find a “pr review buddy” who is going to review my pull request.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @fsbraun, your pull request is larger than the review limit of 150000 diff characters

attrs["class"] = "vForeignKeyRawIdAdminField"
super_attrs = attrs.copy()
hidden_input = super(ForeignKeyRawIdWidget, self).render(name, value, super_attrs) # grandparent super
hidden_input = super(ForeignKeyRawIdWidget, self).render(

Check failure

Code scanning / CodeQL

First argument to super() is not enclosing class Error

First argument to super() should be AdminFolderWidget.

Copilot Autofix

AI 1 day ago

To fix the problem, update the call to super() on line 51 in the render method so that the first argument is the enclosing class: AdminFolderWidget. Thus, change super(ForeignKeyRawIdWidget, self).render(...) to super(AdminFolderWidget, self).render(...). This ensures that if the class is used in a multiple inheritance scenario, or extended, Python's MRO will correctly traverse parent classes, calling render() from the correct superclass.

Only line 51 inside filer/fields/folder.py needs modification. No new imports, methods, or definitions are required.


Suggested changeset 1
filer/fields/folder.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/filer/fields/folder.py b/filer/fields/folder.py
--- a/filer/fields/folder.py
+++ b/filer/fields/folder.py
@@ -48,7 +48,7 @@
             # The JavaScript looks for this hook.
             attrs["class"] = "vForeignKeyRawIdAdminField"
         super_attrs = attrs.copy()
-        hidden_input = super(ForeignKeyRawIdWidget, self).render(
+        hidden_input = super(AdminFolderWidget, self).render(
             name, value, super_attrs
         )  # grandparent super
 
EOF
@@ -48,7 +48,7 @@
# The JavaScript looks for this hook.
attrs["class"] = "vForeignKeyRawIdAdminField"
super_attrs = attrs.copy()
hidden_input = super(ForeignKeyRawIdWidget, self).render(
hidden_input = super(AdminFolderWidget, self).render(
name, value, super_attrs
) # grandparent super

Copilot is powered by AI and may make mistakes. Always verify output.
className = 'class="hidden"';
});
if (dropdown) {
dropdown.insertAdjacentHTML('beforeend', html);

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 1 day ago

How to fix the problem in general:
Whenever DOM text or user data is injected into an HTML string (especially via innerHTML, insertAdjacentHTML, etc.), it must be properly escaped to prevent HTML interpretation and hence XSS. You must ensure all generated HTML is safe, which means escaping or encoding any dynamic content to prevent meta-characters being interpreted as markup.

Best way to fix:
Before interpolating option.textContent into the HTML, escape it so that any special HTML characters (&, <, >, ", ') are replaced with their HTML entity equivalents. This can be implemented with a simple utility function, escapeHTML. The escaping should be applied directly before interpolation so that malicious content cannot break out of the anchor's structure.
In this context, there is no need for external libraries—modern JS makes escaping straightforward.

Where to change:

  • Introduce an escapeHTML function in this file.
  • Rreplace ${option.textContent} with ${escapeHTML(option.textContent)} in the line building the HTML for the dropdown.
  • No further code changes are needed; the remainder of the code can stay the same.

Suggested changeset 1
filer/static/filer/js/base.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/filer/static/filer/js/base.js b/filer/static/filer/js/base.js
--- a/filer/static/filer/js/base.js
+++ b/filer/static/filer/js/base.js
@@ -13,6 +13,16 @@
 Cl.Toggler = Toggler;
 
 
+// Escape HTML utility to safely inject text into HTML
+function escapeHTML(str) {
+    return str
+        .replace(/&/g, "&amp;")
+        .replace(/</g, "&lt;")
+        .replace(/>/g, "&gt;")
+        .replace(/"/g, "&quot;")
+        .replace(/'/g, "&#39;");
+}
+
 document.addEventListener('DOMContentLoaded', () => {
     let showErrorTimeout;
 
@@ -201,7 +211,7 @@
                 if (option.value === valueDelete || option.value === valueCopy || option.value === valueMove) {
                     className = 'class="hidden"';
                 }
-                html += `<li><a href="#"${className}>${option.textContent}</a></li>`;
+                html += `<li><a href="#"${className}>${escapeHTML(option.textContent)}</a></li>`;
             }
         });
         if (dropdown) {
EOF
@@ -13,6 +13,16 @@
Cl.Toggler = Toggler;


// Escape HTML utility to safely inject text into HTML
function escapeHTML(str) {
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}

document.addEventListener('DOMContentLoaded', () => {
let showErrorTimeout;

@@ -201,7 +211,7 @@
if (option.value === valueDelete || option.value === valueCopy || option.value === valueMove) {
className = 'class="hidden"';
}
html += `<li><a href="#"${className}>${option.textContent}</a></li>`;
html += `<li><a href="#"${className}>${escapeHTML(option.textContent)}</a></li>`;
}
});
if (dropdown) {
Copilot is powered by AI and may make mistakes. Always verify output.
var checkMinWidth = function (element) {
element.toggleClass(mobileClass, element.width() < minWidth);
document.addEventListener('DOMContentLoaded', () => {
const dropzoneTemplateSelector = '.js-filer-dropzone-template';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable dropzoneTemplateSelector.

Copilot Autofix

AI 1 day ago

To fix the problem, remove the declaration of dropzoneTemplateSelector from line 12 in filer/static/filer/js/addons/dropzone.init.js.

  • Only delete the line declaring the unused variable.
  • No additional imports, methods, or definitions are required.
  • This change does not affect functionality as the variable is unused.
Suggested changeset 1
filer/static/filer/js/addons/dropzone.init.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/filer/static/filer/js/addons/dropzone.init.js b/filer/static/filer/js/addons/dropzone.init.js
--- a/filer/static/filer/js/addons/dropzone.init.js
+++ b/filer/static/filer/js/addons/dropzone.init.js
@@ -9,7 +9,6 @@
 }
 
 document.addEventListener('DOMContentLoaded', () => {
-    const dropzoneTemplateSelector = '.js-filer-dropzone-template';
     const previewImageSelector = '.js-img-preview';
     const dropzoneSelector = '.js-filer-dropzone';
     const dropzones = document.querySelectorAll(dropzoneSelector);
EOF
@@ -9,7 +9,6 @@
}

document.addEventListener('DOMContentLoaded', () => {
const dropzoneTemplateSelector = '.js-filer-dropzone-template';
const previewImageSelector = '.js-img-preview';
const dropzoneSelector = '.js-filer-dropzone';
const dropzones = document.querySelectorAll(dropzoneSelector);
Copilot is powered by AI and may make mistakes. Always verify output.
if (clearButton) {
clearButton.addEventListener('click', () => {
dropzone.classList.remove(objectAttachedClass);
const changeEvent = new Event('change', { bubbles: true });

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable changeEvent.

Copilot Autofix

AI 1 day ago

To fix the problem, remove the unused local variable changeEvent defined on line 102, since it is never used in the code and only created for a commented-out statement. Locate the definition const changeEvent = new Event('change', { bubbles: true }); inside the click event handler for clearButton, and delete this line.

No additional imports, methods, or definitions are necessary. Ensure that only the line creating changeEvent is removed, without disturbing the surrounding logic.


Suggested changeset 1
filer/static/filer/js/addons/dropzone.init.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/filer/static/filer/js/addons/dropzone.init.js b/filer/static/filer/js/addons/dropzone.init.js
--- a/filer/static/filer/js/addons/dropzone.init.js
+++ b/filer/static/filer/js/addons/dropzone.init.js
@@ -99,7 +99,6 @@
                 if (clearButton) {
                     clearButton.addEventListener('click', () => {
                         dropzone.classList.remove(objectAttachedClass);
-                        const changeEvent = new Event('change', { bubbles: true });
                         // inputId?.dispatchEvent(changeEvent);
                     });
                 }
EOF
@@ -99,7 +99,6 @@
if (clearButton) {
clearButton.addEventListener('click', () => {
dropzone.classList.remove(objectAttachedClass);
const changeEvent = new Event('change', { bubbles: true });
// inputId?.dispatchEvent(changeEvent);
});
}
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants