Skip to content

[feature] Added API endpoint to manage RADIUS groups of users #676#680

Merged
nemesifier merged 10 commits intomasterfrom
issues/676-api-radius-user-group
Mar 10, 2026
Merged

[feature] Added API endpoint to manage RADIUS groups of users #676#680
nemesifier merged 10 commits intomasterfrom
issues/676-api-radius-user-group

Conversation

@pandafy
Copy link
Member

@pandafy pandafy commented Feb 6, 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.

Reference to Existing Issue

Closes #676

@pandafy pandafy moved this from To do (general) to In progress in OpenWISP Contributor's Board Feb 6, 2026
@pandafy pandafy self-assigned this Feb 6, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0b020925-690d-4e5e-9e33-4b4ce966ac33

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

Adds REST API endpoints to manage per-user RADIUS group assignments: list/create at /api/v1/users/user/<user_pk>/radius-group/ and detail (retrieve/update/destroy) at /api/v1/users/user/<user_pk>/radius-group//. Introduces RadiusUserGroupSerializer (limits group choices to the parent user's organizations and injects user info), new views (BaseRadiusUserGroupView, RadiusUserGroupListCreateView, RadiusUserGroupDetailView) with organization-aware queryset/filtering and permission checks, URL registrations, tests for list/detail flows, and documentation updates.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as "API Server\n(RadiusUserGroupListCreateView)"
    participant Auth as "Auth/Permission\nChecks"
    participant DB as "Database\n(User, RadiusGroup, RadiusUserGroup)"

    Client->>API: GET/POST /users/user/{user_pk}/radius-group/
    API->>Auth: verify authentication & permissions
    Auth-->>API: allow/deny
    alt allowed
        API->>DB: fetch parent User & organizations
        DB-->>API: parent user + orgs
        API->>DB: (GET) query RadiusUserGroup filtered by orgs
        DB-->>API: list results
        API-->>Client: 200 with list
        opt POST flow
            Client->>API: POST with group UUID, optional priority
            API->>DB: create RadiusUserGroup (validate same-organization)
            DB-->>API: created instance
            API-->>Client: 201 with created object
        end
    else denied
        API-->>Client: 401/403
    end
Loading
sequenceDiagram
    participant Client
    participant API as "API Server\n(RadiusUserGroupDetailView)"
    participant Auth as "Auth/Permission\nChecks"
    participant DB as "Database\n(RadiusUserGroup)"

    Client->>API: GET/PATCH/PUT/DELETE /users/user/{user_pk}/radius-group/{id}/
    API->>Auth: verify authentication & permissions
    Auth-->>API: allow/deny
    alt allowed
        API->>DB: fetch RadiusUserGroup by id constrained to parent user
        DB-->>API: instance or 404
        alt found
            opt Update (PATCH/PUT)
                API->>DB: validate (ensure group same-organization) and update
                DB-->>API: updated instance
                API-->>Client: 200 with updated data
            end
            opt Delete
                API->>DB: delete instance
                DB-->>API: confirmation
                API-->>Client: 204
            end
        else not found
            API-->>Client: 404
        end
    else denied
        API-->>Client: 401/403
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding API endpoints to manage RADIUS groups for users, referencing issue #676.
Description check ✅ Passed The PR description includes all required checklist items (guidelines read, manual testing, tests written, documentation updated) and references the related issue #676, meeting the template requirements.
Linked Issues check ✅ Passed The implementation fully addresses all objectives from issue #676: list/filter RADIUS user-group relationships, create new relationships, update (via PUT/PATCH), and delete relationships, with multi-tenancy enforcement.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing RADIUS user-group API endpoints, serializers, views, URLs, tests, and documentation—no unrelated modifications detected.

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


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

@coderabbitai coderabbitai bot added the enhancement New feature or request label Feb 6, 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: 2

🤖 Fix all issues with AI agents
In `@docs/user/rest-api.rst`:
- Around line 1011-1025: Fix the PATCH section description and parameter
optionality: change the summary sentence that currently reads "Partially updates
a RADIUS group identified by its UUID." to "Partially updates a RADIUS user
group assignment." (this is the PATCH section header/first sentence) and update
the parameters table entries for `group` and `priority` so neither is marked as
"required" — document both as optional to reflect PATCH's partial-update
behavior; ensure `group` still notes it expects a UUID and `priority` remains an
integer.

In `@openwisp_radius/tests/test_api/test_api.py`:
- Line 1342: Remove the leftover debug print by deleting the
print(response.data) statement in the test (the debug print in
openwisp_radius/tests/test_api/test_api.py); ensure the test relies on
assertions or proper logging instead of printing response.data to avoid noisy
test output.
🧹 Nitpick comments (1)
openwisp_radius/api/serializers.py (1)

386-390: Misleading ref_name on GroupSerializer.

ref_name = "radius_user_group_serializer" on the Django auth Group serializer is confusing — it implies this serializer is for RadiusUserGroup when it actually serializes django.contrib.auth.models.Group. Consider a clearer name like "radius_auth_group" to avoid confusion in the OpenAPI schema.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33bbd55 and dc8d445.

📒 Files selected for processing (6)
  • docs/user/rest-api.rst
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/urls.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
  • tests/openwisp2/sample_radius/api/views.py
🧰 Additional context used
🧬 Code graph analysis (3)
tests/openwisp2/sample_radius/api/views.py (1)
openwisp_radius/api/views.py (4)
  • RadiusGroupDetailView (942-946)
  • RadiusGroupListView (896-904)
  • RadiusUserGroupDetailView (1039-1040)
  • RadiusUserGroupListCreateView (1000-1001)
openwisp_radius/api/serializers.py (3)
openwisp_radius/base/models.py (14)
  • Meta (312-316)
  • Meta (344-348)
  • Meta (535-539)
  • Meta (618-622)
  • Meta (649-652)
  • Meta (749-754)
  • Meta (785-789)
  • Meta (817-821)
  • Meta (851-855)
  • Meta (939-944)
  • Meta (1173-1177)
  • Meta (1330-1333)
  • Meta (1469-1477)
  • Meta (1648-1651)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (1)
  • RadiusUserGroup (58-60)
openwisp_radius/api/views.py (2)
openwisp_radius/api/serializers.py (2)
  • RadiusUserGroupSerializer (368-383)
  • user (716-717)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
⏰ 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.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.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~=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.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
🔇 Additional comments (7)
tests/openwisp2/sample_radius/api/views.py (1)

20-25: New view imports and bindings follow the existing pattern for RadiusGroup views.

The direct import approach (without local subclassing) is consistent with how RadiusGroupListView and RadiusGroupDetailView are already handled in this file (lines 125-126). No issues.

Also applies to: 127-128

openwisp_radius/api/urls.py (1)

101-110: URL patterns look correct.

The nested resource pattern under users/user/<user_pk>/ is appropriate for user-scoped RADIUS group assignments. Using <str:user_pk> accommodates both integer and UUID-based user primary keys.

openwisp_radius/tests/test_api/test_api.py (1)

1295-1494: Tests are thorough and cover key scenarios well.

Good coverage of authentication, authorization, cross-org restrictions, CRUD operations, and superuser access for both list and detail endpoints.

One optional gap: consider adding a test case for PATCH/PUT on the detail endpoint that attempts to change the group to one from a different organization (similar to the "Cannot create RadiusUserGroup with group from other org" subtest in test_radius_user_group_list).

openwisp_radius/api/views.py (3)

951-981: BaseRadiusUserGroupView is well-structured with proper parent and organization filtering.

The separation of get_queryset, get_parent_queryset, and get_organization_queryset is clean and follows the FilterByParentManaged pattern. The select_related on the base queryset is a good optimization.


1039-1040: organization_field correctly set for organization-based filtering.

Using "group__organization" properly traverses the relationship from RadiusUserGroupRadiusGroupOrganization for permission checks on detail operations.


51-55: Imports are correct.

FilterByParentManaged and RadiusUserGroupSerializer are properly imported and used by the new view classes.

Also applies to: 76-76

openwisp_radius/api/serializers.py (1)

376-383: Potential AttributeError if parent user does not exist.

view.get_parent_queryset().first() returns None when the user PK from the URL doesn't exist. In that case, user.username on line 380 will raise an AttributeError, resulting in a 500 error instead of a clean 404.

While FilterByParentManaged is designed to validate parent existence at the view level and should prevent this, there is no explicit test case for a completely non-existent user_pk, making it difficult to confirm the exact behavior. Adding a defensive check here would ensure protection regardless of the parent validation order.

Proposed defensive fix
     def validate(self, data):
         view = self.context.get("view")
         user = view.get_parent_queryset().first()
+        if user is None:
+            raise serializers.ValidationError(
+                {"user": _("User not found.")}
+            )
         if "username" not in data:
             data["username"] = user.username
         if "user" not in data:
             data["user"] = user
         return super().validate(data)

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

@pandafy pandafy force-pushed the issues/676-api-radius-user-group branch 3 times, most recently from e7ee368 to 84b13c8 Compare February 6, 2026 14:52
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/api/urls.py`:
- Around line 101-110: The URL patterns for radius_user_group_list and
radius_user_group_detail currently use the parameter "<str:user_pk>" which
allows arbitrary strings; change both to use "<uuid:user_pk>" so Django
validates UUIDs at routing and stays consistent with other patterns (e.g., those
using "<uuid:pk>"). Update the two path() calls referencing
radius_user_group_list and radius_user_group_detail to replace "str:user_pk"
with "uuid:user_pk" and run tests to ensure URL reversing and view lookups still
work.
🧹 Nitpick comments (4)
openwisp_radius/api/serializers.py (2)

374-381: Consider guarding against a missing parent user.

If get_parent_queryset().first() returns None (e.g., due to a race condition or the parent check not yet having run), the group field's queryset will remain unfiltered at this level. While FilterSerializerByOrgManaged and the view's parent check likely prevent this in practice, adding a defensive guard would make the intent clearer and prevent accidental misuse.

Proposed fix
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         view = self.context.get("view")
         self._user = view.get_parent_queryset().first()
         if self._user:
             self.fields["group"].queryset = self.fields["group"].queryset.filter(
                 organization_id__in=self._user.organizations_dict.keys()
             )
+        else:
+            self.fields["group"].queryset = self.fields["group"].queryset.none()

392-396: Misleading ref_name on GroupSerializer.

ref_name = "radius_user_group_serializer" on GroupSerializer (which serializes django.contrib.auth.models.Group) could easily be confused with the newly added RadiusUserGroupSerializer. A clearer name like "radius_auth_group_serializer" or "radius_django_group_serializer" would reduce ambiguity in the Swagger schema.

openwisp_radius/api/views.py (1)

963-967: Minor: redundant super().get_queryset() call in swagger branch.

qs already holds super().get_queryset(). The swagger branch can simply return qs.

Proposed fix
     def get_queryset(self):
         qs = super().get_queryset()
         if getattr(self, "swagger_fake_view", False):
-            return super().get_queryset()
+            return qs
         return qs.filter(user_id=self.kwargs["user_pk"])
openwisp_radius/tests/test_api/test_api.py (1)

1295-1389: Good coverage, but consider adding a multi-org user scenario.

The tests thoroughly cover auth, permissions, cross-org restrictions, and superuser flows. However, a scenario where the target user belongs to multiple organizations while the admin manages only one is not tested. This would validate that the list endpoint doesn't leak RadiusUserGroups from organizations the admin doesn't manage — directly tied to the organization_field concern flagged in the views.

Example scenario to add:

  1. target_user is a member of both org1 and org2
  2. admin_user manages only org1
  3. target_user has RadiusUserGroups in both org1 and org2
  4. Assert the list endpoint only returns org1's RadiusUserGroups
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc8d445 and 84b13c8.

📒 Files selected for processing (6)
  • docs/user/rest-api.rst
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/urls.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
  • tests/openwisp2/sample_radius/api/views.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/user/rest-api.rst
🧰 Additional context used
🧬 Code graph analysis (4)
openwisp_radius/tests/test_api/test_api.py (7)
openwisp_radius/api/serializers.py (1)
  • user (722-723)
openwisp_radius/models.py (2)
  • RadiusGroup (43-46)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (2)
  • RadiusGroup (43-45)
  • RadiusUserGroup (58-60)
openwisp_radius/api/views.py (2)
  • get (166-178)
  • get (707-719)
openwisp_radius/api/freeradius_views.py (1)
  • get (495-500)
openwisp_radius/tests/__init__.py (1)
  • _create_radius_usergroup (123-128)
openwisp_radius/base/models.py (2)
  • delete (686-689)
  • delete (1076-1079)
openwisp_radius/api/views.py (3)
openwisp_radius/api/serializers.py (2)
  • RadiusUserGroupSerializer (368-389)
  • user (722-723)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (1)
  • RadiusUserGroup (58-60)
openwisp_radius/api/serializers.py (3)
openwisp_radius/api/views.py (5)
  • Meta (424-427)
  • Meta (870-871)
  • get (166-178)
  • get (707-719)
  • get_parent_queryset (969-971)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (1)
  • RadiusUserGroup (58-60)
tests/openwisp2/sample_radius/api/views.py (1)
openwisp_radius/api/views.py (4)
  • RadiusGroupDetailView (942-946)
  • RadiusGroupListView (896-904)
  • RadiusUserGroupDetailView (1041-1042)
  • RadiusUserGroupListCreateView (1002-1003)
⏰ 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). (9)
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
🔇 Additional comments (4)
openwisp_radius/api/views.py (2)

973-983: get_organization_queryset correctly scopes parent users — LGTM.

Filters by managed orgs and excludes superusers, with .distinct() to handle the join. The dynamic app_label construction for the organization user lookup follows the established openwisp pattern.


952-984: Revert this review: organization_field is not used for child queryset filtering in FilterByParentManaged.

The original analysis assumes that setting organization_field = "group__organization" on a view will filter the child RadiusUserGroup queryset by organization. However, examining the FilterByParentManaged mixin from openwisp-users shows that organization_field is used only for filtering the parent queryset (the User) during the assert_parent_exists() validation. The mixin does not filter the child queryset based on this attribute.

Both RadiusUserGroupListCreateView and RadiusUserGroupDetailView inherit the same parent validation logic from FilterByParentManaged. Moving organization_field to the base class would have no effect on child queryset filtering and would not resolve any multi-tenancy vulnerability.

If there is an actual multi-tenancy concern (e.g., admin managing only Org1 can see RadiusUserGroups from Org2 for a user that belongs to multiple organizations), the proper fix would be to filter the child queryset by the group's organization in the get_queryset() method, not by setting organization_field.

Likely an incorrect or invalid review comment.

tests/openwisp2/sample_radius/api/views.py (1)

20-25: LGTM — consistent with the existing pattern for RadiusGroup views.

The direct import and as_view() binding pattern matches how RadiusGroupListView and RadiusGroupDetailView are already handled in this file (lines 21-22, 125-126).

Also applies to: 127-128

openwisp_radius/tests/test_api/test_api.py (1)

1391-1493: Detail endpoint tests look solid.

Comprehensive coverage for all CRUD operations, authentication, permissions, 404 handling, cross-org isolation, and superuser access.

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

@coveralls
Copy link

coveralls commented Feb 6, 2026

Coverage Status

coverage: 97.46% (+0.03%) from 97.426%
when pulling 783bf35 on issues/676-api-radius-user-group
into eb34b91 on master.

@pandafy pandafy force-pushed the issues/676-api-radius-user-group branch from af7d042 to 52da134 Compare February 27, 2026 18:47
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 `@openwisp_radius/api/serializers.py`:
- Around line 374-377: The serializer __init__ assumes a nested view and parent
queryset exist and can crash; update __init__ in the serializer to defensively
get view = self.context.get("view") and only call view.get_parent_queryset() if
view is truthy and has that method, assigning self._user = parent_qs.first()
when available; if view is missing or parent queryset is None, fall back to
resolve a user from self.initial_data.get("user_pk") (or
self.context.get("user_pk")) and set self._user accordingly, and otherwise set
self._user = None so initialization never raises when view/user_pk are absent.

In `@openwisp_radius/api/views.py`:
- Around line 1002-1003: RadiusUserGroupListCreateView lacks organization
filtering for ?organization=...; update the class
(RadiusUserGroupListCreateView) to enable filtering by adding
DjangoFilterBackend to filter_backends and exposing organization (e.g., via
filterset_fields = ("organization",) or by assigning a filterset_class like
RadiusUserGroupFilter that includes an organization field), and ensure
DjangoFilterBackend is imported (from django_filters.rest_framework import
DjangoFilterBackend) so the view honors the ?organization query param.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af7d042 and 52da134.

📒 Files selected for processing (3)
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
📜 Review details
⏰ 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.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | 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.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
🧰 Additional context used
🧬 Code graph analysis (3)
openwisp_radius/tests/test_api/test_api.py (6)
openwisp_radius/api/serializers.py (1)
  • user (724-725)
openwisp_radius/models.py (2)
  • RadiusGroup (43-46)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (2)
  • RadiusGroup (43-45)
  • RadiusUserGroup (58-60)
openwisp_radius/tests/__init__.py (1)
  • _create_radius_usergroup (123-128)
tests/openwisp2/sample_users/models.py (1)
  • OrganizationUser (37-39)
openwisp_radius/base/models.py (2)
  • delete (686-689)
  • delete (1076-1079)
openwisp_radius/api/serializers.py (3)
openwisp_radius/base/models.py (14)
  • Meta (312-316)
  • Meta (344-348)
  • Meta (535-539)
  • Meta (618-622)
  • Meta (649-652)
  • Meta (749-754)
  • Meta (785-789)
  • Meta (817-821)
  • Meta (851-855)
  • Meta (939-944)
  • Meta (1173-1177)
  • Meta (1330-1333)
  • Meta (1469-1477)
  • Meta (1648-1651)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (1)
  • RadiusUserGroup (58-60)
openwisp_radius/api/views.py (2)
openwisp_radius/api/serializers.py (2)
  • RadiusUserGroupSerializer (368-391)
  • user (724-725)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
🔇 Additional comments (1)
openwisp_radius/tests/test_api/test_api.py (1)

1295-1511: Strong authorization and CRUD coverage for the new RadiusUserGroup endpoints.

The new tests validate unauthenticated/unauthorized paths, cross-organization boundaries, and superuser behavior well across both list/create and detail flows.

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

🤖 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/rest-api.rst`:
- Around line 983-984: Replace the incorrect word "sorting" with "filtering" in
the endpoint description sentence that currently reads "It supports sorting by
organization id and organization slug." — update it to "It supports filtering by
organization id and organization slug." and ensure any nearby uses of "sorting"
in the same endpoint description are likewise changed to "filtering" to
accurately describe query-parameter behavior.
- Around line 1018-1038: The detail endpoint docs currently list only
GET/PATCH/DELETE but the API also supports PUT (full update); add a "PUT"
subsection under the same detail block (near the "GET (detail)", "PATCH", and
"DELETE" headings) that documents full updates for the RADIUS user group
assignment, describing that PUT performs a full replacement of the resource and
listing the same parameters (group UUID and priority integer) and any
required/optional semantics so behavior matches the exposed API. Reference the
"GET (detail)", "PATCH", and "DELETE" sections when inserting the new "PUT"
subsection to keep ordering and formatting consistent.

In `@openwisp_radius/tests/test_api/test_api.py`:
- Around line 1395-1401: The test incorrectly grants org2 admin membership to
target_user instead of admin_user; update the setup so that
OrganizationUser.objects.filter(..., user=target_user,
organization=org2).delete() remains but call
self._create_org_user(user=admin_user, organization=org2, is_admin=True)
(instead of user=target_user) so the subtest matches the comment scenario where
target_user is only a member of org1 and admin_user can manage both org1 and
org2.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52da134 and cb3c96c.

📒 Files selected for processing (4)
  • docs/user/rest-api.rst
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
📜 Review details
⏰ 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). (2)
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
🧰 Additional context used
🧬 Code graph analysis (3)
openwisp_radius/api/serializers.py (4)
openwisp_radius/api/views.py (6)
  • Meta (424-427)
  • Meta (870-871)
  • Meta (993-995)
  • get (166-178)
  • get (707-719)
  • get_parent_queryset (969-971)
openwisp_radius/base/models.py (13)
  • Meta (312-316)
  • Meta (344-348)
  • Meta (535-539)
  • Meta (618-622)
  • Meta (649-652)
  • Meta (749-754)
  • Meta (785-789)
  • Meta (817-821)
  • Meta (851-855)
  • Meta (939-944)
  • Meta (1173-1177)
  • Meta (1330-1333)
  • Meta (1469-1477)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (1)
  • RadiusUserGroup (58-60)
openwisp_radius/tests/test_api/test_api.py (5)
openwisp_radius/api/serializers.py (1)
  • user (728-729)
openwisp_radius/models.py (2)
  • RadiusGroup (43-46)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (2)
  • RadiusGroup (43-45)
  • RadiusUserGroup (58-60)
openwisp_radius/tests/__init__.py (1)
  • _create_radius_usergroup (123-128)
openwisp_radius/base/models.py (2)
  • delete (686-689)
  • delete (1076-1079)
openwisp_radius/api/views.py (2)
openwisp_radius/api/serializers.py (11)
  • RadiusUserGroupSerializer (368-395)
  • user (728-729)
  • Meta (141-144)
  • Meta (294-297)
  • Meta (304-306)
  • Meta (347-350)
  • Meta (369-372)
  • Meta (399-402)
  • Meta (408-410)
  • Meta (505-508)
  • Meta (766-781)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
🔇 Additional comments (2)
openwisp_radius/api/serializers.py (1)

374-395: Good user-scoped serializer enforcement.

Constraining group queryset by parent-user organizations and injecting parent user context in validation is a solid guardrail for nested endpoint integrity.

openwisp_radius/api/views.py (1)

952-1020: Nice separation of concerns for nested RadiusUserGroup endpoints.

BaseRadiusUserGroupView cleanly centralizes parent-user and organization scoping, and the list/create view wiring is consistent and maintainable.

@pandafy pandafy force-pushed the issues/676-api-radius-user-group branch from 1a139d2 to c8fa5f9 Compare February 27, 2026 21:15
@pandafy
Copy link
Member Author

pandafy commented Mar 2, 2026

@coderabbitai review and close all addressed inline comments.

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

@pandafy, I'll review the changes in this PR and resolve all addressed inline comments.

✅ Actions performed

Comments resolved and changes approved.


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.

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

♻️ Duplicate comments (1)
docs/user/rest-api.rst (1)

983-983: ⚠️ Potential issue | 🟡 Minor

Use “filtering” (not “sorting”) in this endpoint description.

Line 983 describes query-parameter filtering behavior, not ordering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/rest-api.rst` at line 983, The sentence "It supports sorting by
organization id and organization slug." is incorrect — replace "sorting" with
"filtering" so it reads "It supports filtering by organization id and
organization slug." to correctly describe the query-parameter behavior in the
REST API docs; update that exact sentence in the REST API endpoint description
to avoid implying ordering behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_radius/api/views.py`:
- Around line 963-967: get_queryset currently only filters by user_id and can
return RadiusUserGroup rows from organizations the requester doesn't manage;
update get_queryset to, after obtaining qs = super().get_queryset(),
additionally restrict qs to organizations managed by the requesting user unless
request.user.is_superuser is True. Use request.user.is_superuser to skip scoping
for superusers and filter by the requesting user's managed organizations (e.g.,
request.user.managed_organizations or an equivalent method/queryset) when
calling qs.filter(user_id=self.kwargs["user_pk"], organization__in=...) so only
assignments within managed orgs are returned.

---

Duplicate comments:
In `@docs/user/rest-api.rst`:
- Line 983: The sentence "It supports sorting by organization id and
organization slug." is incorrect — replace "sorting" with "filtering" so it
reads "It supports filtering by organization id and organization slug." to
correctly describe the query-parameter behavior in the REST API docs; update
that exact sentence in the REST API endpoint description to avoid implying
ordering behavior.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb3c96c and 52cb84f.

📒 Files selected for processing (4)
  • docs/user/rest-api.rst
  • openwisp_radius/api/serializers.py
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (3)
openwisp_radius/tests/test_api/test_api.py (5)
openwisp_radius/api/serializers.py (2)
  • RadiusUserGroupSerializer (368-397)
  • user (730-731)
openwisp_radius/models.py (2)
  • RadiusGroup (43-46)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (2)
  • RadiusGroup (43-45)
  • RadiusUserGroup (58-60)
openwisp_radius/tests/__init__.py (1)
  • _create_radius_usergroup (123-128)
tests/openwisp2/sample_users/models.py (1)
  • OrganizationUser (37-39)
openwisp_radius/api/serializers.py (3)
openwisp_radius/api/views.py (6)
  • Meta (424-427)
  • Meta (870-871)
  • Meta (993-995)
  • get (166-178)
  • get (707-719)
  • get_parent_queryset (969-971)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (1)
  • RadiusUserGroup (58-60)
openwisp_radius/api/views.py (2)
openwisp_radius/api/serializers.py (11)
  • RadiusUserGroupSerializer (368-397)
  • user (730-731)
  • Meta (141-144)
  • Meta (294-297)
  • Meta (304-306)
  • Meta (347-350)
  • Meta (369-372)
  • Meta (401-404)
  • Meta (410-412)
  • Meta (507-510)
  • Meta (768-783)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
🔇 Additional comments (2)
openwisp_radius/api/serializers.py (1)

374-397: Good defensive initialization and tenant-scoped validation path.

Line 374 onward now safely handles missing nested view context and keeps write choices constrained.

openwisp_radius/tests/test_api/test_api.py (1)

1296-1527: Solid coverage for new RadiusUserGroup API surface.

The added list/detail/serializer tests exercise permissions, tenancy paths, CRUD flows, and no-view serializer initialization well.

@pandafy
Copy link
Member Author

pandafy commented Mar 2, 2026

@coderabbitai review!

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

@pandafy, I'll review the changes in this PR right away.

✅ 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.

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

🤖 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/rest-api.rst`:
- Around line 1023-1033: The PUT parameters table is misleading because it lists
both fields as optional; update the docs for the PUT operation to state that
"group" is required for full updates (no default on the model) while "priority"
remains optional, or alternatively document that PUT supports partial updates if
the endpoint actually accepts missing fields; specifically adjust the PUT
section and the Params table entries for "group" and "priority" to reflect the
real behavior (mention "group" is required for full update on the RADIUS user
group assignment model and keep "priority" as optional) so readers of the PUT
documentation see accurate validation requirements.

In `@openwisp_radius/api/views.py`:
- Around line 996-1006: The class RadiusUserGroupFilter currently sets
organization_slug = None which overrides the parent OrganizationManagedFilter
attribute; add a brief inline comment next to the organization_slug assignment
in RadiusUserGroupFilter explaining that this intentionally disables the
inherited slug-based filter (because this filter uses group__organization and
group__organization__slug instead) so future maintainers understand the
override, or remove the assignment if it was unintentional.

In `@openwisp_radius/tests/test_api/test_api.py`:
- Around line 1424-1527: Add a subTest inside test_radius_user_group_detail that
issues a PUT to the existing url variable with a payload omitting the "group"
field (e.g., {"priority": 4}) and assert the API's expected behavior (most
likely a 400 Bad Request if PUT requires a full representation); refresh rug
from DB and verify that the group was not changed (or adjust the expected status
if your API treats PUT like PATCH and returns 200 and keeps group unchanged).
Use the existing radius_user_group_detail endpoint, the rug object
(RadiusUserGroup) and org1_default_group/org1_power_users_group fixtures to
locate the resource and validate the outcome.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52cb84f and 4064ce1.

📒 Files selected for processing (3)
  • docs/user/rest-api.rst
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (2)
openwisp_radius/tests/test_api/test_api.py (1)
openwisp_radius/api/serializers.py (2)
  • RadiusUserGroupSerializer (368-397)
  • user (730-731)
openwisp_radius/api/views.py (4)
openwisp_radius/api/serializers.py (12)
  • RadiusUserGroupSerializer (368-397)
  • get_queryset (416-421)
  • user (730-731)
  • Meta (141-144)
  • Meta (294-297)
  • Meta (304-306)
  • Meta (347-350)
  • Meta (369-372)
  • Meta (401-404)
  • Meta (410-412)
  • Meta (507-510)
  • Meta (768-783)
openwisp_radius/models.py (1)
  • RadiusUserGroup (61-64)
tests/openwisp2/sample_radius/models.py (1)
  • RadiusUserGroup (58-60)
openwisp_radius/api/freeradius_views.py (1)
  • get_queryset (492-493)
🔇 Additional comments (5)
docs/user/rest-api.rst (1)

964-1050: Documentation for RADIUS User Groups endpoints looks well-structured.

The new documentation section follows the established patterns in this file, covers all CRUD operations, and properly documents the organization filtering capabilities and constraints.

openwisp_radius/api/views.py (2)

952-994: Well-implemented base view with proper multi-tenancy controls.

The BaseRadiusUserGroupView correctly implements:

  • Organization-scoped access for non-superusers via get_queryset (lines 973-977)
  • Parent user filtering via get_parent_queryset
  • Dynamic filter construction that works with swappable user models

The cross-tenant access concern from previous reviews has been properly addressed.


1024-1069: LGTM!

Both RadiusUserGroupListCreateView and RadiusUserGroupDetailView are properly implemented with:

  • Appropriate pagination and filtering for the list view
  • Correct organization_field for permission checks on the detail view
  • Comprehensive Swagger documentation for all HTTP methods
openwisp_radius/tests/test_api/test_api.py (2)

1296-1422: Comprehensive test coverage for RadiusUserGroup list/create operations.

The test thoroughly covers:

  • Authentication requirements
  • Permission checks
  • Basic list and filtering functionality
  • Create operations with organization constraints
  • Cross-organization access restrictions
  • Superuser override capabilities

The fix from the past review (ensuring admin_user gets org2 membership at line 1406, not target_user) has been correctly applied.


1528-1531: Good edge case test for serializer initialization.

This test validates the defensive behavior when the serializer is instantiated without view context, ensuring the group queryset is properly restricted to empty.

)

def get_parent_queryset(self):
"""Get the parent user from the URL."""
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""Get the parent user from the URL."""

@nemesifier
Copy link
Member

@coderabbitai review, focus on real issues

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

@nemesifier, I'll review the PR now with a focus on real issues.


🧠 Learnings used
Learnt from: pandafy
Repo: openwisp/openwisp-radius PR: 680
File: docs/user/rest-api.rst:1023-1033
Timestamp: 2026-03-02T16:47:07.405Z
Learning: In the RADIUS User Groups API endpoint (`/api/v1/users/user/<user_pk>/radius-group/<uuid>/`), the `group` parameter is optional in PUT requests, allowing partial updates rather than requiring all fields for a full update.
✅ 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.

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

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

Inline comments:
In `@openwisp_radius/api/views.py`:
- Around line 957-971: RadiusUserGroupDetailView's object-level check uses
organization_field on the pre-update object but
RadiusUserGroupSerializer.__init__ currently scopes the group field to the
target user's organizations, allowing a manager to reassign a relation to an org
they don't manage; fix by further filtering the serializer's group queryset to
the request user's managed organizations (use
self.context["view"].request.user.organizations_managed) so the group choices
are limited to groups whose organization is in that set, and add a regression
test that creates a target user in two orgs, logs in as an org1 manager,
attempts to PUT/PATCH the user-group relation to a group in org2 and asserts the
update is rejected (403 or validation error).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cb98f6c7-9d67-452c-9434-5f4a32b6419c

📥 Commits

Reviewing files that changed from the base of the PR and between 4064ce1 and 4aae33d.

📒 Files selected for processing (2)
  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pandafy
Repo: openwisp/openwisp-radius PR: 680
File: docs/user/rest-api.rst:1023-1033
Timestamp: 2026-03-02T16:47:07.405Z
Learning: In the RADIUS User Groups API endpoint (`/api/v1/users/user/<user_pk>/radius-group/<uuid>/`), the `group` parameter is optional in PUT requests, allowing partial updates rather than requiring all fields for a full update.
📚 Learning: 2026-03-02T16:47:07.405Z
Learnt from: pandafy
Repo: openwisp/openwisp-radius PR: 680
File: docs/user/rest-api.rst:1023-1033
Timestamp: 2026-03-02T16:47:07.405Z
Learning: In the RADIUS User Groups API endpoint (`/api/v1/users/user/<user_pk>/radius-group/<uuid>/`), the `group` parameter is optional in PUT requests, allowing partial updates rather than requiring all fields for a full update.

Applied to files:

  • openwisp_radius/api/views.py
  • openwisp_radius/tests/test_api/test_api.py
🔇 Additional comments (1)
openwisp_radius/tests/test_api/test_api.py (1)

1487-1496: Good regression coverage for PUT-as-partial-update behavior.

This locks in the API contract that group stays optional on PUT, which is easy to regress when the serializer or update flow changes.

Based on learnings, in the RADIUS User Groups API endpoint (/api/v1/users/user/<user_pk>/radius-group/<uuid>/), the group parameter is optional in PUT requests, allowing partial updates rather than requiring all fields for a full update.

self._user = None
if self._user:
self.fields["group"].queryset = self.fields["group"].queryset.filter(
organization_id__in=self._user.organizations_dict.keys()
Copy link
Member

Choose a reason for hiding this comment

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

@pandafy after reading the last comment from coderabbit I realized this may be indeed wrong, shouldn't we use self._user.organizations_managed here?

Copy link
Member Author

Choose a reason for hiding this comment

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

@nemesifier you are correct, I have fixed this in 944c990

@openwisp-companion
Copy link

CI Failure Analysis

Hello @pandafy,
(Analysis for commit 944c990)

There are multiple test failures and performance issues identified in the CI logs.

Failures & Remediation

  1. Test Failures:

    • Explanation: The CI run failed due to numerous test failures, indicated by the F markers in the test output and the summary of slow tests. Specifically, many tests in openwisp_radius.tests.test_admin, openwisp_radius.tests.test_api, openwisp_radius.tests.test_models, and openwisp_radius.tests.test_selenium are taking significantly longer than the acceptable threshold (e.g., test_radiustoken_inline took 1.86s, test_batch_user_creation took 5.34s, test_standard_csv_import took 3.01s). This suggests potential performance regressions or inefficient test setups. The specific error "user with username "5c:7d:c1:72:a7:3b" does not exists" also points to a potential data or test setup issue. Additionally, there are several RuntimeWarning messages related to naive datetime objects being used with active time zone support, which, while warnings, can indicate underlying data issues that might lead to test failures.
    • Remediation:
      • Investigate the specific test failures indicated by the F markers to understand the root cause of each failure.
      • Optimize the slow tests identified in the summary. This might involve:
        • Reducing the amount of data processed per test.
        • Improving query efficiency.
        • Using more targeted mocking where appropriate.
        • Ensuring test data is set up and torn down efficiently.
      • Address the "user with username '5c:7d:c1:72:a7:3b' does not exist" error by ensuring that the necessary test data (users) is correctly created and available before the tests that rely on it are executed.
      • Address the RuntimeWarning about naive datetime objects by ensuring all datetime objects used in tests and application logic are timezone-aware, especially when time zone support is active in the Django settings.
  2. Build/Infrastructure/Other:

    • Explanation: The coveralls report command failed with an exit code of 1, and the output 🚨 Nothing to report suggests that the coverage data was not generated or collected correctly. This could be due to an issue with the coverage collection process itself or a problem with how the coverage data is being processed by Coveralls.
    • Remediation:
      • Review the coverage run and coverage combine steps in the CI script to ensure they are correctly capturing coverage data.
      • Verify that the coveralls report command is being executed with the correct arguments and that the necessary configuration for Coveralls is in place.
      • If the issue is intermittent, consider re-running the CI job. If it persists, investigate the coverage setup and the Coveralls integration.

@pandafy pandafy force-pushed the issues/676-api-radius-user-group branch from 944c990 to 81714c6 Compare March 9, 2026 15:53
@openwisp-companion
Copy link

CI Failure Analysis (commit 81714c6)

Hello @pandafy,

(Analysis for commit 81714c6)

The CI run failed due to a test execution error, specifically related to slow tests.

Failure: Test Execution Timeout

Explanation:
The CI pipeline timed out during the test execution phase. The logs indicate that a significant number of tests (281 in total) exceeded their execution time thresholds. Many tests in openwisp_radius.tests.test_admin and openwisp_radius.tests.test_api were flagged as slow, with some taking over 2 seconds to complete. The test test_batch_user_creation in openwisp_radius.tests.test_selenium.BasicTest was particularly slow, taking 5.26 seconds. This suggests a potential performance issue or inefficient test setup that is causing the overall test suite to run too long, leading to the timeout.

Remediation:

  1. Investigate Slow Tests: The first step is to identify the root cause of these slow tests. This might involve:

    • Profiling: Running tests with profiling tools to pinpoint bottlenecks in test setup, data creation, or assertions.
    • Optimizing Test Data: If tests rely on large amounts of data, consider optimizing how this data is generated or if it can be reduced.
    • Database Operations: Review tests that perform extensive database queries or complex transactions. Ensure efficient use of ORM features like select_related and prefetch_related.
    • External Dependencies: Check if any tests are slow due to interactions with external services or slow mock setups.
    • Test Parallelization: While the logs show --parallel being used, investigate if the parallelization is effective or if there are contention issues.
  2. Address Specific Slow Tests: Based on the logs, focus on the tests marked with [31;1m (red) as they are the slowest:

    • test_inline_registered_user (2.15s)
    • test_nas_organization_fk_autocomplete_view (1.25s)
    • test_nas_queryset (1.38s)
    • test_organization_radsettings_allowed_mobile_prefixes (2.91s)
    • test_organization_radsettings_freeradius_allowed_hosts (2.36s)
    • test_organization_radsettings_password_reset_url (2.45s)
    • test_organization_radsettings_sms_message (2.89s)
    • test_authorize_simultaneous_use (2.34s)
    • test_cache (1.76s)
    • test_ip_from_radsetting_not_exist (1.49s)
    • test_authorize_counters_exception_handling (1.17s)
    • test_authorize_counters_reply_interaction (1.71s)
    • test_user_auth_token_disposed_after_auth (1.43s)
    • test_user_auth_token_obtain_auth_token_renew (1.46s)
    • test_authorize_with_password_after_radius_token_expires (1.18s)
    • test_authorize_with_user_auth (1.43s)
    • test_authorize_without_user_auth (1.15s)
    • test_user_auth_token_disposed_after_auth (1.43s)
    • test_user_auth_token_obtain_auth_token_renew (1.46s)
    • test_change_user (1.34s)
    • test_radiustoken_inline (2.16s)
    • test_cache (1.76s)
    • test_ip_from_radsetting_not_exist (1.49s)
    • test_authorize_simultaneous_use (2.34s)
    • `test_authorize_with_password

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 9, 2026
self.fields["group"].queryset = (
self.fields["group"]
.queryset.filter(organization__in=orgs)
.filter(organization__in=self._user.organizations_dict.keys())
Copy link
Member

@nemesifier nemesifier Mar 9, 2026

Choose a reason for hiding this comment

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

Here the code filters using user.organizations_dict.keys() but above user.organizations_managed is used.

Is this difference intentional and if so what's the motivation?

Keep in mind organizations_dict.keys() returns all the organizations the user is associated with, irregardless of whether the user is manager, owner or end user.

The discrepancy doesn't look intentional, if it is we need to add a comment explaining the nuances.

@nemesifier nemesifier merged commit eef0edf into master Mar 10, 2026
19 checks passed
@nemesifier nemesifier deleted the issues/676-api-radius-user-group branch March 10, 2026 21:16
@github-project-automation github-project-automation bot moved this from In progress to Done in OpenWISP Contributor's Board Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Development

Successfully merging this pull request may close these issues.

[feature] API endpoint to manage RADIUS groups of users

3 participants