PortalAccessGrant

Introduction #

The PortalAccessGrant model represents family portal access to a patient’s chart. Staff give a grant to a family member or another representative from one of the patient’s contacts. The grant names the portal login it was given to and its access level in each area of the chart: appointments, messaging, clinical, and billing.

Use it to show a patient or staff member who has access to a chart, to show a family member which charts they can use, or to read what a grant allows.

Each area’s level is "none", "view", or "act". Follow the level on your portal pages the way the built-in pages do:

LevelOn a portal page
noneHide the area
viewShow the area without its actions
actShow the area and allow its actions

Each level is a PortalAccessLevel, which you can import from canvas_sdk.v1.data or canvas_sdk.handlers.simple_api. Levels compare in order from NONE to ACT, and NONE is falsy, so if grant.billing: checks for any billing access. A stored level that the SDK doesn’t recognize reads as NONE.

A revoked or flagged grant stays as a record. A grant gives its login the chart only while revoked_at and flagged_at are both empty and the patient is active. PortalAccessGrant.objects.giving_access() returns only those grants.

Basic usage #

To list the logins that currently have access to a patient’s chart, filter the grants that give access:

from canvas_sdk.v1.data import PortalAccessGrant

grants = PortalAccessGrant.objects.giving_access().filter(
    patient__id="d7af3e356368446c85b40a5d6ff7288e",
)

A patient’s grants are also on the patient as patient.portal_grants, and the grants given to a login are on its CanvasUser as portal_grants. Both include revoked and flagged grants. Call giving_access() on them, such as patient.portal_grants.giving_access(), to keep only the grants that give access.

To find the grants that give a level in one area, filter on that area’s field:

from canvas_sdk.v1.data import PortalAccessGrant, PortalAccessLevel

grants = PortalAccessGrant.objects.giving_access().filter(
    patient__id="d7af3e356368446c85b40a5d6ff7288e",
    billing=PortalAccessLevel.ACT,
)

Reading the grant for a portal request #

In a SimpleAPI handler or WebSocket API, portal_access.grant is the grant that gives the acting family member or representative the chart. It’s None on a patient’s own chart.

from canvas_sdk.effects import Effect
from canvas_sdk.effects.simple_api import JSONResponse, Response
from canvas_sdk.handlers.simple_api import PatientSessionAuthMixin, SimpleAPIRoute

RELATIONSHIP_SYSTEM = "http://terminology.hl7.org/CodeSystem/v3-RoleCode"


class AccessSummary(PatientSessionAuthMixin, SimpleAPIRoute):
    PATH = "/access-summary"
    PORTAL_ACCESS = {}

    def get(self) -> list[Response | Effect]:
        grant = self.request.portal_access.grant
        if grant is None:
            return [JSONResponse({"own_chart": True})]

        relationship = grant.contact.categories.filter(category__system=RELATIONSHIP_SYSTEM)
        return [
            JSONResponse(
                {
                    "own_chart": False,
                    "relationship": [link.category.name for link in relationship],
                    "billing": grant.billing,
                }
            )
        ]

Reading the grant for a portal event #

Portal events and portal_menu_item applications name the chart’s patient and the acting login separately. The event’s target, or the user in an application’s context, is the patient whose chart the portal is open on, and self.event.actor is the login. Look up the grant from both:

from canvas_sdk.v1.data import PortalAccessGrant


def current_grant(actor, patient_id):
    return (
        PortalAccessGrant.objects.giving_access()
        .filter(user=actor.instance, patient__id=patient_id)
        .first()
    )

Call it as current_grant(self.event.actor, patient_id) from your handler. It returns None when the login is the patient’s own, or when the login’s grant was revoked or flagged.

Responding to a flagged grant #

A flag usually means the invite reached the wrong person. When a family member reports that they don’t know the patient, Canvas sets the grant’s flagged_at and opens a task so staff can correct the contact or remove the access. The task is on the patient’s chart, its creator is Canvas Bot, and its tag is portal-access-flagged. Canvas assigns it to the patient’s care team lead. When the patient has no care team lead or the lead is no longer active, Canvas assigns it to the staff member in granted_by. If that staff member isn’t active either, the task has no assignee.

Each flagged grant gets one task. If staff complete it by hand, Canvas doesn’t open another. Canvas completes the task when the grant is revoked, including when staff correct the contact and the access moves to the corrected person.

The task raises the usual task events: TASK_CREATED when Canvas opens it and TASK_COMPLETED when Canvas completes it. To act on a flag, such as alerting a team outside Canvas, respond to TASK_CREATED and check the task’s tag:

from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler
from canvas_sdk.v1.data import PortalAccessGrant
from canvas_sdk.v1.data.task import Task


class FlaggedAccessHandler(BaseHandler):
    RESPONDS_TO = [EventType.Name(EventType.TASK_CREATED)]

    def compute(self):
        task = Task.objects.get(id=self.target)
        if task.tag != "portal-access-flagged":
            return []

        flagged = PortalAccessGrant.objects.filter(
            patient=task.patient,
            flagged_at__isnull=False,
            revoked_at__isnull=True,
        )
        for grant in flagged:
            self.log(f"{grant.user.email} flagged access to patient {task.patient.id}")
        return []

Attributes #

PortalAccessGrant #

Field NameTypeDescription
idUUID 
dbidInteger 
createdDateTime 
modifiedDateTime 
patientPatientThe patient whose chart the grant gives access to
userCanvasUserThe portal login the grant was given to
contactPatientContactPersonThe patient’s contact the grant was given from. Empty once the contact is changed to a different person after the grant is revoked. After staff remove the contact from the chart, contact_id keeps its ID, but plugins can’t read the contact, and reading contact raises PatientContactPerson.DoesNotExist. Use contact_name and contact_relationship instead.
contact_nameStringThe contact’s name, saved when the contact is removed or changed. Empty while contact is the patient’s contact
contact_relationshipStringThe contact’s relationship to the patient, saved from its relationship categories when the contact is removed or changed. Empty while contact is the patient’s contact; read contact.categories instead
appointmentsPortalAccessLevelAccess level in appointments: none, view, or act
messagingPortalAccessLevelAccess level in messaging: none, view, or act
clinicalPortalAccessLevelAccess level in clinical records: none, view, or act
billingPortalAccessLevelAccess level in billing: none, view, or act
dob_verified_atDateTimeWhen the login confirmed the patient’s date of birth
dob_failed_attemptsIntegerHow many date of birth entries didn’t match
flagged_atDateTimeWhen the login reported that they don’t know the patient
granted_byStaffThe staff member who gave the grant
granted_atDateTimeWhen the grant was given
notified_atDateTimeWhen the login was notified of the grant
revoked_atDateTimeWhen the grant was revoked
revoked_byStaffThe staff member who revoked the grant
revoke_reasonStringThe reason given for revoking the grant