Skip to content

[change] Honor notification data["url"] for the web target link#490

Open
Eeshu-Yadav wants to merge 1 commit into
gsoc26-persistent-scheduled-upgradesfrom
feature/notification-url-web-link
Open

[change] Honor notification data["url"] for the web target link#490
Eeshu-Yadav wants to merge 1 commit into
gsoc26-persistent-scheduled-upgradesfrom
feature/notification-url-web-link

Conversation

@Eeshu-Yadav

@Eeshu-Yadav Eeshu-Yadav commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

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.

Reference to Existing Issue

Related to the persistent mass upgrades work in
openwisp/openwisp-firmware-upgrader#436, which needs a generic_message notification to link to a filtered admin page without registering a dedicated notification type.

Description of Changes

The web notification widget links to the serialized target_url, which for generic_message is always the target object's admin URL. A per-notification url passed to notify.send() was only used for the email link, so a notification could not point its web link at a specific page without registering a dedicated notification type with a
target_link.
This returns data["url"] from the serialized target_url when it is set, falling back to the target object's URL otherwise, which is consistent with the email link that already uses data["url"]. The model target_url property is left unchanged, so message templates that render {target_link} continue to point at the target object.

Copilot AI review requested due to automatic review settings July 11, 2026 15:17
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d26c6c45-71e0-4e16-a807-d7a931553fd6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

NotificationSerializer now overrides to_representation to use the URL from notification data when present, otherwise retaining the stored target URL. A test verifies both precedence and fallback behavior, including generated URLs containing the target user’s primary key.

Estimated code review effort: 2 (Simple) | ~10 minutes


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (2 errors, 2 warnings)

Check name Status Explanation Resolution
Bug Fixes ❌ Error The fix and unit regression test look correct, but the user-facing notification widget path lacks Selenium/browser coverage for the new data['url'] override. Add a Selenium test that creates a notification with url set and verifies the widget/toast link uses that URL, not just the serializer unit test.
General Rules ❌ Error NotificationSerializer trusts instance.data['url'] blindly, so javascript: URLs can reach the frontend; tests only cover happy path/fallback. Validate the override with http/https (or url_has_allowed_host_and_scheme) before setting target_url, and add a regression test for unsafe schemes.
Features ⚠️ Warning The referenced #436 resolves to a PR, not a validated issue; docs weren't updated, and the PR description has no screenshots for this UI-linked change. Add/confirm an accepted issue, update docs for the target_url override, and include screenshots or a screen recording for the widget behavior.
Changes ⚠️ Warning Docs weren’t updated for the new target_url semantics, the PR description has no screenshots for this UI-facing change, and the API change lacks a migration path. Update the websocket/API docs and changelog to describe the data.url override, add screenshots or a recording to the PR description, and document migration guidance for clients relying on the old target_url behavior.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly matches the PR’s main change and uses an allowed type prefix.
Description check ✅ Passed The description mostly follows the template with checklist, issue context, and change details; only the screenshot section is omitted.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/notification-url-web-link

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@kilo-code-bot

kilo-code-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

The incremental change since the last review hardens the web target_url handling. to_representation now validates the per-notification data["url"] via a new _is_safe_target_url helper: protocol-relative URLs (//...) are rejected and only empty (relative) or http/https schemes are honored, otherwise it falls back to the target object's admin URL. This resolves the previously raised concern about unsafe/javascript: schemes reaching the web link. The regression test in test_notifications.py covers all three paths (safe relative URL honored, unsafe scheme falls back, protocol-relative falls back), giving solid coverage for the new behavior.

No new critical bugs or security vulnerabilities were introduced by the incremental change.

Files Reviewed (2 files)
  • openwisp_notifications/api/serializers.py
  • openwisp_notifications/tests/test_notifications.py
Previous Review Summaries (3 snapshots, latest commit 3edff65)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 3edff65)

Status: No Issues Found | Recommendation: Merge

The incremental change since the last review is a trivial, correct refactor: the NotificationSerializer import in test_notifications.py was moved from inside test_target_url_data_override up to the module-level imports, and the redundant inline import was removed. Behavior is unchanged, the regression test still exercises the full serialized output (NotificationSerializer(n).data["target_url"]) for both the data["url"] override and the target-object fallback paths, and there are no new bugs or security concerns in the changed code.

Files Reviewed (1 file)
  • openwisp_notifications/tests/test_notifications.py

Previous review (commit 9c542f4)

Status: No Issues Found | Recommendation: Merge

The incremental changes since the last review cleanly address the earlier feedback. The serializer now overrides to_representation instead of using a SerializerMethodField/get_target_url, honoring data["url"] for the web target_url and falling back to the model target_url otherwise — behavior consistent with the existing email link. The regression test now exercises the full serialized output (NotificationSerializer(n).data["target_url"]) rather than calling the internal method directly, covering both the override and fallback paths.

The data["url"] value originates from trusted OpenWISP module code via notify.send() (not end-user input), and data["url"] was already used for the email link, so this introduces no new injection surface. The model target_url property is unchanged, so {target_link} templates still point at the target object.

Files Reviewed (2 files)
  • openwisp_notifications/api/serializers.py
  • openwisp_notifications/tests/test_notifications.py

Previous review (commit e7ef750)

Status: No Issues Found | Recommendation: Merge

This is a clean, focused change. The serializer now honors data["url"] for the web target link, mirroring the existing email-link behavior in utils.py (which already uses notification.data.get("url") with a fallback to the target object URL). The URL originates from trusted OpenWISP module code via notify.send(), so no new injection surface is introduced, and the model target_url property is left unchanged so {target_link} templates keep pointing at the target object. A regression-style test covers both the override path and the fallback path.

Files Reviewed (2 files)
  • openwisp_notifications/api/serializers.py
  • openwisp_notifications/tests/test_notifications.py

Reviewed by claude-opus-4.8 · Input: 26 · Output: 3.9K · Cached: 328.6K

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the notifications API serialization so the web notification link can honor a per-notification data["url"] override (matching existing email behavior), while keeping the model’s target_url property unchanged to preserve {target_link} template behavior.

Changes:

  • Add a serializer-level target_url field that returns data["url"] when present, otherwise falls back to the model’s target_url.
  • Add a regression test intended to verify the override vs fallback behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
openwisp_notifications/api/serializers.py Exposes target_url via SerializerMethodField so API clients can use data["url"] overrides for web links.
openwisp_notifications/tests/test_notifications.py Adds a test for the data["url"] override and fallback behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread openwisp_notifications/api/serializers.py Outdated
Comment thread openwisp_notifications/tests/test_notifications.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_notifications/api/serializers.py`:
- Line 45: Remove "target_url" from the extra_fields declaration, while keeping
the SerializerMethodField declaration unchanged so
super().get_field_names(declared_fields, info) supplies it automatically.
- Around line 47-50: Update get_target_url to validate obj.data["url"] before
returning it: retain the override only when its URL scheme is http or https, and
otherwise fall back to obj.target_url. Preserve the existing fallback when data
or url is absent.
🪄 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: 2727e63c-3fcd-4db0-99fa-e652053e81dd

📥 Commits

Reviewing files that changed from the base of the PR and between a2c3865 and e7ef750.

📒 Files selected for processing (2)
  • openwisp_notifications/api/serializers.py
  • openwisp_notifications/tests/test_notifications.py
📜 Review details
⏰ Context from checks skipped due to timeout. (18)
  • GitHub Check: CodeRabbit
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (3)
openwisp_notifications/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Mark user-facing strings as translatable with Django i18n helpers, typically gettext_lazy imported as _

Files:

  • openwisp_notifications/tests/test_notifications.py
  • openwisp_notifications/api/serializers.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Avoid unnecessary blank lines inside function and method bodies
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it

Files:

  • openwisp_notifications/tests/test_notifications.py
  • openwisp_notifications/api/serializers.py
**/*.{py,html,txt,md}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework.

Files:

  • openwisp_notifications/tests/test_notifications.py
  • openwisp_notifications/api/serializers.py
🧠 Learnings (1)
📚 Learning: 2026-05-07T16:17:11.806Z
Learnt from: pandafy
Repo: openwisp/openwisp-notifications PR: 450
File: openwisp_notifications/tests/test_selenium.py:276-285
Timestamp: 2026-05-07T16:17:11.806Z
Learning: In OpenWISP projects’ test code (e.g., openwisp-notifications), Celery is configured project-wide to run tasks eagerly/synchronously during tests (e.g., `CELERY_TASK_ALWAYS_EAGER=True` or an equivalent setting). Therefore, when reviewing these tests, don’t flag “race condition” or “async Celery” concerns for code that assumes task completion—tasks are guaranteed to finish before execution proceeds to the next line of test code.

Applied to files:

  • openwisp_notifications/tests/test_notifications.py
🔇 Additional comments (1)
openwisp_notifications/tests/test_notifications.py (1)

774-794: LGTM!

class Meta:
model = Notification
exclude = ["description", "deleted", "public"]
extra_fields = ["message", "email_subject", "target_url"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant target_url from extra_fields.

Now that target_url is declared as a SerializerMethodField, super().get_field_names(declared_fields, info) already includes it. Keeping it in extra_fields causes a duplicate entry in the field names list — harmless but unnecessary.

♻️ Proposed cleanup
-        extra_fields = ["message", "email_subject", "target_url"]
+        extra_fields = ["message", "email_subject"]
📝 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
extra_fields = ["message", "email_subject", "target_url"]
extra_fields = ["message", "email_subject"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_notifications/api/serializers.py` at line 45, Remove "target_url"
from the extra_fields declaration, while keeping the SerializerMethodField
declaration unchanged so super().get_field_names(declared_fields, info) supplies
it automatically.

Comment thread openwisp_notifications/api/serializers.py Outdated
@Eeshu-Yadav
Eeshu-Yadav force-pushed the feature/notification-url-web-link branch from e7ef750 to 9c542f4 Compare July 11, 2026 16:37
@Eeshu-Yadav
Eeshu-Yadav changed the base branch from master to gsoc26-persistent-scheduled-upgrades July 11, 2026 16:38
@Eeshu-Yadav
Eeshu-Yadav force-pushed the feature/notification-url-web-link branch from 9c542f4 to 3edff65 Compare July 11, 2026 16:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_notifications/api/serializers.py`:
- Around line 50-54: Update to_representation to validate instance.data["url"]
before assigning it to target_url: only accept URLs with http or https schemes,
and preserve the serialized base target_url for all other schemes, including
javascript and malformed values.
🪄 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: 55868b75-5863-49c2-9c1b-4516ecad7959

📥 Commits

Reviewing files that changed from the base of the PR and between e7ef750 and 9c542f4.

📒 Files selected for processing (2)
  • openwisp_notifications/api/serializers.py
  • openwisp_notifications/tests/test_notifications.py
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: CodeRabbit
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (3)
openwisp_notifications/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Mark user-facing strings as translatable with Django i18n helpers, typically gettext_lazy imported as _

Files:

  • openwisp_notifications/tests/test_notifications.py
  • openwisp_notifications/api/serializers.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Avoid unnecessary blank lines inside function and method bodies
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it

Files:

  • openwisp_notifications/tests/test_notifications.py
  • openwisp_notifications/api/serializers.py
**/*.{py,html,txt,md}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework.

Files:

  • openwisp_notifications/tests/test_notifications.py
  • openwisp_notifications/api/serializers.py
🧠 Learnings (1)
📚 Learning: 2026-05-07T16:17:11.806Z
Learnt from: pandafy
Repo: openwisp/openwisp-notifications PR: 450
File: openwisp_notifications/tests/test_selenium.py:276-285
Timestamp: 2026-05-07T16:17:11.806Z
Learning: In OpenWISP projects’ test code (e.g., openwisp-notifications), Celery is configured project-wide to run tasks eagerly/synchronously during tests (e.g., `CELERY_TASK_ALWAYS_EAGER=True` or an equivalent setting). Therefore, when reviewing these tests, don’t flag “race condition” or “async Celery” concerns for code that assumes task completion—tasks are guaranteed to finish before execution proceeds to the next line of test code.

Applied to files:

  • openwisp_notifications/tests/test_notifications.py
🔇 Additional comments (2)
openwisp_notifications/tests/test_notifications.py (1)

774-793: LGTM!

openwisp_notifications/api/serializers.py (1)

50-54: 🎯 Functional Correctness

NotificationListSerializer already inherits the override

NotificationListSerializer(NotificationSerializer) already picks up to_representation, so list responses also include data["url"] in target_url.

			> Likely an incorrect or invalid review comment.

Comment on lines +50 to +54
def to_representation(self, instance):
data = super().to_representation(instance)
if instance.data and instance.data.get("url"):
data["target_url"] = instance.data["url"]
return data

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject non-http(s) url overrides in to_representation.

instance.data["url"] is returned as target_url without validating the URL scheme. A crafted javascript: URL in notification data would be passed to the frontend and could execute on click. Only allow http/https schemes and fall back to the base target_url otherwise.

🛡️ Proposed fix: validate URL scheme before overriding
    def to_representation(self, instance):
        data = super().to_representation(instance)
-        if instance.data and instance.data.get("url"):
-            data["target_url"] = instance.data["url"]
+        if instance.data:
+            url = instance.data.get("url")
+            if url and url.startswith(("http://", "https://")):
+                data["target_url"] = url
        return data
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_notifications/api/serializers.py` around lines 50 - 54, Update
to_representation to validate instance.data["url"] before assigning it to
target_url: only accept URLs with http or https schemes, and preserve the
serialized base target_url for all other schemes, including javascript and
malformed values.

Eeshu-Yadav added a commit to openwisp/openwisp-firmware-upgrader that referenced this pull request Jul 11, 2026
…tegration

The persistent mass-upgrade reminder relies on openwisp-notifications
honoring the per-notification "url" for the web notification link (so the
"Open" button lands on the batch filtered to its pending devices). That
support lands in openwisp/openwisp-notifications#490; pin the dependency to
that commit so the integration is reproducible until it is released.
Eeshu-Yadav added a commit to openwisp/openwisp-firmware-upgrader that referenced this pull request Jul 11, 2026
…tegration

The persistent mass-upgrade reminder relies on openwisp-notifications
honoring the per-notification "url" for the web notification link, so the
"Open" button lands on the batch filtered to its pending devices. That
support lands in openwisp/openwisp-notifications#490; pin the dependency to
that commit so the integration is reproducible until it is released.
Eeshu-Yadav added a commit to openwisp/openwisp-firmware-upgrader that referenced this pull request Jul 11, 2026
…tegration

The persistent mass-upgrade reminder relies on openwisp-notifications
honoring the per-notification "url" for the web notification link, so the
"Open" button lands on the batch filtered to its pending devices. That
support lands in openwisp/openwisp-notifications#490; pin the dependency to
that commit so the integration is reproducible until it is released.
The web notification widget links to the serialized target_url. Return
data["url"] when it is set so a per-notification link (for example a
generic_message sent with url=...) points the notification at a specific
page, consistent with the email link which already uses data["url"]. The
model target_url is left unchanged so message templates that render
{target_link} keep pointing at the target object.

The override is restricted to relative paths and http(s) URLs; the widget
assigns target_url to window.location, so a javascript: scheme would
otherwise execute on click.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants