Skip to content

Conversation

@atif09
Copy link
Contributor

@atif09 atif09 commented Jan 2, 2026

The password reset and related email templates expect current_site.name to be available as site_name in translated templates. Add current_site to the email template context so templates can render the ssite name again.

Fixes #482

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

Closes #482

Description of Changes

  • Inject current_site into the email template context when missing by obtaining it from get_current-site(self.request), which is guarded with a conservative try/except so the adapter remains safe when a request/site cannot be determined.
  • Added a unit test that asserts the site name appears in the passsword-reset email.

Screenshot

BEFORE

(example given in the issue description)

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [example.com] Unknown Account
From: webmaster@localhost
To: [email protected]
Date: Tue, 30 Dec 2025 17:48:26 -0000
Message-ID: <176711690666.189643.12815373774729762444@envy>

Welcome to !

You are receiving this email because you, or someone else, tried to access an account with email . However, we do not have any record of such an account in our database.

This mail can be safely ignored if you did not initiate this action.

If it was you, you can sign up for an account using the link below.

http://localhost:8000/accounts/signup/

Thank you for using !

AFTER

(output in the terminal)

image

atif09 and others added 2 commits January 2, 2026 21:19
…#482

Ensure password-reset email templates have access to current_site so template variables like site_name render correctly. Inject current_site into the email context when missing by calling get_current_site(self.request) if a request is available; use a conservative try/except to avoid raising if the site cannot be determined.

Adds a unit test (openwisp_users/tests/test_adapter.py) that asserts the site name appears in password reset emails.

Fixes openwisp#482
@coveralls
Copy link

coveralls commented Jan 2, 2026

Coverage Status

coverage: 97.894% (+0.003%) from 97.891%
when pulling 8a54a35 on atif09:issues/482-password-reset-not-displaying-site-name
into f82177d on openwisp:master.

if getattr(self, "request", None):
context["current_site"] = get_current_site(self.request)
except Exception:
pass
Copy link
Member

@nemesifier nemesifier Jan 9, 2026

Choose a reason for hiding this comment

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

This entire block looks quite clunky, I'll let @coderabbitai review it.

Copy link

Choose a reason for hiding this comment

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

@nemesifier, I agree that this block could be improved. Here are the main issues:

  1. Overly broad exception handling: The bare except Exception: pass silently catches all exceptions, which can hide real problems and make debugging difficult.

  2. Unnecessary nesting: The try-except wrapping a conditional check creates awkward nesting that reduces readability.

  3. Silent failures: Errors are completely ignored, which might mask configuration issues or real problems with the request object.

Here's a cleaner approach:

if "current_site" not in context:
    request = getattr(self, "request", None)
    if request is not None:
        try:
            context["current_site"] = get_current_site(request)
        except Exception as e:
            # Log the error for debugging purposes
            # Optionally: logger.warning(f"Failed to get current site: {e}")
            pass

Or even more concise if you want to handle specific exceptions:

if "current_site" not in context:
    request = getattr(self, "request", None)
    if request:
        try:
            context["current_site"] = get_current_site(request)
        except (AttributeError, RuntimeError):
            # Expected exceptions when site framework is not properly configured
            pass

Benefits:

  • Flatter structure with better readability
  • Request is assigned once and reused
  • Optional: More specific exception handling or logging for better debugging
  • The logic flow is clearer

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we could just simplify this to:

    try:
        context["current_site"] = get_current_site(self.request)
    except AttributeError:
        pass

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

Django get_current_site function exceptions and behavior with None request

💡 Result:

Short answer

  • django.contrib.sites.shortcuts.get_current_site(request) simply forwards to Site.objects.get_current(request) if the sites app is installed; otherwise it returns a RequestSite(request). (django.readthedocs.io)
  • If SITE_ID is set in settings, get_current_site returns that Site. If SITE_ID is not set and you pass a request it looks up the Site by request.get_host() (tries host with and without port). If you pass request=None and SITE_ID is not set, Site.objects.get_current raises ImproperlyConfigured. If the domain lookup fails a Site.DoesNotExist will be raised. (docs.djangoproject.com)

Practical implications / how to avoid exceptions

  • Ensure django.contrib.sites is in INSTALLED_APPS and set SITE_ID to a valid site id (safe server-side usage without a request). (docs.djangoproject.com)
  • When SITE_ID is not used, always pass a valid HttpRequest (or use CurrentSiteMiddleware so request.site is available) so lookups use request.get_host(). (docs.djangoproject.com)
  • Catch Site.DoesNotExist or ImproperlyConfigured if you must call get_current_site in contexts where neither SITE_ID nor a request can be guaranteed. (docs.djangoproject.com)

References

  • django.contrib.sites.shortcuts.get_current_site implementation and docs. [1]. (django.readthedocs.io)
  • django.contrib.sites.models.SiteManager.get_current behavior (raises ImproperlyConfigured when no SITE_ID and no request; looks up by request.get_host(); cached lookup). [2]. (docs.djangoproject.com)
  • Sites framework docs describing RequestSite fallback and host/port lookup behavior. [3]. (docs.djangoproject.com)

Citations:


@nemesifier, that simplification would be risky because it doesn't handle all the failure modes of get_current_site:

  1. AttributeError only catches missing self.request attribute - but if self.request exists and is None, calling get_current_site(None) raises ImproperlyConfigured (when SITE_ID isn't set), not AttributeError

  2. Site lookup failures - If the domain lookup fails, get_current_site raises Site.DoesNotExist, which also wouldn't be caught

  3. Overwrites existing values - Removing the "current_site" not in context check means you'd overwrite any current_site that was explicitly passed in the context

A better middle ground would be:

if "current_site" not in context:
    request = getattr(self, "request", None)
    if request is not None:
        try:
            context["current_site"] = get_current_site(request)
        except (AttributeError, ImproperlyConfigured, Site.DoesNotExist):
            pass

This:

  • Preserves the context check to avoid overwriting
  • Extracts request once for clarity
  • Catches the specific exceptions that get_current_site can raise
  • Still fails gracefully when site framework isn't properly configured

You'd also need to add the import: from django.core.exceptions import ImproperlyConfigured and from django.contrib.sites.models import Site

@nemesifier
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Walkthrough

EmailAdapter.send_mail now ensures the email rendering context includes current_site when it is missing: if self.request is available, it calls get_current_site(self.request) and sets context["current_site"] before rendering the subject (errors suppressed). No public API or method signatures were changed. A test test_password_reset_includes_site_name was added to verify the password reset email body (plain text and HTML alternative) contains the site name (e.g., "example.com").

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the main fix: ensuring site_name displays in password reset emails, which is the primary objective of the changeset.
Description check ✅ Passed The description follows the template with all required sections completed: checklist items (3/4 checked with documentation noted as not updated), reference to issue #482, detailed description of changes, and before/after screenshots demonstrating the fix.
Linked Issues check ✅ Passed The code changes fully address issue #482 requirements: context enrichment with current_site for email template rendering [#482], and a new test validating site_name appears in password reset emails [#482].
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #482: importing get_current_site, enriching email context with current_site, and adding a test for site_name display in password reset emails.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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: 0

🧹 Nitpick comments (1)
openwisp_users/accounts/adapter.py (1)

12-17: Consider adding logging to the exception handler.

The logic correctly injects current_site into the context when missing, and the conservative try/except ensures email sending remains robust. However, silently suppressing all exceptions makes it difficult to diagnose when and why site resolution fails.

📝 Add logging for better observability
+import logging
+
+logger = logging.getLogger(__name__)
+
 class EmailAdapter(DefaultAccountAdapter):
     def send_mail(self, template_prefix, email, context):
         if "current_site" not in context:
             try:
                 if getattr(self, "request", None):
                     context["current_site"] = get_current_site(self.request)
             except Exception:
+                logger.debug(
+                    "Could not resolve current_site for email %s", template_prefix
+                )
                 pass

Alternatively, narrow the exception types if you want to be more specific:

-            except Exception:
+            except (RuntimeError, AttributeError):
                 pass

Based on static analysis hints (Ruff BLE001, S110).

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f82177d and d0c9dfb.

📒 Files selected for processing (2)
  • openwisp_users/accounts/adapter.py
  • openwisp_users/tests/test_adapter.py
🧰 Additional context used
🧬 Code graph analysis (1)
openwisp_users/tests/test_adapter.py (3)
openwisp_users/base/models.py (1)
  • _create_user (23-30)
openwisp_users/tests/utils.py (1)
  • _create_user (33-45)
openwisp_users/api/views.py (1)
  • post (66-67)
🪛 Ruff (0.14.10)
openwisp_users/accounts/adapter.py

16-17: try-except-pass detected, consider logging the exception

(S110)


16-16: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (2)
openwisp_users/tests/test_adapter.py (1)

47-58: LGTM! Test effectively validates the site name injection.

The test correctly verifies that the password reset email includes the site name ("example.com") in both plain text and HTML versions. The logic is sound and directly exercises the scenario that issue #482 describes.

openwisp_users/accounts/adapter.py (1)

2-2: LGTM!

The import of get_current_site is appropriate for resolving the current site from the request.

@nemesifier nemesifier added the bug label Jan 9, 2026
@nemesifier nemesifier changed the title Ensure site_name displays in password reset email [fix] Ensure site_name displays in password reset email Jan 9, 2026
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: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0c9dfb and 4b9dc84.

📒 Files selected for processing (1)
  • openwisp_users/accounts/adapter.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.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.0.0
  • GitHub Check: Python==3.12 | django~=5.0.0
🔇 Additional comments (1)
openwisp_users/accounts/adapter.py (1)

2-2: LGTM: Standard Django import.

The addition of get_current_site import is appropriate for obtaining the current site information.

Comment on lines 12 to 15
try:
context["current_site"] = get_current_site(self.request)
except AttributeError:
pass
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

Check if current_site already exists before overwriting.

The PR description states the intent is to inject current_site "when missing," but the implementation unconditionally overwrites context["current_site"] without checking if it already exists. This could override values intentionally set by the caller.

Additionally, consider the following improvements:

  1. Broader exception handling: get_current_site can raise exceptions beyond AttributeError (e.g., ImproperlyConfigured if the sites framework is misconfigured).
  2. Observability: Silent failure with pass makes debugging difficult—consider logging when the site cannot be determined.
🔧 Proposed fix with conditional check and improved error handling
+import logging
+
+logger = logging.getLogger(__name__)
+
 class EmailAdapter(DefaultAccountAdapter):
     def send_mail(self, template_prefix, email, context):
-        try:
-            context["current_site"] = get_current_site(self.request)
-        except AttributeError:
-            pass
+        if "current_site" not in context:
+            try:
+                context["current_site"] = get_current_site(self.request)
+            except (AttributeError, Exception) as e:
+                logger.debug(
+                    "Could not determine current_site for email context: %s", e
+                )
         subject = render_to_string("{0}_subject.txt".format(template_prefix), context)

Committable suggestion skipped: line range outside the PR's diff.

@nemesifier nemesifier force-pushed the issues/482-password-reset-not-displaying-site-name branch from 4b9dc84 to 8a54a35 Compare January 9, 2026 17:11
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: 1

🤖 Fix all issues with AI agents
In @openwisp_users/accounts/adapter.py:
- Around line 12-13: The code in the adapter method sets context["current_site"]
using get_current_site(self.request) without error handling and uses
hasattr(self, "request") which doesn't ensure the request is not None; wrap that
logic in a conservative try/except that first checks self.request is not None
(e.g., if hasattr(self, "request") and self.request is not None) and then
attempts context["current_site"] = get_current_site(self.request) inside a try
block, catching Exception (or Site.DoesNotExist/ImproperlyConfigured plus a
broad fallback) and silently skipping setting current_site on failure so
password reset flow continues; reference the adapter method that contains the
existing context["current_site"] assignment to locate where to add the
try/except.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b9dc84 and 8a54a35.

📒 Files selected for processing (1)
  • openwisp_users/accounts/adapter.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.0.0
🔇 Additional comments (1)
openwisp_users/accounts/adapter.py (1)

2-2: LGTM! Standard Django import.

The import is correct and necessary for obtaining the current site instance.

Comment on lines +12 to +13
if "current_site" not in context and hasattr(self, "request"):
context["current_site"] = get_current_site(self.request)
Copy link

@coderabbitai coderabbitai bot Jan 9, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add exception handling as described in the PR description.

The PR description mentions the logic is "wrapped in a conservative try/except to remain safe when a request or site cannot be determined," but the implementation lacks this error handling. Without it, get_current_site() can raise exceptions (e.g., Site.DoesNotExist, ImproperlyConfigured, database errors) that would prevent password reset emails from being sent entirely.

Additionally, consider verifying self.request is not None, as hasattr only checks attribute existence.

🛡️ Proposed fix with exception handling
-        if "current_site" not in context and hasattr(self, "request"):
-            context["current_site"] = get_current_site(self.request)
+        if "current_site" not in context and hasattr(self, "request") and self.request:
+            try:
+                context["current_site"] = get_current_site(self.request)
+            except Exception:
+                # Silently skip if site cannot be determined
+                pass
📝 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
if "current_site" not in context and hasattr(self, "request"):
context["current_site"] = get_current_site(self.request)
if "current_site" not in context and hasattr(self, "request") and self.request:
try:
context["current_site"] = get_current_site(self.request)
except Exception:
# Silently skip if site cannot be determined
pass
🤖 Prompt for AI Agents
In @openwisp_users/accounts/adapter.py around lines 12 - 13, The code in the
adapter method sets context["current_site"] using get_current_site(self.request)
without error handling and uses hasattr(self, "request") which doesn't ensure
the request is not None; wrap that logic in a conservative try/except that first
checks self.request is not None (e.g., if hasattr(self, "request") and
self.request is not None) and then attempts context["current_site"] =
get_current_site(self.request) inside a try block, catching Exception (or
Site.DoesNotExist/ImproperlyConfigured plus a broad fallback) and silently
skipping setting current_site on failure so password reset flow continues;
reference the adapter method that contains the existing context["current_site"]
assignment to locate where to add the try/except.

Copy link
Member

Choose a reason for hiding this comment

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

If Site.DoesNotExist, ImproperlyConfigured are raised, they would be raised much sooner during the application life cycle and this code would not even execute, so we can avoid needless redundancy.

Copy link

Choose a reason for hiding this comment

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

@nemesifier, thank you for the clarification! You're right that Site.DoesNotExist and ImproperlyConfigured would surface during Django's initialization phase rather than at email-sending time, making the exception handling unnecessary here. I appreciate the insight into the application lifecycle behavior.

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

Thanks @atif09

@github-project-automation github-project-automation bot moved this from To do (general) to In progress in OpenWISP Contributor's Board Jan 9, 2026
@nemesifier nemesifier merged commit acc2652 into openwisp:master Jan 9, 2026
16 checks passed
@github-project-automation github-project-automation bot moved this from In progress to Done in OpenWISP Contributor's Board Jan 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Development

Successfully merging this pull request may close these issues.

[bug] Password reset email not displaying site_name anymore

3 participants