Skip to content

[monitoring/js] Consistent datetime format in RADIUS sessions tab #662#665

Open
Eeshu-Yadav wants to merge 2 commits intoopenwisp:masterfrom
Eeshu-Yadav:issues/662-radius_sessions_issue
Open

[monitoring/js] Consistent datetime format in RADIUS sessions tab #662#665
Eeshu-Yadav wants to merge 2 commits intoopenwisp:masterfrom
Eeshu-Yadav:issues/662-radius_sessions_issue

Conversation

@Eeshu-Yadav
Copy link
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.

Use Django locale for date formatting in device page RADIUS sessions tab JS, matching Django admin and localization settings.

Fixes #662

@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/662-radius_sessions_issue branch 2 times, most recently from aa60f50 to 830d07e Compare December 26, 2025 10:31
Copy link
Member

@pandafy pandafy left a comment

Choose a reason for hiding this comment

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

I don't think it is working as expected.

The created/modified timestamp of the Device is in different format from the "Start Time" of the RADIUS session

Check the formatting of "Created" and "Modified" datetime of the Device object:
Image

Check the formatting of "Start time" field in the "RADIUS Sessions" tab:

Image

Check if something like this will solve the problem:

https://github.com/openwisp/openwisp-controller/blob/115cd9ac1b418204bdb704c02af308569e19e3fd/openwisp_controller/connection/static/connection/js/commands.js#L631-L638

@coveralls
Copy link

coveralls commented Dec 30, 2025

Coverage Status

coverage: 88.054% (-0.09%) from 88.147%
when pulling dbd1b63 on Eeshu-Yadav:issues/662-radius_sessions_issue
into 43361fd on openwisp:master.

@stktyagi
Copy link
Member

stktyagi commented Dec 30, 2025

@stktyagi i am working on that as suggested by @pandafy ,

Hey @Eeshu-Yadav, sorry for any confusion. I didn’t start working on this. Since the CI failure was fixed, I just updated the branch earlier to keep it in sync and just as a check.

@Eeshu-Yadav
Copy link
Contributor Author

@stktyagi i am working on that as suggested by @pandafy ,

Hey @Eeshu-Yadav, sorry for any confusion. I didn’t start working on this. Since the CI failure was fixed, I just updated the branch earlier to keep it in sync and just as a check.

i though first , yaa ci failed previously , i thought , i will fix in next commit

@nemesifier
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 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 16, 2026

📝 Walkthrough

Walkthrough

Adds a monitoring accounting API for RadiusAccounting exposed at GET /api/radius/monitoring/sessions/. Introduces MonitoringRadiusAccountingSerializer that returns localized start_time/stop_time, a MonitoringAccountingView (ListAPIView with filtering and pagination), and conditional exposure of the view in api/views and api/urls only when the integration is available. Updates the admin monitoring template to point to the new monitoring_accounting endpoint and removes client-side datetime reformatting so the frontend uses server-localized start_time/stop_time. Tests add a local MonitoringAccountingView alias.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant AdminJS as Admin JS
    participant API as MonitoringAccountingView (Server)
    participant DB as RadiusAccounting (Database)

    Browser->>AdminJS: Open device RADIUS sessions tab
    AdminJS->>API: GET /api/radius/monitoring/sessions/?filters
    API->>DB: Query RadiusAccounting (apply filters, order by start_time desc)
    DB-->>API: Return records
    API-->>AdminJS: JSON list (serialized localized start_time/stop_time)
    AdminJS->>Browser: Render table (use start_time/stop_time as-is)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description is incomplete. It unchecks both 'written new test cases' and 'updated documentation' without explanation, and lacks a detailed description of changes and screenshots mentioned in the template. Add a detailed description of changes and explain why tests/documentation were not updated. Include relevant screenshots if available.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly references issue #662 and accurately describes the main change: making RADIUS sessions tab datetime formatting consistent using Django locale.
Linked Issues check ✅ Passed Code changes comprehensively address issue #662 objectives: MonitoringRadiusAccountingSerializer provides Django-localized datetime formatting [integrations/monitoring/serializers.py], MonitoringAccountingView exposes the formatted data [integrations/monitoring/views.py], and frontend JS removal of custom formatting [device-change.js] allows server-side Django localization to be used instead.
Out of Scope Changes check ✅ Passed Changes in api/urls.py and api/views.py that conditionally expose the monitoring endpoint, combined with test fixtures, are directly necessary to make the monitoring accounting serializer and view available as required by issue #662. All changes are in-scope.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🧹 Recent nitpick comments
openwisp_radius/integrations/monitoring/serializers.py (1)

3-5: Consider using the local load_model wrapper for consistency.

The codebase has a local wrapper in openwisp_radius/utils.py that simplifies the call:

from openwisp_radius.utils import load_model

This would allow the model loading to be simplified to:

RadiusAccounting = load_model("RadiusAccounting")

While the current approach is functionally correct, using the local wrapper maintains consistency with the rest of the codebase.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b189d0d and dbd1b63.

📒 Files selected for processing (8)
  • openwisp_radius/api/urls.py
  • openwisp_radius/api/views.py
  • openwisp_radius/integrations/monitoring/admin.py
  • openwisp_radius/integrations/monitoring/serializers.py
  • openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js
  • openwisp_radius/integrations/monitoring/templates/admin/config/radius-monitoring/device/change_form.html
  • openwisp_radius/integrations/monitoring/views.py
  • tests/openwisp2/sample_radius/api/views.py
💤 Files with no reviewable changes (2)
  • openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js
  • openwisp_radius/integrations/monitoring/templates/admin/config/radius-monitoring/device/change_form.html
🚧 Files skipped from review as they are similar to previous changes (4)
  • openwisp_radius/integrations/monitoring/views.py
  • openwisp_radius/integrations/monitoring/admin.py
  • openwisp_radius/api/views.py
  • tests/openwisp2/sample_radius/api/views.py
🧰 Additional context used
🧬 Code graph analysis (1)
openwisp_radius/integrations/monitoring/serializers.py (2)
openwisp_radius/utils.py (1)
  • load_model (33-34)
openwisp_radius/api/views.py (1)
  • Meta (411-414)
🪛 Ruff (0.14.14)
openwisp_radius/integrations/monitoring/serializers.py

[warning] 39-49: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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). (11)
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | 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.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
🔇 Additional comments (3)
openwisp_radius/api/urls.py (1)

92-100: LGTM!

The conditional URL registration pattern is well-implemented. The is not None check properly guards against the view being unavailable when the monitoring integration isn't installed, and the endpoint naming follows the existing convention (monitoring_accounting_list mirrors radius_accounting_list).

openwisp_radius/integrations/monitoring/serializers.py (2)

18-27: Well-implemented datetime formatting with proper timezone handling.

The _format_datetime helper correctly:

  1. Handles None values gracefully
  2. Checks timezone awareness before calling localtime() to avoid ValueError on naive datetimes
  3. Applies Django's localization settings for consistent formatting

This addresses the concern from the previous review about naive datetime handling.


37-50: LGTM!

The Meta class configuration is clean and appropriate. Setting read_only_fields = fields is an elegant way to mark all fields as read-only for this monitoring serializer.

Note: The static analysis hint (RUF012) about ClassVar annotations is a false positive here—DRF Meta class attributes are framework conventions and not subject to typical mutable class attribute concerns.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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

🤖 Fix all issues with AI agents
In
`@openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js`:
- Around line 23-31: The options object in device-change.js contains hour12:
false which forces 24-hour time; remove the hour12 property from the options
constant so toLocaleString() can honor the user's locale preferences (locate and
edit the options declaration in device-change.js where the options object with
year/month/day/hour/minute/second is defined and delete the hour12: false
entry).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4053a46 and e44b632.

📒 Files selected for processing (1)
  • openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js
🔇 Additional comments (1)
openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js (1)

22-22: No guard needed; djangoLocale is always defined.

The template explicitly sets const djangoLocale = "{{ LANGUAGE_CODE }}" (line 39 of change_form.html) before this script loads, so djangoLocale is guaranteed to exist. A guard is unnecessary.

The concern about replace("_", "-") replacing only the first underscore is technically valid but low-impact: Django's LANGUAGE_CODE setting uses hyphens by convention (e.g., "en-gb"), and Intl.DateTimeFormat gracefully falls back if an invalid locale string is provided.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copilot AI review requested due to automatic review settings January 30, 2026 10:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request fixes inconsistent datetime formatting in the RADIUS sessions tab on the device page by moving datetime formatting from client-side JavaScript to server-side Django using the locale-aware formats.date_format() function.

Changes:

  • Created new MonitoringRadiusAccountingSerializer that formats datetime fields server-side using Django's localization settings
  • Created new MonitoringAccountingView to serve the formatted data
  • Updated admin template to use new API endpoint and removed client-side datetime formatting from JavaScript
  • Registered new URL pattern for the monitoring accounting endpoint

Reviewed changes

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

Show a summary per file
File Description
openwisp_radius/integrations/monitoring/serializers.py New serializer with server-side datetime formatting using Django's DATETIME_FORMAT
openwisp_radius/integrations/monitoring/views.py New API view for monitoring integration with formatted datetime output
openwisp_radius/integrations/monitoring/admin.py Updated to point to new monitoring_accounting_list endpoint
openwisp_radius/integrations/monitoring/templates/admin/config/radius-monitoring/device/change_form.html Removed djangoLocale variable (no longer needed)
openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js Removed client-side datetime formatting function
openwisp_radius/api/views.py Imported and registered MonitoringAccountingView
openwisp_radius/api/urls.py Added new URL pattern for monitoring/sessions endpoint
tests/openwisp2/sample_radius/api/views.py Extended MonitoringAccountingView in test app

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

Comment on lines +1 to +29
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.generics import ListAPIView
from swapper import load_model

from openwisp_radius.api.freeradius_views import (
AccountingFilter,
AccountingViewPagination,
)
from openwisp_users.api.mixins import FilterByOrganizationManaged, ProtectedAPIMixin

from .serializers import MonitoringRadiusAccountingSerializer

RadiusAccounting = load_model("openwisp_radius", "RadiusAccounting")


class MonitoringAccountingView(
ProtectedAPIMixin, FilterByOrganizationManaged, ListAPIView
):
"""
API view for RADIUS accounting in monitoring integration.
Uses server-side datetime formatting for consistency with Django admin.
"""

throttle_scope = "radius_accounting_list"
serializer_class = MonitoringRadiusAccountingSerializer
pagination_class = AccountingViewPagination
filter_backends = (DjangoFilterBackend,)
filterset_class = AccountingFilter
queryset = RadiusAccounting.objects.all().order_by("-start_time")
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The new MonitoringAccountingView lacks test coverage. The repository has comprehensive API test coverage (see openwisp_radius/tests/test_api/ directory), and similar views like RadiusAccountingView have dedicated tests (test_radius_accounting in test_api.py). Consider adding tests that verify:

  1. The datetime formatting is working correctly with Django's localization
  2. The view properly filters by organization
  3. The view handles authentication/authorization correctly
  4. The formatted datetime strings match the expected Django admin format

Copilot uses AI. Check for mistakes.
Comment on lines 1 to 45
from django.utils import formats, timezone
from rest_framework import serializers
from swapper import load_model

RadiusAccounting = load_model("openwisp_radius", "RadiusAccounting")


class MonitoringRadiusAccountingSerializer(serializers.ModelSerializer):
"""
Read-only serializer for RADIUS accounting in monitoring integration
that formats datetime fields server-side using Django's localization
for consistency with Django admin datetime formatting.
"""

start_time = serializers.SerializerMethodField()
stop_time = serializers.SerializerMethodField()

def get_start_time(self, obj):
"""Format start_time using Django's localization settings"""
if not obj.start_time:
return None
local_time = timezone.localtime(obj.start_time)
return formats.date_format(local_time, "DATETIME_FORMAT")

def get_stop_time(self, obj):
"""Format stop_time using Django's localization settings"""
if not obj.stop_time:
return None
local_time = timezone.localtime(obj.stop_time)
return formats.date_format(local_time, "DATETIME_FORMAT")

class Meta:
model = RadiusAccounting
fields = [
"session_id",
"unique_id",
"username",
"input_octets",
"output_octets",
"calling_station_id",
"called_station_id",
"start_time",
"stop_time",
]
read_only_fields = fields
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The new MonitoringRadiusAccountingSerializer lacks test coverage. The repository has comprehensive API test coverage, and serializers are typically tested alongside their views. Consider adding tests that verify:

  1. The start_time and stop_time fields are correctly formatted using Django's DATETIME_FORMAT
  2. The formatting works correctly with different timezone settings
  3. None values are handled correctly for start_time and stop_time
  4. The serialized output contains all expected fields

Copilot uses AI. Check for mistakes.
SmsAttemptCooldownException,
UserAlreadyVerified,
)
from ..integrations.monitoring.views import MonitoringAccountingView
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

This import will cause an ImportError when the monitoring integration is not installed, since the integration is optional (see docs/user/radius_monitoring.rst). The import should be conditional or the view should be defined within the monitoring integration's own URL configuration. Consider either:

  1. Moving the monitoring_accounting view definition to openwisp_radius/integrations/monitoring/api/views.py and registering it there
  2. Using a conditional import like:
try:
    from ..integrations.monitoring.views import MonitoringAccountingView
    monitoring_accounting = MonitoringAccountingView.as_view()
except ImportError:
    monitoring_accounting = None

And conditionally adding the URL only when monitoring_accounting is not None.

Suggested change
from ..integrations.monitoring.views import MonitoringAccountingView
try:
from ..integrations.monitoring.views import MonitoringAccountingView
monitoring_accounting = MonitoringAccountingView.as_view()
except ImportError:
MonitoringAccountingView = None
monitoring_accounting = None

Copilot uses AI. Check for mistakes.
Comment on lines 91 to 95
path(
"radius/monitoring/sessions/",
api_views.monitoring_accounting,
name="monitoring_accounting_list",
),
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

This URL registration should be conditional on whether the monitoring integration is installed. When api_views.monitoring_accounting is None or doesn't exist (in installations without the monitoring integration), this will cause an error. Consider checking if the view exists before adding it to the URL patterns, or handle this in the monitoring integration's own URL configuration.

Copilot uses AI. Check for mistakes.
…nwisp#662

Use Django locale for date formatting in device page RADIUS sessions tab JS, matching Django admin and localization settings.

Fixes openwisp#662
@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/662-radius_sessions_issue branch from 18f2767 to b189d0d Compare January 30, 2026 10:16
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_radius/integrations/monitoring/serializers.py`:
- Around line 18-30: The serializer currently passes datetimes directly to
timezone.localtime() in get_start_time and get_stop_time, which will raise
ValueError for naive datetimes; update both methods to first check
timezone.is_aware(obj.start_time/obj.stop_time) and, if naive, call
timezone.make_aware(obj.start_time/obj.stop_time,
timezone.get_default_timezone()) before calling timezone.localtime(), then
format with formats.date_format as before so external/legacy naive RADIUS
timestamps are handled safely.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18f2767 and b189d0d.

📒 Files selected for processing (8)
  • openwisp_radius/api/urls.py
  • openwisp_radius/api/views.py
  • openwisp_radius/integrations/monitoring/admin.py
  • openwisp_radius/integrations/monitoring/serializers.py
  • openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js
  • openwisp_radius/integrations/monitoring/templates/admin/config/radius-monitoring/device/change_form.html
  • openwisp_radius/integrations/monitoring/views.py
  • tests/openwisp2/sample_radius/api/views.py
💤 Files with no reviewable changes (2)
  • openwisp_radius/integrations/monitoring/static/radius-monitoring/js/device-change.js
  • openwisp_radius/integrations/monitoring/templates/admin/config/radius-monitoring/device/change_form.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/openwisp2/sample_radius/api/views.py
  • openwisp_radius/integrations/monitoring/admin.py
🧰 Additional context used
🧬 Code graph analysis (3)
openwisp_radius/api/views.py (2)
openwisp_radius/integrations/monitoring/views.py (1)
  • MonitoringAccountingView (16-29)
tests/openwisp2/sample_radius/api/views.py (1)
  • MonitoringAccountingView (104-105)
openwisp_radius/integrations/monitoring/views.py (4)
openwisp_radius/utils.py (1)
  • load_model (33-34)
openwisp_radius/api/freeradius_views.py (2)
  • AccountingFilter (67-83)
  • AccountingViewPagination (466-469)
openwisp_radius/integrations/monitoring/serializers.py (1)
  • MonitoringRadiusAccountingSerializer (8-45)
tests/openwisp2/sample_radius/api/views.py (1)
  • MonitoringAccountingView (104-105)
openwisp_radius/integrations/monitoring/serializers.py (2)
openwisp_radius/utils.py (1)
  • load_model (33-34)
openwisp_radius/api/views.py (1)
  • Meta (412-415)
🪛 Ruff (0.14.14)
openwisp_radius/integrations/monitoring/serializers.py

[warning] 34-44: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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). (8)
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
🔇 Additional comments (5)
openwisp_radius/integrations/monitoring/serializers.py (2)

8-17: Good move to localize datetime formatting server-side.

This aligns well with the goal of consistent locale-aware formatting.


32-45: Meta fields/read-only configuration looks good.

Clear and explicit field exposure for the monitoring endpoint.

openwisp_radius/api/urls.py (1)

91-95: New monitoring sessions route is correctly wired.

Naming and URL pattern are consistent with the existing API routes.

openwisp_radius/integrations/monitoring/views.py (1)

16-29: Monitoring view configuration is solid.

Permissions, filtering, and pagination are consistent with existing accounting views.

openwisp_radius/api/views.py (1)

56-56: Monitoring endpoint exposure looks correct.

Import and view alias match the new API route.

Also applies to: 851-852

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 18 to 30
def get_start_time(self, obj):
"""Format start_time using Django's localization settings"""
if not obj.start_time:
return None
local_time = timezone.localtime(obj.start_time)
return formats.date_format(local_time, "DATETIME_FORMAT")

def get_stop_time(self, obj):
"""Format stop_time using Django's localization settings"""
if not obj.stop_time:
return None
local_time = timezone.localtime(obj.stop_time)
return formats.date_format(local_time, "DATETIME_FORMAT")
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:

Django timezone.localtime naive datetime behavior USE_TZ False

💡 Result:

With USE_TZ = False, Django operates in local time using naive datetime objects (i.e., no tzinfo). That means there’s nothing to “convert to local time” — values are already assumed local. (docs.djangoproject.com)

django.utils.timezone.localtime() with naive datetimes (including when USE_TZ=False)

timezone.localtime(value) requires an aware datetime. If value is naive, it raises:

This is true regardless of USE_TZ.

timezone.localtime() with no argument when USE_TZ=False

If you call timezone.localtime() with no value, it defaults to timezone.now(). But timezone.now() returns a naive datetime when USE_TZ=False, so localtime() will then raise the same ValueError. (docs.djangoproject.com)

Template behavior difference

Django’s template helper (template_localtime) only converts when time zone support is enabled and the datetime is aware; otherwise it returns the value unchanged. (docs.djangoproject.com)

Practical takeaway

  • If USE_TZ=False: don’t use timezone.localtime(); treat your naive datetimes as already-local. (docs.djangoproject.com)
  • If you need conversions between time zones: set USE_TZ=True and use aware datetimes. (docs.djangoproject.com)

Citations:


🏁 Script executed:

# Find and examine the serializer file
fd -t f "serializers.py" | grep monitoring

Repository: openwisp/openwisp-radius

Length of output: 122


🏁 Script executed:

# Check the model definition and imports in the serializer
head -40 openwisp_radius/integrations/monitoring/serializers.py

Repository: openwisp/openwisp-radius

Length of output: 1447


🏁 Script executed:

# Check Django settings for USE_TZ configuration
fd -t f "settings.py" | head -5 | xargs grep -l "USE_TZ"

Repository: openwisp/openwisp-radius

Length of output: 52


🏁 Script executed:

# Find the RadiusAccounting model
fd -t f "models.py" | grep radius | head -5

Repository: openwisp/openwisp-radius

Length of output: 201


🏁 Script executed:

# Search for timezone.localtime usage in the codebase
rg "timezone\.localtime" -A 2 -B 2

Repository: openwisp/openwisp-radius

Length of output: 974


🏁 Script executed:

# Search for RadiusAccounting model definition
rg "class RadiusAccounting" -A 10

Repository: openwisp/openwisp-radius

Length of output: 5857


🏁 Script executed:

# Check for USE_TZ in settings
rg "USE_TZ\s*=" openwisp_radius/

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Find AbstractRadiusAccounting definition
rg "class AbstractRadiusAccounting" -A 20

Repository: openwisp/openwisp-radius

Length of output: 1398


🏁 Script executed:

# Look for start_time and stop_time field definitions
rg "start_time|stop_time" -B 2 -A 2 openwisp_radius/base/models.py | head -50

Repository: openwisp/openwisp-radius

Length of output: 1751


🏁 Script executed:

# Check if there are tests for the monitoring serializer
fd -t f "test*monitoring*" | grep -i serializer

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Search for any datetime handling or timezone awareness in tests
rg "start_time|stop_time" openwisp_radius/integrations/monitoring/ -t py

Repository: openwisp/openwisp-radius

Length of output: 5562


🏁 Script executed:

# Check the DateTimeField definitions more thoroughly
rg "start_time|stop_time" openwisp_radius/base/models.py -B 3 -A 3

Repository: openwisp/openwisp-radius

Length of output: 1937


🏁 Script executed:

# Check if there are any tests that exercise the serializer with naive datetimes
fd -t f "test_metrics.py" -exec head -100 {} \;

Repository: openwisp/openwisp-radius

Length of output: 256


🏁 Script executed:

# Look for any timezone awareness checks in similar code
rg "is_aware|is_naive" openwisp_radius/

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Check test files for the serializer usage
fd -t f "test" openwisp_radius/integrations/monitoring/ | xargs grep -l "Serializer\|serializer"

Repository: openwisp/openwisp-radius

Length of output: 52


🏁 Script executed:

# Look at the test file that likely uses the serializer
cat openwisp_radius/integrations/monitoring/tests/test_metrics.py | head -100

Repository: openwisp/openwisp-radius

Length of output: 4130


🏁 Script executed:

# Check if there's a specific test for the monitoring serializer
rg "MonitoringRadiusAccountingSerializer" openwisp_radius/integrations/monitoring/tests/

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Check what now() function is being used and how it behaves with USE_TZ
rg "from.*import.*now|def now" openwisp_radius/base/models.py -B 2 -A 2

Repository: openwisp/openwisp-radius

Length of output: 276


🏁 Script executed:

# Check the actual project settings for USE_TZ
find . -name "settings*.py" -type f | head -10

Repository: openwisp/openwisp-radius

Length of output: 181


🏁 Script executed:

# Look at test settings to see if USE_TZ is set
cat tests/openwisp2/settings.py | grep -i "use_tz" -A 2 -B 2

Repository: openwisp/openwisp-radius

Length of output: 290


🏁 Script executed:

# Check if there are any integration tests for the monitoring serializer that use actual data
rg "MonitoringRadiusAccountingSerializer\|get_start_time\|get_stop_time" openwisp_radius/ -A 5

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Check how data flows from RADIUS protocol (which often produces naive datetimes) into the serializer
rg "_RADACCT|start_time.*=" openwisp_radius/tests/ -B 2 -A 2 | head -50

Repository: openwisp/openwisp-radius

Length of output: 3134


🏁 Script executed:

# Check _RADACCT definition to see what datetime format is used for test data
rg "_RADACCT\s*=" openwisp_radius/tests/ -A 20

Repository: openwisp/openwisp-radius

Length of output: 1318


🏁 Script executed:

# Look for any data migrations or imports from external RADIUS sources
rg "naive|aware|timezone|import.*datetime" openwisp_radius/tests/__init__.py

Repository: openwisp/openwisp-radius

Length of output: 130


🏁 Script executed:

# Check if there are any settings variations where USE_TZ might be False
rg "USE_TZ" openwisp_radius/ --include="*.py"

Repository: openwisp/openwisp-radius

Length of output: 509


🏁 Script executed:

# Check for USE_TZ in the entire codebase more carefully
rg "USE_TZ" openwisp_radius/ -t py

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Check what happens when datetime strings are parsed - check the model creation
rg "start_time.*=" openwisp_radius/tests/__init__.py -B 5 -A 5

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Let's understand how the test data (string) gets converted to a datetime object
cat openwisp_radius/tests/__init__.py | head -50

Repository: openwisp/openwisp-radius

Length of output: 1647


🏁 Script executed:

# Check if there are any database interactions that could affect timezone awareness
rg "strptime|parse_datetime|DateTimeField" openwisp_radius/base/models.py -B 2 -A 2

Repository: openwisp/openwisp-radius

Length of output: 882


🏁 Script executed:

# Check if there are API tests that actually call the monitoring serializer
fd -t f "test" openwisp_radius/integrations/monitoring/ -exec grep -l "serializer\|api\|json" {} \;

Repository: openwisp/openwisp-radius

Length of output: 283


🏁 Script executed:

# Look for views that use the monitoring serializer
rg "MonitoringRadiusAccountingSerializer" openwisp_radius/integrations/monitoring/views.py -B 5 -A 5

Repository: openwisp/openwisp-radius

Length of output: 831


🏁 Script executed:

# Check if there are API endpoint tests that would exercise the serializer
fd -t f "test*.py" openwisp_radius/integrations/monitoring/tests/

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# List all test files in monitoring integration
fd -t f "test" openwisp_radius/integrations/monitoring/tests/

Repository: openwisp/openwisp-radius

Length of output: 189


🏁 Script executed:

# Check if there are any API tests for the monitoring endpoint
rg "MonitoringAccountingView\|api.*accounting\|test.*monitoring" openwisp_radius/integrations/monitoring/tests/ -l

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Look at the test directory structure
ls -la openwisp_radius/integrations/monitoring/tests/

Repository: openwisp/openwisp-radius

Length of output: 433


🏁 Script executed:

# Check test_admin.py for serializer usage
rg "serializer\|Serializer" openwisp_radius/integrations/monitoring/tests/test_admin.py

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Check if there's any test that actually exercises the API with the serializer
rg "get\(.*api\|get_json\|api.*accounting" openwisp_radius/integrations/monitoring/tests/test_admin.py -A 5

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Look for any comment or issue reference about this serializer being added
rg "localtime\|timezone.*aware\|naive" openwisp_radius/integrations/monitoring/ -B 3 -A 3

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Check if there's a way the datetime could actually be naive - look at how RADIUS accounting data is created
rg "RadiusAccounting.objects.create\|_create_radius_accounting" openwisp_radius/tests/mixins.py -A 10

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Check if there's any documentation about how RADIUS accounting data is imported
rg "import\|migrate\|load" openwisp_radius/integrations/monitoring/ | grep -i account

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Let's search for any other similar patterns where timezone handling might be incomplete
rg "formats.date_format" openwisp_radius/ -B 3 -A 3

Repository: openwisp/openwisp-radius

Length of output: 1342


🏁 Script executed:

# Check what Django version this project targets
cat setup.py | grep -i django

Repository: openwisp/openwisp-radius

Length of output: 374


🏁 Script executed:

# Check if there's any documentation or comments about timezone handling in RADIUS
rg "timezone\|aware\|naive\|USE_TZ" openwisp_radius/base/models.py | head -20

Repository: openwisp/openwisp-radius

Length of output: 50


🏁 Script executed:

# Verify the exact Django version requirement
cat setup.py | head -50

Repository: openwisp/openwisp-radius

Length of output: 1723


🏁 Script executed:

# Check if there are any RADIUS data import scripts that might introduce naive datetimes
rg "import.*csv\|loaddata\|migrate" openwisp_radius/ -l | head -5

Repository: openwisp/openwisp-radius

Length of output: 50


Guard against naive datetimes before calling timezone.localtime().

timezone.localtime() raises ValueError when passed a naive datetime, regardless of the USE_TZ setting. Since RADIUS accounting data originates from external sources and may include legacy naive datetimes, the serializer should check timezone awareness before conversion.

Suggested fix
     def get_start_time(self, obj):
         """Format start_time using Django's localization settings"""
         if not obj.start_time:
             return None
-        local_time = timezone.localtime(obj.start_time)
-        return formats.date_format(local_time, "DATETIME_FORMAT")
+        value = obj.start_time
+        if timezone.is_aware(value):
+            value = timezone.localtime(value)
+        return formats.date_format(value, "DATETIME_FORMAT")
 
     def get_stop_time(self, obj):
         """Format stop_time using Django's localization settings"""
         if not obj.stop_time:
             return None
-        local_time = timezone.localtime(obj.stop_time)
-        return formats.date_format(local_time, "DATETIME_FORMAT")
+        value = obj.stop_time
+        if timezone.is_aware(value):
+            value = timezone.localtime(value)
+        return formats.date_format(value, "DATETIME_FORMAT")
📝 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
def get_start_time(self, obj):
"""Format start_time using Django's localization settings"""
if not obj.start_time:
return None
local_time = timezone.localtime(obj.start_time)
return formats.date_format(local_time, "DATETIME_FORMAT")
def get_stop_time(self, obj):
"""Format stop_time using Django's localization settings"""
if not obj.stop_time:
return None
local_time = timezone.localtime(obj.stop_time)
return formats.date_format(local_time, "DATETIME_FORMAT")
def get_start_time(self, obj):
"""Format start_time using Django's localization settings"""
if not obj.start_time:
return None
value = obj.start_time
if timezone.is_aware(value):
value = timezone.localtime(value)
return formats.date_format(value, "DATETIME_FORMAT")
def get_stop_time(self, obj):
"""Format stop_time using Django's localization settings"""
if not obj.stop_time:
return None
value = obj.stop_time
if timezone.is_aware(value):
value = timezone.localtime(value)
return formats.date_format(value, "DATETIME_FORMAT")
🤖 Prompt for AI Agents
In `@openwisp_radius/integrations/monitoring/serializers.py` around lines 18 - 30,
The serializer currently passes datetimes directly to timezone.localtime() in
get_start_time and get_stop_time, which will raise ValueError for naive
datetimes; update both methods to first check
timezone.is_aware(obj.start_time/obj.stop_time) and, if naive, call
timezone.make_aware(obj.start_time/obj.stop_time,
timezone.get_default_timezone()) before calling timezone.localtime(), then
format with formats.date_format as before so external/legacy naive RADIUS
timestamps are handled safely.

@Eeshu-Yadav Eeshu-Yadav requested a review from pandafy January 30, 2026 10:28
@atif09
Copy link

atif09 commented Jan 30, 2026

please resolve code rabbit's comments by either:

-> replying to the suggestions made (reply to it stating why u don't agree with it's approach / reply to it with a question related to it's approach to get more clarity on it)

Or,

-> applying the changes suggested by it if you're sure that they are worth addressing.

…nwisp#662

Use Django locale for date formatting in device page RADIUS sessions tab JS, matching Django admin and localization settings.

Fixes openwisp#662
@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/662-radius_sessions_issue branch from b189d0d to dbd1b63 Compare January 30, 2026 11:19
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.

@Eeshu-Yadav once you're done, please post a screenshot of the UI result in high res so we can verify the visual result matches our expectations (as @pandafy explained). Thanks 👍

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.

[bug] RADIUS sessions tab in device page uses inconsistent date time format

7 participants