<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://ohcnwiki.tellmey.fyi/index.php?action=history&amp;feed=atom&amp;title=References%2FRole</id>
	<title>References/Role - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://ohcnwiki.tellmey.fyi/index.php?action=history&amp;feed=atom&amp;title=References%2FRole"/>
	<link rel="alternate" type="text/html" href="https://ohcnwiki.tellmey.fyi/index.php?title=References/Role&amp;action=history"/>
	<updated>2026-07-06T05:22:52Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.4</generator>
	<entry>
		<id>https://ohcnwiki.tellmey.fyi/index.php?title=References/Role&amp;diff=429&amp;oldid=prev</id>
		<title>Admin: Automated edit (via update-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://ohcnwiki.tellmey.fyi/index.php?title=References/Role&amp;diff=429&amp;oldid=prev"/>
		<updated>2026-07-06T04:08:15Z</updated>

		<summary type="html">&lt;p&gt;Automated edit (via update-page on MediaWiki MCP Server)&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 09:38, 6 July 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 328:&lt;/td&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 328:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* To change a role&#039;s membership, write &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; rows rather than editing &amp;lt;code&amp;gt;RoleModel&amp;lt;/code&amp;gt; directly. Use the ORM so the cache-invalidation signal fires; raw SQL leaves the Redis cache stale.&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* To change a role&#039;s membership, write &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; rows rather than editing &amp;lt;code&amp;gt;RoleModel&amp;lt;/code&amp;gt; directly. Use the ORM so the cache-invalidation signal fires; raw SQL leaves the Redis cache stale.&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* Other access-control models (organization memberships, patient/encounter associations) reference roles to resolve a user&#039;s effective permissions in a given context.&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* Other access-control models (organization memberships, patient/encounter associations) reference roles to resolve a user&#039;s effective permissions in a given context.&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-empty diff-side-deleted&quot;&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-empty diff-side-deleted&quot;&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{Navbox access governance}}&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{Related}}&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{Related}}&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-empty diff-side-deleted&quot;&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Wiki: ohcnwiki.tellmey.fyi&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Admin</name></author>
	</entry>
	<entry>
		<id>https://ohcnwiki.tellmey.fyi/index.php?title=References/Role&amp;diff=131&amp;oldid=prev</id>
		<title>Admin: OHC identity seed</title>
		<link rel="alternate" type="text/html" href="https://ohcnwiki.tellmey.fyi/index.php?title=References/Role&amp;diff=131&amp;oldid=prev"/>
		<updated>2026-07-04T22:43:57Z</updated>

		<summary type="html">&lt;p&gt;OHC identity seed&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Doc header&lt;br /&gt;
|type=reference&lt;br /&gt;
|domain=access-governance&lt;br /&gt;
|title=Role&lt;br /&gt;
|order=3&lt;br /&gt;
|introduced=3.0&lt;br /&gt;
|model=Role&lt;br /&gt;
|reference=References/Base models &amp;amp;amp; conventions&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
A role is a flat set of permissions granted to a user inside one organizational boundary. It carries no semantics of its own — &amp;amp;quot;Doctor&amp;amp;quot;, &amp;amp;quot;Doctor Read Only&amp;amp;quot; and &amp;amp;quot;Doctor Scheduler&amp;amp;quot; are three unrelated permission sets, not variations on a job title. A user can hold roles in many organizations but only one role per organization chain. &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; join rows expand a role into its effective permission list.&lt;br /&gt;
&lt;br /&gt;
Two layers back the concept. The Django model (&amp;lt;code&amp;gt;care/security/models/role.py&amp;lt;/code&amp;gt;) is storage: &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;description&amp;lt;/code&amp;gt;, flags, and the &amp;lt;code&amp;gt;contexts&amp;lt;/code&amp;gt; array. The Pydantic resource specs (&amp;lt;code&amp;gt;care/emr/resources/role/spec.py&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;care/emr/resources/permissions.py&amp;lt;/code&amp;gt;) define the API read/write schemas, bind &amp;lt;code&amp;gt;contexts&amp;lt;/code&amp;gt; to an enum, type the permissions field, and hold every create/update validation rule.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Source:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
* Model: [&amp;lt;nowiki/&amp;gt;https://github.com/ohcnetwork/care/blob/develop/care/security/models/role.py &amp;lt;code&amp;gt;care/security/models/role.py&amp;lt;/code&amp;gt;]&lt;br /&gt;
* Spec: [&amp;lt;nowiki/&amp;gt;https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/role/spec.py &amp;lt;code&amp;gt;care/emr/resources/role/spec.py&amp;lt;/code&amp;gt;]&lt;br /&gt;
* Permissions mixins: [&amp;lt;nowiki/&amp;gt;https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/permissions.py &amp;lt;code&amp;gt;care/emr/resources/permissions.py&amp;lt;/code&amp;gt;]&lt;br /&gt;
* Role definitions: [&amp;lt;nowiki/&amp;gt;https://github.com/ohcnetwork/care/blob/develop/care/security/roles/role.py &amp;lt;code&amp;gt;care/security/roles/role.py&amp;lt;/code&amp;gt;]&lt;br /&gt;
* Permission registry: [&amp;lt;nowiki/&amp;gt;https://github.com/ohcnetwork/care/blob/develop/care/security/permissions/base.py &amp;lt;code&amp;gt;care/security/permissions/base.py&amp;lt;/code&amp;gt;]&lt;br /&gt;
&lt;br /&gt;
== Models ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Model&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RoleModel&amp;lt;/code&amp;gt;&lt;br /&gt;
| A named role grouping a set of permissions&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt;&lt;br /&gt;
| Join table linking a &amp;lt;code&amp;gt;RoleModel&amp;lt;/code&amp;gt; to a &amp;lt;code&amp;gt;PermissionModel&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Both extend &amp;lt;code&amp;gt;BaseModel&amp;lt;/code&amp;gt; (see [[References/Base models &amp;amp;amp; conventions|Base model]]), so they get &amp;lt;code&amp;gt;external_id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;created_date&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;modified_date&amp;lt;/code&amp;gt;, and soft-delete via &amp;lt;code&amp;gt;deleted&amp;lt;/code&amp;gt; — but none of the &amp;lt;code&amp;gt;created_by&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;updated_by&amp;lt;/code&amp;gt;/history/meta fields that &amp;lt;code&amp;gt;EMRBaseModel&amp;lt;/code&amp;gt; adds.&lt;br /&gt;
&lt;br /&gt;
== &amp;lt;code&amp;gt;RoleModel&amp;lt;/code&amp;gt; fields ==&lt;br /&gt;
&lt;br /&gt;
User-created roles can be deleted by anyone holding the right permission. System roles (&amp;lt;code&amp;gt;is_system=True&amp;lt;/code&amp;gt;) are exempt: the API blocks creating, updating, or deleting them.&lt;br /&gt;
&lt;br /&gt;
=== Identity &amp;amp;amp; description ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Required&lt;br /&gt;
! Default&lt;br /&gt;
! Notes&lt;br /&gt;
{{Field|model=Role|section=Identity &amp;amp;amp; description|name=name|type=CharField(1024)|notes=yes}}&lt;br /&gt;
{{Field|model=Role|section=Identity &amp;amp;amp; description|name=description|type=TextField|notes=no}}&lt;br /&gt;
{{Field|model=Role|section=Identity &amp;amp;amp; description|name=contexts|type=ArrayField[CharField(24)]|notes=yes}}&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Status flags ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Required&lt;br /&gt;
! Default&lt;br /&gt;
! Notes&lt;br /&gt;
{{Field|model=Role|section=Status flags|name=is_system|type=BooleanField|notes=no}}&lt;br /&gt;
{{Field|model=Role|section=Status flags|name=temp_deleted|type=BooleanField|notes=no}}&lt;br /&gt;
{{Field|model=Role|section=Status flags|name=is_archived|type=BooleanField|notes=no}}&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Meta / constraints ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; is unique only among non-deleted rows, enforced by a partial constraint:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;UniqueConstraint(fields=[&amp;quot;name&amp;quot;], condition=Q(deleted=False), name=&amp;quot;unique_name_if_not_deleted&amp;quot;)&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;RoleCreateSpec&amp;lt;/code&amp;gt; checks the same uniqueness case-insensitively (&amp;lt;code&amp;gt;name__iexact&amp;lt;/code&amp;gt;) before the request ever reaches the DB constraint.&lt;br /&gt;
&lt;br /&gt;
== Related models ==&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
Each row connects a role to one permission; a role accumulates its grants across many rows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;role         → FK RoleModel (CASCADE, required)&lt;br /&gt;
permission   → FK PermissionModel (CASCADE, required)&lt;br /&gt;
temp_deleted → BooleanField (default False)&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;temp_deleted&amp;lt;/code&amp;gt; excludes a grant from the role without deleting the row — permission lookups filter on &amp;lt;code&amp;gt;rolepermission__temp_deleted=False&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;PermissionModel&amp;lt;/code&amp;gt; ===&lt;br /&gt;
&lt;br /&gt;
The permission a &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; points at ([[References/Permission]]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;slug         → CharField(1024) unique, indexed&lt;br /&gt;
name         → CharField(1024)&lt;br /&gt;
description  → TextField (default &amp;quot;&amp;quot;)&lt;br /&gt;
context      → CharField(1024)   # one of the PermissionContext values&lt;br /&gt;
temp_deleted → BooleanField (default False)&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The set of available permissions lives in code, not as fixed DB rows. &amp;lt;code&amp;gt;PermissionController&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;care/security/permissions/base.py&amp;lt;/code&amp;gt;) registers them by aggregating per-domain &amp;lt;code&amp;gt;enum.Enum&amp;lt;/code&amp;gt; handlers such as &amp;lt;code&amp;gt;PatientPermissions&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FacilityPermissions&amp;lt;/code&amp;gt;, where each member&amp;#039;s value is a &amp;lt;code&amp;gt;Permission(name, description, context, roles)&amp;lt;/code&amp;gt; dataclass.&lt;br /&gt;
&lt;br /&gt;
== Enum / value tables ==&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;RoleContext&amp;lt;/code&amp;gt; values ===&lt;br /&gt;
&lt;br /&gt;
Each element of &amp;lt;code&amp;gt;contexts&amp;lt;/code&amp;gt; is validated against this enum (&amp;lt;code&amp;gt;care/security/roles/role.py&amp;lt;/code&amp;gt;). These name the organizational boundary a role can apply to — a different axis from &amp;lt;code&amp;gt;PermissionContext&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Value&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;&lt;br /&gt;
| Role applies within a facility&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;GOVT_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
| Role applies within a government organization&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ROLE_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
| Role applies within a role (user-group) organization&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;PermissionContext&amp;lt;/code&amp;gt; values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;PermissionModel.context&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;Permission.context&amp;lt;/code&amp;gt; is one of these (&amp;lt;code&amp;gt;care/security/permissions/constants.py&amp;lt;/code&amp;gt;). The &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; mixins below filter grants by context to resolve a user&amp;#039;s effective permissions.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;GENERIC&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;PATIENT&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;QUESTIONNAIRE&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ORGANIZATION&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY_ORGANIZATION&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ENCOUNTER&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;code&amp;gt;PermissionEnum&amp;lt;/code&amp;gt; (write field for &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt;) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;RoleCreateSpec.permissions&amp;lt;/code&amp;gt; is typed against &amp;lt;code&amp;gt;PermissionController.get_enum()&amp;lt;/code&amp;gt;, a &amp;lt;code&amp;gt;str&amp;lt;/code&amp;gt; enum built at runtime from every registered permission &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; across all handlers. Values are permission identifiers (slugs) like &amp;lt;code&amp;gt;can_create_patient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;can_write_patient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;can_list_patients&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;can_view_clinical_data&amp;lt;/code&amp;gt;. The set is the union of &amp;lt;code&amp;gt;PermissionController.internal_permission_handlers&amp;lt;/code&amp;gt; plus anything plugins register, so it varies by deployment.&lt;br /&gt;
&lt;br /&gt;
=== Built-in (&amp;lt;code&amp;gt;is_system&amp;lt;/code&amp;gt;) roles ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;RoleController&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;care/security/roles/role.py&amp;lt;/code&amp;gt;) seeds these with &amp;lt;code&amp;gt;is_system=True&amp;lt;/code&amp;gt;. None can be created, updated, or deleted through the API.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Role&lt;br /&gt;
! Contexts&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Doctor&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GOVT_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Nurse&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GOVT_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Staff&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GOVT_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Volunteer&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GOVT_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Pharmacist&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Administrator&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GOVT_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Facility Admin&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Admin&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;FACILITY&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GOVT_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Admin&amp;lt;/code&amp;gt; (role org)&lt;br /&gt;
| &amp;lt;code&amp;gt;ROLE_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Manager&amp;lt;/code&amp;gt; (role org)&lt;br /&gt;
| &amp;lt;code&amp;gt;ROLE_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Member&amp;lt;/code&amp;gt; (role org)&lt;br /&gt;
| &amp;lt;code&amp;gt;ROLE_ORG&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Resource specs (API schema) ==&lt;br /&gt;
&lt;br /&gt;
Every spec extends &amp;lt;code&amp;gt;EMRResource&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;care/emr/resources/base.py&amp;lt;/code&amp;gt;), which supplies &amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt; (DB → Pydantic, via &amp;lt;code&amp;gt;perform_extra_serialization&amp;lt;/code&amp;gt;) and &amp;lt;code&amp;gt;de_serialize&amp;lt;/code&amp;gt; (Pydantic → DB, via &amp;lt;code&amp;gt;perform_extra_deserialization&amp;lt;/code&amp;gt;). &amp;lt;code&amp;gt;RoleBaseSpec&amp;lt;/code&amp;gt; sets &amp;lt;code&amp;gt;__exclude__ = [&amp;amp;quot;permissions&amp;amp;quot;]&amp;lt;/code&amp;gt; so each subclass declares &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; itself instead of inheriting it.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Spec class&lt;br /&gt;
! Role&lt;br /&gt;
! Fields&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RoleBaseSpec&amp;lt;/code&amp;gt;&lt;br /&gt;
| shared base&lt;br /&gt;
| &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;description&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;is_system&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;is_archived&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;contexts&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RoleCreateSpec&amp;lt;/code&amp;gt;&lt;br /&gt;
| write · create &amp;amp;amp; update&lt;br /&gt;
| base fields + &amp;lt;code&amp;gt;permissions: list[PermissionEnum]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RoleReadSpec&amp;lt;/code&amp;gt;&lt;br /&gt;
| read · detail/list&lt;br /&gt;
| base fields + &amp;lt;code&amp;gt;permissions: list[PermissionSpec]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RoleReadMinimalSpec&amp;lt;/code&amp;gt;&lt;br /&gt;
| read · minimal/embedded&lt;br /&gt;
| base fields only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;PermissionSpec&amp;lt;/code&amp;gt;&lt;br /&gt;
| nested (read)&lt;br /&gt;
| &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;description&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;slug&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;SlugType&amp;lt;/code&amp;gt;), &amp;lt;code&amp;gt;context&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Field shapes &amp;amp;amp; bindings ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Field&lt;br /&gt;
! Spec type&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;UUID4 | None&amp;lt;/code&amp;gt;&lt;br /&gt;
| The role&amp;#039;s &amp;lt;code&amp;gt;external_id&amp;lt;/code&amp;gt; on read (set in &amp;lt;code&amp;gt;perform_extra_serialization&amp;lt;/code&amp;gt;); ignored on write&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;str | None&amp;lt;/code&amp;gt;&lt;br /&gt;
| Required and non-blank on create; case-insensitive uniqueness enforced&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;description&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;str | None&amp;lt;/code&amp;gt;&lt;br /&gt;
| Optional&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;is_system&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;bool | None&amp;lt;/code&amp;gt; (default &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;)&lt;br /&gt;
| Must be falsy on write — &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; is rejected&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;is_archived&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;bool | None&amp;lt;/code&amp;gt; (default &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt;)&lt;br /&gt;
| —&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;contexts&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;list[RoleContext]&amp;lt;/code&amp;gt;&lt;br /&gt;
| Bound to the &amp;lt;code&amp;gt;RoleContext&amp;lt;/code&amp;gt; enum&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; (write)&lt;br /&gt;
| &amp;lt;code&amp;gt;list[PermissionEnum]&amp;lt;/code&amp;gt; (default &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt;)&lt;br /&gt;
| Dynamic &amp;lt;code&amp;gt;str&amp;lt;/code&amp;gt; enum of every registered permission name; must contain ≥ 1 entry&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; (read)&lt;br /&gt;
| &amp;lt;code&amp;gt;list[PermissionSpec]&amp;lt;/code&amp;gt;&lt;br /&gt;
| Resolved server-side from the role&amp;#039;s active grants&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;PermissionSpec.slug&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;SlugType&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;str&amp;lt;/code&amp;gt;, length 5–50, pattern &amp;lt;code&amp;gt;^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Validation (&amp;lt;code&amp;gt;RoleCreateSpec.validate_role&amp;lt;/code&amp;gt;, mode=&amp;amp;quot;after&amp;amp;quot;) ===&lt;br /&gt;
&lt;br /&gt;
The validator runs on both create and update; it reads the &amp;lt;code&amp;gt;is_update&amp;lt;/code&amp;gt; flag and the target &amp;lt;code&amp;gt;object&amp;lt;/code&amp;gt; from the serializer context.&lt;br /&gt;
&lt;br /&gt;
* On create, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; must be present and non-blank (&amp;lt;code&amp;gt;&amp;amp;quot;Role name cannot be empty&amp;amp;quot;&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; must be unique case-insensitively (&amp;lt;code&amp;gt;name__iexact&amp;lt;/code&amp;gt;), excluding the current row on update. A duplicate raises &amp;lt;code&amp;gt;&amp;amp;quot;Role with this name already exists&amp;amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Updating a role with &amp;lt;code&amp;gt;is_system=True&amp;lt;/code&amp;gt; raises &amp;lt;code&amp;gt;&amp;amp;quot;Cannot update system roles&amp;amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;is_system=True&amp;lt;/code&amp;gt; in the payload raises &amp;lt;code&amp;gt;&amp;amp;quot;Cannot create system roles&amp;amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; must be non-empty, otherwise &amp;lt;code&amp;gt;&amp;amp;quot;At least one permission must be assigned to the role&amp;amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; is de-duplicated (&amp;lt;code&amp;gt;list(set(...))&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Server-side serialization behaviour ===&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Write&amp;#039;&amp;#039;&amp;#039; (&amp;lt;code&amp;gt;RoleCreateSpec.perform_extra_deserialization&amp;lt;/code&amp;gt;): the validated &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; list is attached to the instance as &amp;lt;code&amp;gt;obj.permissions&amp;lt;/code&amp;gt;, a transient attribute — &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; is in &amp;lt;code&amp;gt;__exclude__&amp;lt;/code&amp;gt; and isn&amp;#039;t a model column. The view/service layer materializes it into &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; rows.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Read detail&amp;#039;&amp;#039;&amp;#039; (&amp;lt;code&amp;gt;RoleReadSpec.perform_extra_serialization&amp;lt;/code&amp;gt;): sets &amp;lt;code&amp;gt;id = obj.external_id&amp;lt;/code&amp;gt; and fills &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;obj.get_permissions_for_role()&amp;lt;/code&amp;gt;, the cached &amp;lt;code&amp;gt;{name, slug, context, description}&amp;lt;/code&amp;gt; list for every active (non-&amp;lt;code&amp;gt;temp_deleted&amp;lt;/code&amp;gt;) grant.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Read minimal&amp;#039;&amp;#039;&amp;#039; (&amp;lt;code&amp;gt;RoleReadMinimalSpec.perform_extra_serialization&amp;lt;/code&amp;gt;): sets &amp;lt;code&amp;gt;id = obj.external_id&amp;lt;/code&amp;gt; and nothing else; &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; is omitted.&lt;br /&gt;
&lt;br /&gt;
=== Permission resolution mixins ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;care/emr/resources/permissions.py&amp;lt;/code&amp;gt; defines mixins that other resource specs inherit to expose the &amp;#039;&amp;#039;&amp;#039;calling user&amp;#039;s&amp;#039;&amp;#039;&amp;#039; effective permissions on an object — not the role&amp;#039;s own permission list. They populate &amp;lt;code&amp;gt;mapping[&amp;amp;quot;permissions&amp;amp;quot;]&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;perform_extra_user_serialization&amp;lt;/code&amp;gt;, but only when an authenticated &amp;lt;code&amp;gt;user&amp;lt;/code&amp;gt; is passed to &amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Mixin&lt;br /&gt;
! Resolves&lt;br /&gt;
! Context filter&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;PermissionsMixin&amp;lt;/code&amp;gt;&lt;br /&gt;
| base — adds &amp;lt;code&amp;gt;permissions: list[str]&amp;lt;/code&amp;gt;&lt;br /&gt;
| —&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;PatientPermissionsMixin&amp;lt;/code&amp;gt;&lt;br /&gt;
| roles the user holds on a patient&lt;br /&gt;
| &amp;lt;code&amp;gt;permission__context in (&amp;amp;quot;PATIENT&amp;amp;quot;, &amp;amp;quot;FACILITY&amp;amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;EncounterPermissionsMixin&amp;lt;/code&amp;gt;&lt;br /&gt;
| roles the user holds on an encounter&lt;br /&gt;
| &amp;lt;code&amp;gt;permission__context in (&amp;amp;quot;ENCOUNTER&amp;amp;quot;, &amp;amp;quot;PATIENT&amp;amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FacilityPermissionsMixin&amp;lt;/code&amp;gt;&lt;br /&gt;
| roles on facility root + sub-orgs; also exposes &amp;lt;code&amp;gt;root_org_permissions&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;child_org_permissions&amp;lt;/code&amp;gt; (child set excludes &amp;lt;code&amp;gt;can_update_facility&amp;lt;/code&amp;gt;)&lt;br /&gt;
| none (root) / excludes &amp;lt;code&amp;gt;can_update_facility&amp;lt;/code&amp;gt; (child)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Methods &amp;amp;amp; save behaviour ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;RoleModel&amp;lt;/code&amp;gt; resolves its effective permissions through two cached helpers backed by the Django cache (Redis) with a 7-day TTL:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;get_permission_sk_for_role() → list[str]&amp;lt;/code&amp;gt; — the &amp;lt;code&amp;gt;slug&amp;lt;/code&amp;gt; of every active permission on the role. Cache key &amp;lt;code&amp;gt;role_permissions_cache:{id}&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;get_permissions_for_role() → list[dict]&amp;lt;/code&amp;gt; — full detail (&amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;slug&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;context&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;description&amp;lt;/code&amp;gt;) for every active permission. Cache key &amp;lt;code&amp;gt;role_permissions:{id}&amp;lt;/code&amp;gt;. Used by &amp;lt;code&amp;gt;RoleReadSpec&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Both queries join through &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; and exclude &amp;lt;code&amp;gt;temp_deleted=True&amp;lt;/code&amp;gt; grants.&lt;br /&gt;
&lt;br /&gt;
=== Cache invalidation signal ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;invalidate_role_permissions_cache&amp;lt;/code&amp;gt;, a &amp;lt;code&amp;gt;@receiver([post_save, post_delete], sender=RolePermission)&amp;lt;/code&amp;gt; handler, clears both cache keys for the affected &amp;lt;code&amp;gt;role_id&amp;lt;/code&amp;gt; on every create, update, or delete of a &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt;. ORM writes invalidate the cache automatically; raw SQL writes skip the signal and leave stale caches behind.&lt;br /&gt;
&lt;br /&gt;
== API integration notes ==&lt;br /&gt;
&lt;br /&gt;
* Both create and update go through &amp;lt;code&amp;gt;RoleCreateSpec&amp;lt;/code&amp;gt;. &amp;lt;code&amp;gt;permissions&amp;lt;/code&amp;gt; is required (≥ 1) and de-duplicated; you never set &amp;lt;code&amp;gt;is_system&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;True&amp;lt;/code&amp;gt; is rejected.&lt;br /&gt;
* Reads use &amp;lt;code&amp;gt;RoleReadSpec&amp;lt;/code&amp;gt; (full, with nested &amp;lt;code&amp;gt;PermissionSpec[]&amp;lt;/code&amp;gt;) or &amp;lt;code&amp;gt;RoleReadMinimalSpec&amp;lt;/code&amp;gt; (no permissions), depending on the endpoint.&lt;br /&gt;
* To change a role&amp;#039;s membership, write &amp;lt;code&amp;gt;RolePermission&amp;lt;/code&amp;gt; rows rather than editing &amp;lt;code&amp;gt;RoleModel&amp;lt;/code&amp;gt; directly. Use the ORM so the cache-invalidation signal fires; raw SQL leaves the Redis cache stale.&lt;br /&gt;
* Other access-control models (organization memberships, patient/encounter associations) reference roles to resolve a user&amp;#039;s effective permissions in a given context.&lt;br /&gt;
&lt;br /&gt;
{{Related}}&lt;/div&gt;</summary>
		<author><name>Admin</name></author>
	</entry>
</feed>