6009: add organizations to invitation querysets and sync#6039
6009: add organizations to invitation querysets and sync#6039yasinelmi wants to merge 3 commits into
Conversation
Extends the channel-invitation flow to support organization invitations, per learningequality#6009 (following learningequality#6008's Invitation.organization field): - Add Organization.filter_edit_queryset/filter_view_queryset based on active OrganizationRole membership, and extend Invitation's equivalents to grant org admins edit access and org members view access. - Allow InvitationSerializer to accept an organization in place of (or alongside) a channel, requiring at least one of the two. - Teach the /sync endpoint's permission gate to recognize organization- scoped changes, since org admins acting on another user's invitation don't fit the existing channel-scoped or self-scoped checks.
|
👋 Hi @yasinelmi, thanks for contributing! For the review process to begin, please verify that the following is satisfied:
Also check that issue requirements are satisfied & you ran Pull requests that don't follow the guidelines will be closed. Reviewer assignment can take up to 2 weeks. |
…tures - generate_create_event/generate_update_event/generate_delete_event (viewsets/sync/utils.py) never gained an organization_id parameter when handle_changes() was taught to read it, causing a TypeError in every organization invitation test that passed organization_id. - Two OrganizationInvitationSyncTestCase fixtures were missing invited=self.invited_user, which InvitationSerializer.get_fields() requires to unlock the accepted field for sync-based updates, matching the existing SyncTestCase fixture pattern.
Per the "Organizations XOR channels" requirement from learningequality#5971 and rtibbles' comment on learningequality#6009 confirming this was deliberately deferred to the API layer: InvitationSerializer.validate() now rejects an invitation that has both channel and organization set, not just one that has neither. This invalidates the "co-owner" scenario (one invitation with both fields set) the earlier test exercised, so that test is replaced with one asserting the combination is rejected.
Summary
Following #6008 (which added
Invitation.organization), this extends the invitation querysets and the/syncmechanism to actually support organization invitations end-to-end, per #6009.Organization.filter_edit_queryset/filter_view_queryset— new classmethods based on activeOrganizationRolemembership (admin = edit, admin/editor/viewer = view), mirroring the existingChannelpattern.Invitation.filter_edit_queryset/filter_view_queryset— extended to grant the same access to organization invitations.InvitationSerializer— acceptsorganizationin place ofchannel;channelis now optional. Validation requires exactly one ofchannel/organization(not neither, not both) — this is the "Organizations XOR channels" requirement from Add Organizations to Invitation Model #5971 that was deliberately deferred to this API-layer work (per rtibbles' comment on Add Organizations to Invitation API Route (And Sync Events) #6009 and the review thread on 5971: added organizations to invitation model #6008)./syncendpoint (handle_changes) — organization-scoped changes (e.g. an org admin creating/revoking an invitation for someone else) didn't fit either existing permission branch (channel-scoped or self-scoped), so this adds a permission check viaOrganization.filter_edit_querysetand routes allowed org-scoped changes through the existing per-user change queue, enqueuingapply_user_changes_taskfor the actual target user rather than just the requester.generate_create_event/generate_update_event/generate_delete_event(viewsets/sync/utils.py) gained anorganization_idparameter so events can carry it, matchingchannel_id.Scope note: this reuses the existing per-user
Changeprocessing queue rather than adding a dedicatedorganization_idfield/task to theChangemodel (which would mirrorchannel_id's three roles: permission gate, task routing, and broadcast). That means an org admin creating/revoking an invitation is fully functional and the invitee gets realtime sync, but other org admins won't see it appear live without a page refresh (the list view always reflects live DB state regardless). Full parity with channel-scoped broadcast would need aChange.organizationmigration + dedicated task, which felt like more than this issue's "Low" complexity warranted, and is expected to overlap with #5967's org permission infrastructure.Testing
test_models.py(OrganizationTestCase,InvitationOrganizationTestCase) covering the queryset permission logic directly.test_invitation.py(OrganizationInvitationSyncTestCase) covering create/accept/revoke via/sync, non-admin rejection, the "requires at least one of channel/organization" validation, and a new test asserting an invitation with both channel and organization set is rejected (mutual exclusivity).References
Closes #6009. Builds on #6008 (Invitation.organization field) and #5953 (Organization/OrganizationRole models).
AI usage
Implemented with Claude Code: I worked through the codebase with it to trace how the existing channel-invitation sync mechanism works end-to-end (
handle_changes,Changemodel,apply_channel_changes_task/apply_user_changes_task), identified that organization-scoped changes didn't fit either existing permission branch, and reviewed/directed the resulting implementation and test coverage rather than accepting it as-is. The channel/organization mutual-exclusivity requirement was added after re-reading the #6008 review thread and rtibbles' follow-up comment on #6009 flagging it as still outstanding. This PR is a draft pending review, including coordination with #5967's assignee on the sync-scoping approach described above.