Skip to content

Fix: assign BasicMember role in members API so members can access draft project details#1140

Draft
mohamedelabbas1996 wants to merge 1 commit intomainfrom
fix/members-api-draft-project-access
Draft

Fix: assign BasicMember role in members API so members can access draft project details#1140
mohamedelabbas1996 wants to merge 1 commit intomainfrom
fix/members-api-draft-project-access

Conversation

@mohamedelabbas1996
Copy link
Contributor

@mohamedelabbas1996 mohamedelabbas1996 commented Feb 19, 2026

Summary

Members added via the Members API receive 403 error when viewing draft project details. The fix manually assigns the BasicMember role when creating or updating members via the API.

List of Changes

  • Modified UserProjectMembershipViewSet in ami/users/api/views.py to assign BasicMember in perform_create and perform_update, and to unassign all roles (including BasicMember) in perform_destroy
  • Added TestMembersApiDraftProjectAccess in ami/users/tests/test_membership_management_api.py to verify all roles can access draft project details

Related Issues

TBD

Detailed Description

Draft project permission checks use BasicMember.has_role(). Previously, when members were added via the Members API, only the requested role (Identifier, Researcher, etc.) was assigned, so users without BasicMember got 403 when accessing draft project details.

How to Test the Changes

TBD

Screenshots

N/A

Deployment Notes

TBD

Checklist

  • I have tested these changes appropriately.
  • I have added and/or modified relevant tests.
  • I updated relevant documentation or comments.
  • I have verified that this PR follows the project's coding standards.
  • Any dependent changes have already been merged to main.

Summary by CodeRabbit

  • New Features

    • Introduced two new user roles: Identifier and Researcher, expanding role management options.
    • Assigned members can now access draft project details based on their role permissions.
  • Tests

    • Added coverage for draft project access control by members with assigned roles.

…ft project details

Co-authored-by: Cursor <cursoragent@cursor.com>
@mohamedelabbas1996 mohamedelabbas1996 self-assigned this Feb 19, 2026
@netlify
Copy link

netlify bot commented Feb 19, 2026

Deploy Preview for antenna-preview ready!

Name Link
🔨 Latest commit 02154ad
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/69974e27eea85f00078a9fd7
😎 Deploy Preview https://deploy-preview-1140--antenna-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 62 (🔴 down 4 from production)
Accessibility: 80 (no change from production)
Best Practices: 100 (no change from production)
SEO: 92 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Feb 19, 2026

Deploy Preview for antenna-ssec ready!

Name Link
🔨 Latest commit 02154ad
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/69974e27fca690000802e626
😎 Deploy Preview https://deploy-preview-1140--antenna-ssec.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Updated membership API views to ensure BasicMember role is consistently assigned alongside specific project roles during create and update operations. Added test coverage verifying draft project access control for members with different role types.

Changes

Cohort / File(s) Summary
API Role Assignment
ami/users/api/views.py
Updated perform_create, perform_update, and perform_destroy to unassign all Role subclasses, assign the chosen role via role_cls, and then assign BasicMember. User derivation in perform_create changed from serializer._validated_user to membership.user.
Draft Project Access Tests
ami/users/tests/test_membership_management_api.py
Added TestMembersApiDraftProjectAccess test class with test fixtures and cases verifying that members with assigned roles can access draft project details while non-members receive access denial (403/404).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Hop, skip, and role align,
BasicMember joins the line,
Members hop through draft arrays,
Access gates work every day!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: assigning BasicMember role in members API to enable draft project access.
Description check ✅ Passed The description covers summary, list of changes, detailed explanation, and test coverage, though some template sections (deployment notes, how to test) are marked TBD.

✏️ 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
  • Commit unit tests in branch fix/members-api-draft-project-access

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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
Contributor

@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

🧹 Nitpick comments (2)
ami/users/api/views.py (1)

63-66: Redundant BasicMember.assign_user call when role_cls is BasicMember

When the requested role_id maps to BasicMember, lines 65–66 (and 90–91 in perform_update) both call BasicMember.assign_user(user, project) back-to-back — the second call is a no-op due to Django's idempotent M2M add, but it incurs an extra DB round-trip on every BasicMember membership creation or update.

♻️ Suggested fix (apply to both perform_create and perform_update)
-            role_cls.assign_user(user, project)
-            BasicMember.assign_user(user, project)
+            role_cls.assign_user(user, project)
+            if role_cls is not BasicMember:
+                BasicMember.assign_user(user, project)

Also applies to: 88-91

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

In `@ami/users/api/views.py` around lines 63 - 66, The code unconditionally calls
BasicMember.assign_user(user, project) after assigning role_cls, causing a
redundant DB round-trip when role_cls is BasicMember; update both perform_create
and perform_update to skip the extra call by checking if role_cls is not
BasicMember (e.g., if role_cls is not BasicMember: BasicMember.assign_user(user,
project)) or otherwise only call BasicMember.assign_user when role_cls differs,
leaving the Role.__subclasses__ unassignment and role_cls.assign_user(user,
project) logic unchanged.
ami/users/tests/test_membership_management_api.py (1)

270-297: Test coverage gaps: perform_update and perform_destroy paths not exercised for draft access

The four parameterised tests only exercise perform_create (POST → assert 200). Two additional paths are untested:

  1. Update path — after a role change via PATCH, the member should retain draft access (verifies perform_update re-assigns BasicMember).
  2. Delete path — after membership deletion, the user should lose access (verifies perform_destroy correctly strips all roles including BasicMember).
💡 Suggested additions
def test_member_role_update_retains_draft_access(self):
    """After a role change via API, member should still access draft project."""
    self._add_member_and_assert_can_access_draft(self.user_basic, BasicMember.__name__)

    self.client.force_authenticate(self.superuser)
    membership = UserProjectMembership.objects.get(project=self.project, user=self.user_basic)
    update_url = f"{self.members_url}{membership.pk}/"
    resp = self.client.patch(update_url, {"role_id": Researcher.__name__}, format="json")
    self.assertEqual(resp.status_code, 200)

    self.client.force_authenticate(self.user_basic)
    detail_resp = self.client.get(self.detail_url)
    self.assertEqual(detail_resp.status_code, 200, "Member should retain draft access after role update")


def test_deleted_member_cannot_access_draft_project(self):
    """After membership deletion, former member should lose draft access."""
    self._add_member_and_assert_can_access_draft(self.user_basic, BasicMember.__name__)

    self.client.force_authenticate(self.superuser)
    membership = UserProjectMembership.objects.get(project=self.project, user=self.user_basic)
    delete_url = f"{self.members_url}{membership.pk}/"
    self.client.delete(delete_url)

    self.client.force_authenticate(self.user_basic)
    detail_resp = self.client.get(self.detail_url)
    self.assertIn(detail_resp.status_code, (403, 404), "Removed member should not access draft project")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ami/users/tests/test_membership_management_api.py` around lines 270 - 297,
The tests only exercise perform_create; add two tests to cover perform_update
and perform_destroy: 1) implement test_member_role_update_retains_draft_access
that uses _add_member_and_assert_can_access_draft(self.user_basic,
BasicMember.__name__), then as self.superuser fetch the membership via
UserProjectMembership.objects.get(project=self.project, user=self.user_basic),
PATCH the membership at f"{self.members_url}{membership.pk}/" to change role_id
to Researcher.__name__ and assert 200, then authenticate as the user and assert
GET to self.detail_url returns 200 (verifying perform_update reassigns
BasicMember); 2) implement test_deleted_member_cannot_access_draft_project that
adds the member the same way, then as self.superuser delete the membership at
f"{self.members_url}{membership.pk}/", then authenticate as the user and assert
GET to self.detail_url returns 403 or 404 (verifying perform_destroy removes
draft access).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ami/users/tests/test_membership_management_api.py`:
- Line 260: Ruff flags the hardcoded password literal used when creating the
superuser (User.objects.create_superuser(password="x")), so replace the literal
with a single shared test password constant (e.g., TEST_PASSWORD = "x") defined
at the top of the test module and use that constant in
TestUserProjectMembershipAPI.setUp and here, or alternatively add a local noqa
comment to silence S106 for this line; update usages to reference TEST_PASSWORD
and ensure create_superuser(...) uses that symbol instead of the raw string.

---

Nitpick comments:
In `@ami/users/api/views.py`:
- Around line 63-66: The code unconditionally calls
BasicMember.assign_user(user, project) after assigning role_cls, causing a
redundant DB round-trip when role_cls is BasicMember; update both perform_create
and perform_update to skip the extra call by checking if role_cls is not
BasicMember (e.g., if role_cls is not BasicMember: BasicMember.assign_user(user,
project)) or otherwise only call BasicMember.assign_user when role_cls differs,
leaving the Role.__subclasses__ unassignment and role_cls.assign_user(user,
project) logic unchanged.

In `@ami/users/tests/test_membership_management_api.py`:
- Around line 270-297: The tests only exercise perform_create; add two tests to
cover perform_update and perform_destroy: 1) implement
test_member_role_update_retains_draft_access that uses
_add_member_and_assert_can_access_draft(self.user_basic, BasicMember.__name__),
then as self.superuser fetch the membership via
UserProjectMembership.objects.get(project=self.project, user=self.user_basic),
PATCH the membership at f"{self.members_url}{membership.pk}/" to change role_id
to Researcher.__name__ and assert 200, then authenticate as the user and assert
GET to self.detail_url returns 200 (verifying perform_update reassigns
BasicMember); 2) implement test_deleted_member_cannot_access_draft_project that
adds the member the same way, then as self.superuser delete the membership at
f"{self.members_url}{membership.pk}/", then authenticate as the user and assert
GET to self.detail_url returns 403 or 404 (verifying perform_destroy removes
draft access).

self.project.save()
create_roles_for_project(self.project)

self.superuser = User.objects.create_superuser(email="super@insectai.org", password="x")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Static analysis flag S106 (hardcoded test password) — consistent with existing pattern

Ruff flags password="x" at this line. The identical credential is already present on line 16 in the same file (TestUserProjectMembershipAPI.setUp), so this is consistent. No production risk; flagging for awareness since the linter will surface it in CI.

🧰 Tools
🪛 Ruff (0.15.1)

[error] 260-260: Possible hardcoded password assigned to argument: "password"

(S106)

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

In `@ami/users/tests/test_membership_management_api.py` at line 260, Ruff flags
the hardcoded password literal used when creating the superuser
(User.objects.create_superuser(password="x")), so replace the literal with a
single shared test password constant (e.g., TEST_PASSWORD = "x") defined at the
top of the test module and use that constant in
TestUserProjectMembershipAPI.setUp and here, or alternatively add a local noqa
comment to silence S106 for this line; update usages to reference TEST_PASSWORD
and ensure create_superuser(...) uses that symbol instead of the raw string.

@mohamedelabbas1996 mohamedelabbas1996 marked this pull request as draft February 19, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments