feat: add Invitations tab API and Cancelled invitation state - #71
Open
ccantillo wants to merge 4 commits into
Open
feat: add Invitations tab API and Cancelled invitation state#71ccantillo wants to merge 4 commits into
ccantillo wants to merge 4 commits into
Conversation
ccantillo
marked this pull request as draft
August 19, 2026 22:46
ccantillo
marked this pull request as ready for review
September 3, 2026 18:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Invitations tab — backend API and Cancelled state
Summary
Managers had no visibility over sent-but-unaccepted invitations. This PR adds the
server-side contract for a dedicated Invitations tab: a new
Cancelledinvitationstate,
resendandcancelactions on the invitation viewset, apending_invitationscount for the seat-usage breakdown, and a custom exceptionhandler that emits machine-readable error codes to the learner MFE.
Two pre-existing bugs are also fixed here:
CatalogLearnerInvitationViewSetdefinedget_permission_classes()— which isnot a DRF hook — so the intended
IsPartnerCatalogManagerrestriction was neverapplied.
_validate_invitation_status_accessblocked ordinaryCatalogManagerusers fromremoving invitations, so manager-initiated removal (and any future cancel) silently
failed.
What changed
partner_catalog/models.pyStatus.CANCELLED = 50.cancelled_at(DateTimeField) andcancelled_by(ForeignKey(User)) fields._compute_status()precedence:removed_at → cancelled_at → declined_at → accepted_at → SENT.cla_cancelled_not_acceptedcheck constraint (cancelled_at IS NULL OR accepted_at IS NULL).cancelled_at.partner_catalog/migrations/0010_add_cancelled_invitation_state.py(new)Additive-only migration. Does not touch any existing constraint.
partner_catalog/xapi/constants.pyEVENT_NAME_INVITATION_CANCELLEDandXAPI_VERB_CANCELLED.partner_catalog/events/signals.pyCATALOG_LEARNER_INVITATION_CANCELLED_V1signal.partner_catalog/services/invitations.pyCANCELLEDto_EVENT_MAP,_TRACKING_EVENT_MAP, andALLOWED_TRANSITIONS.cancel_invitation(invitation_id, user)— transitions via_transition_status.resend_invitation(invitation_id, user)— validatesSENT, enqueues email task, does not updateinvited_at._validate_invitation_status_access: activeCatalogManagerusers can now cancel or remove invitations.partner_catalog/api/v1/serializers.pyCatalogInvitationListSerializer: read-only, returns stable machine keys(
pendingforSENT), resolvesis_registeredandusername/full_nameby looking up the account via
invite_emailat read time (avoids mislabellingusers who registered after receiving an invite).
pending_invitationsannotated field toPartnerCatalogSerializer.partner_catalog/api/v1/views.pypermission_classes(replaced deadget_permission_classes()method).CSVExportMixin,filterset_fields,search_fields,ordering_fields.resendandcanceldetail@actionendpoints.get_serializer_class()returnsCatalogInvitationListSerializerfor list / resend / cancel.partner_catalog/api/exception_handlers.py(new)Custom DRF exception handler that adds a
codefield (fromdefault_code) to theresponse body alongside the existing
detail. Scoped to this plugin — does nottouch the global
REST_FRAMEWORK["EXCEPTION_HANDLER"]setting.partner_catalog/api/v1/learner_views.pycatalog_exception_handlerviaget_exception_handler()onLearnerCatalogViewSetandLearnerCatalogCourseViewSet.tests/test_invitations_tab.py(new)16 tests covering:
SENT → CANCELLEDtransition; rejection fromACCEPTED/DECLINED/REMOVEDSENT; rejected otherwiseCatalogManager(not staff) can cancel and removeis_registeredisTruefor a user who registered after being invitedcodealongsidedetail