Skip to content

feat: add Invitations tab API and Cancelled invitation state - #71

Open
ccantillo wants to merge 4 commits into
mainfrom
crls/feat/pending-invitations-learners-tab
Open

feat: add Invitations tab API and Cancelled invitation state#71
ccantillo wants to merge 4 commits into
mainfrom
crls/feat/pending-invitations-learners-tab

Conversation

@ccantillo

Copy link
Copy Markdown
Contributor

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 Cancelled invitation
state, resend and cancel actions on the invitation viewset, a
pending_invitations count for the seat-usage breakdown, and a custom exception
handler that emits machine-readable error codes to the learner MFE.

Two pre-existing bugs are also fixed here:

  • CatalogLearnerInvitationViewSet defined get_permission_classes() — which is
    not a DRF hook — so the intended IsPartnerCatalogManager restriction was never
    applied.
  • _validate_invitation_status_access blocked ordinary CatalogManager users from
    removing invitations, so manager-initiated removal (and any future cancel) silently
    failed.

What changed

partner_catalog/models.py

  • Added Status.CANCELLED = 50.
  • Added cancelled_at (DateTimeField) and cancelled_by (ForeignKey(User)) fields.
  • Extended _compute_status() precedence: removed_at → cancelled_at → declined_at → accepted_at → SENT.
  • Added cla_cancelled_not_accepted check constraint (cancelled_at IS NULL OR accepted_at IS NULL).
  • Added index on 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.py

  • Added EVENT_NAME_INVITATION_CANCELLED and XAPI_VERB_CANCELLED.

partner_catalog/events/signals.py

  • Registered CATALOG_LEARNER_INVITATION_CANCELLED_V1 signal.

partner_catalog/services/invitations.py

  • Added CANCELLED to _EVENT_MAP, _TRACKING_EVENT_MAP, and ALLOWED_TRANSITIONS.
  • Added cancel_invitation(invitation_id, user) — transitions via _transition_status.
  • Added resend_invitation(invitation_id, user) — validates SENT, enqueues email task, does not update invited_at.
  • Fixed _validate_invitation_status_access: active CatalogManager users can now cancel or remove invitations.

partner_catalog/api/v1/serializers.py

  • Added CatalogInvitationListSerializer: read-only, returns stable machine keys
    (pending for SENT), resolves is_registered and username / full_name
    by looking up the account via invite_email at read time (avoids mislabelling
    users who registered after receiving an invite).
  • Added pending_invitations annotated field to PartnerCatalogSerializer.

partner_catalog/api/v1/views.py

  • Fixed permission_classes (replaced dead get_permission_classes() method).
  • Added CSVExportMixin, filterset_fields, search_fields, ordering_fields.
  • Added resend and cancel detail @action endpoints.
  • get_serializer_class() returns CatalogInvitationListSerializer for list / resend / cancel.

partner_catalog/api/exception_handlers.py (new)

Custom DRF exception handler that adds a code field (from default_code) to the
response body alongside the existing detail. Scoped to this plugin — does not
touch the global REST_FRAMEWORK["EXCEPTION_HANDLER"] setting.

partner_catalog/api/v1/learner_views.py

  • Wired catalog_exception_handler via get_exception_handler() on LearnerCatalogViewSet and LearnerCatalogCourseViewSet.

tests/test_invitations_tab.py (new)

16 tests covering:

  • SENT → CANCELLED transition; rejection from ACCEPTED / DECLINED / REMOVED
  • Re-invite after cancel succeeds (unique constraint not hit)
  • Resend enqueues email for SENT; rejected otherwise
  • Ordinary CatalogManager (not staff) can cancel and remove
  • is_registered is True for a user who registered after being invited
  • Exception handler returns code alongside detail
  • List endpoint, status filter, search, cancel action, resend action

@ccantillo
ccantillo marked this pull request as draft August 19, 2026 22:46
@ccantillo
ccantillo marked this pull request as ready for review September 3, 2026 18:01
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