Skip to content

[change] Update theme customization paths and enhance collectstatic logic#579

Open
pandafy wants to merge 1 commit intomasterfrom
fix-theme-customizations
Open

[change] Update theme customization paths and enhance collectstatic logic#579
pandafy wants to merge 1 commit intomasterfrom
fix-theme-customizations

Conversation

@pandafy
Copy link
Member

@pandafy pandafy commented Mar 18, 2026

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Description of Changes

Bug:
Previously, when the OPENWISP_ADMIN_THEME_LINKS setting included any CSS or JavaScript file paths starting with /static/, the OpenWISP utilities logic would strip the /static/ prefix. As a result, Django couldn't recognize these custom static files, preventing them from being collected and served. Additionally, static files were previously mounted directly in the Nginx container, bypassing Django’s static collection workflow entirely.

Fix:
Mount custom theme assets inside the dashboard container and integrate them into Django’s staticfiles pipeline, ensuring they are processed by collectstatic and correctly served via Nginx.

…ogic

Bug:
Previously, when the OPENWISP_ADMIN_THEME_LINKS setting included any
CSS or JavaScript file paths starting with /static/, the OpenWISP
utilities logic would strip the /static/ prefix. As a result, Django
couldn't recognize these custom static files, preventing them from being
collected and served. Additionally, static files were previously
mounted directly in the Nginx container, bypassing Django’s static
collection workflow entirely.

Fix:
Mount custom theme assets inside the dashboard container and integrate
them into Django’s staticfiles pipeline, ensuring they are processed by
collectstatic and correctly served via Nginx.
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

The changes introduce a customization mechanism for OpenWISP static files (CSS and themes) by implementing change detection and recovery workflows. The docker-compose configuration is updated to separate theme customization into static_custom (for Django dashboard) and nginx (for Nginx-specific files). A new hashing function monitors the static_custom directory for file changes, comparing against a cached hash in Redis to determine whether to run Django's collectstatic command. Documentation is added to guide users through the customization process, including new maintenance mode functionality where an Nginx-served maintenance.html file can be enabled by creating it at a specified path.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Bug Fixes ❌ Error Pull request lacks regression tests to verify the static files bug fix and doesn't address review comments about documentation paths and --clear flag implementation. Add tests verifying collectstatic functionality, file detection, and custom asset loading; fix documentation path references; implement --clear flag for stale file cleanup.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes: updating theme customization paths and enhancing collectstatic logic, which directly aligns with the PR's core objectives.
Description check ✅ Passed The description includes completed checklist items, a clear reference to the bug and fix, and updated documentation. However, the 'Reference to Existing Issue' section with an issue number is missing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-theme-customizations
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/user/customization.rst`:
- Around line 190-191: Update the docs/user/customization.rst entry that
currently points to "customization/custom/maintenance.html": change the
documented maintenance file path to the actual mounted location
("customization/nginx/maintenance.html") and add a brief note that
docker-compose mounts ./customization/nginx into /opt/openwisp/public/custom so
placing maintenance.html there enables maintenance mode.

In `@images/common/collectstatic.py`:
- Around line 79-87: The call to run_collectstatic() should include the --clear
flag only when static_changed is true so stale files in STATIC_ROOT are removed;
modify the branch that checks pip_changed or static_changed to call
run_collectstatic("--clear") when static_changed is true and run_collectstatic()
without the flag otherwise, leaving the existing
redis_connection.set("pip_freeze_hash", current_pip_hash) and
redis_connection.set("static_custom_hash", current_static_hash) logic intact;
locate the conditional surrounding pip_changed/static_changed and the
run_collectstatic() invocation to implement this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: efe678e1-ba46-4982-b0eb-1c57e626ba95

📥 Commits

Reviewing files that changed from the base of the PR and between 76cd872 and a0c6c39.

📒 Files selected for processing (4)
  • docker-compose.yml
  • docs/user/customization.rst
  • images/common/collectstatic.py
  • images/common/openwisp/settings.py
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-01-06T11:55:13.672Z
Learnt from: pandafy
Repo: openwisp/docker-openwisp PR: 549
File: docker-compose.yml:85-85
Timestamp: 2026-01-06T11:55:13.672Z
Learning: In docker-compose.yml files, avoid leaving CELERY_SERVICE_NETWORK_MODE as an empty string; an empty value is ignored and Docker Compose falls back to default networking. Do not rely on setting CELERY_SERVICE_NETWORK_MODE to 'bridge' for celery services, as this will not affect their networking. If specific networking is required for celery services, define and attach explicit networks in the compose file and reference them on the celery services.

Applied to files:

  • docker-compose.yml
📚 Learning: 2026-01-06T11:56:48.600Z
Learnt from: pandafy
Repo: openwisp/docker-openwisp PR: 549
File: docker-compose.yml:85-85
Timestamp: 2026-01-06T11:56:48.600Z
Learning: In docker-openwisp projects, ensure CELERY_SERVICE_NETWORK_MODE is set to an empty string "" (which Docker Compose treats as unset/null). This allows containers to connect via the Compose default network with correct service name DNS resolution. Using "bridge" as the value disables service name resolution and breaks communication between celery, dashboard, postgres, and redis. Apply this guideline to docker-compose.yml files in the repository and any similar Compose files where CELERY services rely on service name DNS.

Applied to files:

  • docker-compose.yml
🔇 Additional comments (6)
docker-compose.yml (2)

32-32: Mounting theme assets into dashboard is the right integration point.

This routes custom files through Django’s staticfiles pipeline instead of serving them out-of-band.


132-132: Splitting Nginx-only custom files into customization/nginx looks good.

Keeping /opt/openwisp/public/custom separate from static_custom avoids mixing Nginx overrides with Django-managed assets.

images/common/openwisp/settings.py (1)

287-287: STATICFILES_DIRS now points at the custom theme root correctly.

This makes customization/theme/custom/... resolvable as /static/custom/... during collectstatic.

images/common/collectstatic.py (1)

29-51: The directory hash helper is deterministic.

Sorting dirs and files before hashing keeps change detection stable across restarts.

docs/user/customization.rst (2)

27-27: Adding customization/nginx to the bootstrap commands is useful.

It matches the new bind mount and makes the directory layout explicit for first-time setups.


88-105: The revised theme workflow instructions match the new container layout.

Pointing users at customization/theme/custom/... and rerunning collectstatic on dashboard is consistent with the compose change.

Comment on lines +190 to +191
To enable maintenance mode, create a ``maintenance.html`` file in the
``customization/custom/maintenance.html`` created above.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The maintenance-mode path is documented incorrectly.

docker-compose.yml now mounts ./customization/nginx into /opt/openwisp/public/custom, so following customization/custom/maintenance.html will not enable the feature.

✏️ Suggested doc fix
-To enable maintenance mode, create a ``maintenance.html`` file in the
-``customization/custom/maintenance.html`` created above.
+To enable maintenance mode, create a ``maintenance.html`` file at
+``customization/nginx/maintenance.html``.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
To enable maintenance mode, create a ``maintenance.html`` file in the
``customization/custom/maintenance.html`` created above.
To enable maintenance mode, create a ``maintenance.html`` file at
``customization/nginx/maintenance.html``.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/customization.rst` around lines 190 - 191, Update the
docs/user/customization.rst entry that currently points to
"customization/custom/maintenance.html": change the documented maintenance file
path to the actual mounted location ("customization/nginx/maintenance.html") and
add a brief note that docker-compose mounts ./customization/nginx into
/opt/openwisp/public/custom so placing maintenance.html there enables
maintenance mode.

Comment on lines +79 to +87
if pip_changed or static_changed:
print(
"Changes in Python dependencies or static_custom detected,"
" running collectstatic..."
)
run_collectstatic()
redis_connection.set("pip_freeze_hash", current_pip_hash)
try:
redis_connection.set("pip_freeze_hash", current_pip_hash)
redis_connection.set("static_custom_hash", current_static_hash)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Django's collectstaticcommand remove files fromSTATIC_ROOTwhen the source file is deleted, or is the--clear option required to remove stale files?

💡 Result:

collectstatic does not automatically remove files from STATIC_ROOT when you delete them from the source locations. On subsequent runs, it mainly copies files that are newer than what’s already in STATIC_ROOT, so deleted/removed files can be left behind as stale files. [1]

To remove those stale files, you should run collectstatic --clear (or otherwise wipe STATIC_ROOT) so Django clears the existing files first and then recopies what currently exists. [1]


🏁 Script executed:

# First, let's look at the file to understand the context and structure
cat -n images/common/collectstatic.py

Repository: openwisp/docker-openwisp

Length of output: 4100


Use --clear flag when static files change to prevent serving deleted assets.

Django's collectstatic command does not remove files from STATIC_ROOT when they are deleted from the source directory—it only copies newer files. This means deleting or renaming a file under static_custom leaves the old file in openwisp_static, and Nginx continues serving an asset that no longer exists in the source tree.

To fix this, pass --clear=static_changed when calling run_collectstatic() so the --clear flag is used only when static files change, ensuring stale files are removed.

🛠️ Suggested fix
-def run_collectstatic():
+def run_collectstatic(clear=False):
     try:
-        subprocess.run(
-            [sys.executable, "manage.py", "collectstatic", "--noinput"], check=True
-        )
+        command = [sys.executable, "manage.py", "collectstatic", "--noinput"]
+        if clear:
+            command.append("--clear")
+        subprocess.run(command, check=True)
     except subprocess.CalledProcessError as e:
         print(f"Error running 'collectstatic': {e}", file=sys.stderr)
         sys.exit(1)
@@
-        run_collectstatic()
+        run_collectstatic(clear=static_changed)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@images/common/collectstatic.py` around lines 79 - 87, The call to
run_collectstatic() should include the --clear flag only when static_changed is
true so stale files in STATIC_ROOT are removed; modify the branch that checks
pip_changed or static_changed to call run_collectstatic("--clear") when
static_changed is true and run_collectstatic() without the flag otherwise,
leaving the existing redis_connection.set("pip_freeze_hash", current_pip_hash)
and redis_connection.set("static_custom_hash", current_static_hash) logic
intact; locate the conditional surrounding pip_changed/static_changed and the
run_collectstatic() invocation to implement this change.

@github-project-automation github-project-automation bot moved this from To do (general) to In progress in OpenWISP Contributor's Board Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

1 participant