canvas 0.45.0__py3-none-any.whl → 0.46.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of canvas might be problematic. Click here for more details.

Files changed (62) hide show
  1. {canvas-0.45.0.dist-info → canvas-0.46.0.dist-info}/METADATA +3 -2
  2. {canvas-0.45.0.dist-info → canvas-0.46.0.dist-info}/RECORD +62 -57
  3. canvas_generated/messages/effects_pb2.py +2 -2
  4. canvas_generated/messages/effects_pb2.pyi +10 -0
  5. canvas_generated/messages/events_pb2.py +2 -2
  6. canvas_generated/messages/events_pb2.pyi +18 -0
  7. canvas_sdk/commands/commands/exam.py +2 -1
  8. canvas_sdk/commands/commands/immunization_statement.py +32 -0
  9. canvas_sdk/commands/commands/questionnaire/__init__.py +18 -3
  10. canvas_sdk/commands/commands/questionnaire/question.py +3 -2
  11. canvas_sdk/commands/commands/questionnaire/toggle_questions.py +68 -0
  12. canvas_sdk/commands/commands/review_of_systems.py +2 -1
  13. canvas_sdk/v1/data/__init__.py +17 -3
  14. canvas_sdk/v1/data/allergy_intolerance.py +16 -19
  15. canvas_sdk/v1/data/appointment.py +10 -14
  16. canvas_sdk/v1/data/assessment.py +9 -10
  17. canvas_sdk/v1/data/banner_alert.py +12 -12
  18. canvas_sdk/v1/data/base.py +45 -1
  19. canvas_sdk/v1/data/billing.py +13 -18
  20. canvas_sdk/v1/data/business_line.py +7 -8
  21. canvas_sdk/v1/data/care_team.py +14 -17
  22. canvas_sdk/v1/data/charge_description_master.py +29 -0
  23. canvas_sdk/v1/data/claim.py +87 -95
  24. canvas_sdk/v1/data/claim_line_item.py +17 -18
  25. canvas_sdk/v1/data/command.py +8 -9
  26. canvas_sdk/v1/data/condition.py +9 -12
  27. canvas_sdk/v1/data/coverage.py +47 -53
  28. canvas_sdk/v1/data/detected_issue.py +16 -20
  29. canvas_sdk/v1/data/device.py +20 -21
  30. canvas_sdk/v1/data/discount.py +8 -8
  31. canvas_sdk/v1/data/imaging.py +24 -30
  32. canvas_sdk/v1/data/invoice.py +3 -3
  33. canvas_sdk/v1/data/lab.py +65 -84
  34. canvas_sdk/v1/data/line_item_transaction.py +7 -9
  35. canvas_sdk/v1/data/medication.py +14 -17
  36. canvas_sdk/v1/data/message.py +10 -17
  37. canvas_sdk/v1/data/note.py +27 -36
  38. canvas_sdk/v1/data/observation.py +24 -33
  39. canvas_sdk/v1/data/organization.py +14 -15
  40. canvas_sdk/v1/data/patient.py +57 -68
  41. canvas_sdk/v1/data/patient_consent.py +14 -19
  42. canvas_sdk/v1/data/payment_collection.py +7 -8
  43. canvas_sdk/v1/data/payor_specific_charge.py +10 -12
  44. canvas_sdk/v1/data/posting.py +10 -18
  45. canvas_sdk/v1/data/practicelocation.py +17 -21
  46. canvas_sdk/v1/data/protocol_override.py +8 -10
  47. canvas_sdk/v1/data/questionnaire.py +56 -73
  48. canvas_sdk/v1/data/reason_for_visit.py +7 -9
  49. canvas_sdk/v1/data/staff.py +61 -57
  50. canvas_sdk/v1/data/task.py +21 -31
  51. canvas_sdk/v1/data/team.py +15 -18
  52. canvas_sdk/v1/data/user.py +3 -3
  53. canvas_sdk/v1/data/utils.py +6 -0
  54. plugin_runner/allowed-module-imports.json +1340 -0
  55. plugin_runner/generate_allowed_imports.py +97 -0
  56. plugin_runner/plugin_runner.py +9 -0
  57. plugin_runner/sandbox.py +50 -60
  58. protobufs/canvas_generated/messages/effects.proto +6 -0
  59. protobufs/canvas_generated/messages/events.proto +12 -1
  60. settings.py +56 -22
  61. {canvas-0.45.0.dist-info → canvas-0.46.0.dist-info}/WHEEL +0 -0
  62. {canvas-0.45.0.dist-info → canvas-0.46.0.dist-info}/entry_points.txt +0 -0
@@ -55,6 +55,8 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
55
55
  PRESCRIPTION_UPDATED: _ClassVar[EventType]
56
56
  REFERRAL_REPORT_CREATED: _ClassVar[EventType]
57
57
  REFERRAL_REPORT_UPDATED: _ClassVar[EventType]
58
+ STAFF_CREATED: _ClassVar[EventType]
59
+ STAFF_UPDATED: _ClassVar[EventType]
58
60
  TASK_COMMENT_CREATED: _ClassVar[EventType]
59
61
  TASK_CREATED: _ClassVar[EventType]
60
62
  TASK_LABELS_ADJUSTED: _ClassVar[EventType]
@@ -91,6 +93,8 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
91
93
  TASK_COMPLETED: _ClassVar[EventType]
92
94
  DETECTED_ISSUE_EVIDENCE_CREATED: _ClassVar[EventType]
93
95
  DETECTED_ISSUE_EVIDENCE_UPDATED: _ClassVar[EventType]
96
+ STAFF_ACTIVATED: _ClassVar[EventType]
97
+ STAFF_DEACTIVATED: _ClassVar[EventType]
94
98
  PRE_COMMAND_ORIGINATE: _ClassVar[EventType]
95
99
  POST_COMMAND_ORIGINATE: _ClassVar[EventType]
96
100
  PRE_COMMAND_UPDATE: _ClassVar[EventType]
@@ -842,6 +846,11 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
842
846
  PATIENT_EXTERNAL_IDENTIFIER_CREATED: _ClassVar[EventType]
843
847
  PATIENT_EXTERNAL_IDENTIFIER_UPDATED: _ClassVar[EventType]
844
848
  PATIENT_EXTERNAL_IDENTIFIER_DELETED: _ClassVar[EventType]
849
+ PATIENT_METADATA_CREATED: _ClassVar[EventType]
850
+ PATIENT_METADATA_UPDATED: _ClassVar[EventType]
851
+ DOCUMENT_REFERENCE_CREATED: _ClassVar[EventType]
852
+ DOCUMENT_REFERENCE_UPDATED: _ClassVar[EventType]
853
+ DOCUMENT_REFERENCE_DELETED: _ClassVar[EventType]
845
854
  UNKNOWN: EventType
846
855
  ALLERGY_INTOLERANCE_CREATED: EventType
847
856
  ALLERGY_INTOLERANCE_UPDATED: EventType
@@ -888,6 +897,8 @@ PRESCRIPTION_CREATED: EventType
888
897
  PRESCRIPTION_UPDATED: EventType
889
898
  REFERRAL_REPORT_CREATED: EventType
890
899
  REFERRAL_REPORT_UPDATED: EventType
900
+ STAFF_CREATED: EventType
901
+ STAFF_UPDATED: EventType
891
902
  TASK_COMMENT_CREATED: EventType
892
903
  TASK_CREATED: EventType
893
904
  TASK_LABELS_ADJUSTED: EventType
@@ -924,6 +935,8 @@ TASK_CLOSED: EventType
924
935
  TASK_COMPLETED: EventType
925
936
  DETECTED_ISSUE_EVIDENCE_CREATED: EventType
926
937
  DETECTED_ISSUE_EVIDENCE_UPDATED: EventType
938
+ STAFF_ACTIVATED: EventType
939
+ STAFF_DEACTIVATED: EventType
927
940
  PRE_COMMAND_ORIGINATE: EventType
928
941
  POST_COMMAND_ORIGINATE: EventType
929
942
  PRE_COMMAND_UPDATE: EventType
@@ -1675,6 +1688,11 @@ PATIENT_METADATA__GET_ADDITIONAL_FIELDS: EventType
1675
1688
  PATIENT_EXTERNAL_IDENTIFIER_CREATED: EventType
1676
1689
  PATIENT_EXTERNAL_IDENTIFIER_UPDATED: EventType
1677
1690
  PATIENT_EXTERNAL_IDENTIFIER_DELETED: EventType
1691
+ PATIENT_METADATA_CREATED: EventType
1692
+ PATIENT_METADATA_UPDATED: EventType
1693
+ DOCUMENT_REFERENCE_CREATED: EventType
1694
+ DOCUMENT_REFERENCE_UPDATED: EventType
1695
+ DOCUMENT_REFERENCE_DELETED: EventType
1678
1696
 
1679
1697
  class Event(_message.Message):
1680
1698
  __slots__ = ("type", "target", "context", "target_type")
@@ -1,7 +1,8 @@
1
1
  from canvas_sdk.commands.commands.questionnaire import QuestionnaireCommand
2
+ from canvas_sdk.commands.commands.questionnaire.toggle_questions import ToggleQuestionsMixin
2
3
 
3
4
 
4
- class PhysicalExamCommand(QuestionnaireCommand):
5
+ class PhysicalExamCommand(ToggleQuestionsMixin, QuestionnaireCommand):
5
6
  """A class for managing physical exam command."""
6
7
 
7
8
  class Meta:
@@ -0,0 +1,32 @@
1
+ from datetime import date
2
+
3
+ from pydantic_core import InitErrorDetails
4
+
5
+ from canvas_sdk.commands.base import _BaseCommand as BaseCommand
6
+
7
+
8
+ class ImmunizationStatementCommand(BaseCommand):
9
+ """A class for managing an ImmunizationStatement command within a specific note."""
10
+
11
+ class Meta:
12
+ key = "immunizationStatement"
13
+
14
+ cpt_code: str
15
+ cvx_code: str
16
+ approximate_date: date | None = None
17
+ comments: str | None = None
18
+
19
+ def _get_error_details(self, method: str) -> list[InitErrorDetails]:
20
+ errors = super()._get_error_details(method)
21
+
22
+ if self.comments and len(self.comments) > 255:
23
+ errors.append(
24
+ self._create_error_detail(
25
+ "comments", "Comments must be 255 characters or less.", self.comments
26
+ )
27
+ )
28
+
29
+ return errors
30
+
31
+
32
+ __exports__ = ("ImmunizationStatementCommand",)
@@ -12,7 +12,7 @@ from canvas_sdk.commands.commands.questionnaire.question import (
12
12
  ResponseOption,
13
13
  TextQuestion,
14
14
  )
15
- from canvas_sdk.v1.data import Questionnaire
15
+ from canvas_sdk.v1.data import Command, Questionnaire
16
16
 
17
17
  QUESTION_CLASSES: dict[str, type[BaseQuestion]] = {
18
18
  ResponseOption.TYPE_TEXT: TextQuestion,
@@ -37,8 +37,22 @@ class QuestionnaireCommand(_BaseCommand):
37
37
  @cached_property
38
38
  def _questionnaire(self) -> Questionnaire | None:
39
39
  if not self.questionnaire_id:
40
- return None
41
- return Questionnaire.objects.get(id=self.questionnaire_id)
40
+ if command_uuid := self.command_uuid:
41
+ # If the questionnaire is not set, try to fetch it from the command
42
+ try:
43
+ command_data = Command.objects.values_list("data", flat=True).get(
44
+ id=command_uuid
45
+ )
46
+ if questionnaire_dbid := command_data.get("questionnaire", {}).get("value"):
47
+ questionnaire = Questionnaire.objects.get(dbid=questionnaire_dbid)
48
+ self.questionnaire_id = str(questionnaire.id)
49
+ return questionnaire
50
+ except (Command.DoesNotExist, Questionnaire.DoesNotExist):
51
+ return None
52
+ else:
53
+ return None
54
+
55
+ return Questionnaire.objects.get(id=self.questionnaire_id) # type: ignore[misc]
42
56
 
43
57
  @cached_property
44
58
  def questions(self) -> list[BaseQuestion]:
@@ -54,6 +68,7 @@ class QuestionnaireCommand(_BaseCommand):
54
68
 
55
69
  for question in self._questionnaire.questions.all():
56
70
  qdata: dict[str, Any] = {
71
+ "id": question.pk,
57
72
  "name": f"question-{question.pk}",
58
73
  "label": question.name,
59
74
  "coding": {
@@ -38,8 +38,9 @@ class BaseQuestion(ABC):
38
38
  type: str
39
39
 
40
40
  def __init__(
41
- self, name: str, label: str, coding: dict[str, str], options: list[ResponseOption]
41
+ self, id: str, name: str, label: str, coding: dict[str, str], options: list[ResponseOption]
42
42
  ) -> None:
43
+ self.id: str = id
43
44
  self.name: str = name
44
45
  self.label: str = label
45
46
  self.coding: dict[str, str] = coding
@@ -47,7 +48,7 @@ class BaseQuestion(ABC):
47
48
  self.response: Any | None = None
48
49
 
49
50
  def __repr__(self) -> str:
50
- return f"Question({self.name=!r}, {self.label=!r}, {self.type=!r}, {self.options=!r}, {self.response=!r})"
51
+ return f"Question({self.id=!r}, {self.name=!r}, {self.label=!r}, {self.type=!r}, {self.options=!r}, {self.response=!r})"
51
52
 
52
53
  @abstractmethod
53
54
  def add_response(self, *args: Any, **kwargs: Any) -> None:
@@ -0,0 +1,68 @@
1
+ from canvas_sdk.v1.data import Command
2
+
3
+
4
+ class ToggleQuestionsMixin:
5
+ """Mixin that adds toggle functionality to questionnaire-based commands.
6
+
7
+ This mixin should be used with classes that inherit from QuestionnaireCommand
8
+ and provides the ability to skip/enable individual questions.
9
+
10
+ Note: In the data model, skip=true means the question is ENABLED (not skipped).
11
+ This is counterintuitive but matches the existing behavior.
12
+ """
13
+
14
+ # All toggle states (persisted + runtime changes)
15
+ _question_toggles: dict[str, bool] | None = None
16
+
17
+ def _ensure_toggles_loaded(self) -> None:
18
+ """Load toggle states from the database if not already loaded."""
19
+ if self._question_toggles is not None:
20
+ return
21
+
22
+ self._question_toggles = {}
23
+
24
+ if hasattr(self, "command_uuid") and self.command_uuid:
25
+ try:
26
+ command_data = Command.objects.values_list("data", flat=True).get(
27
+ id=self.command_uuid
28
+ )
29
+ for key, value in command_data.items():
30
+ if key.startswith("skip-"):
31
+ question_id = key.replace("skip-", "")
32
+ self._question_toggles[question_id] = bool(value)
33
+ except Command.DoesNotExist:
34
+ pass
35
+
36
+ @property
37
+ def question_toggles(self) -> dict[str, bool]:
38
+ """Get the current toggle states for questions (question_id -> enabled)."""
39
+ self._ensure_toggles_loaded()
40
+ return self._question_toggles.copy() # type: ignore[union-attr]
41
+
42
+ def is_question_enabled(self, question_id: str | int) -> bool | None:
43
+ """Check if a question is enabled."""
44
+ self._ensure_toggles_loaded()
45
+ question_id = str(question_id)
46
+ return self._question_toggles.get(question_id, None) # type: ignore[union-attr]
47
+
48
+ def set_question_enabled(self, question_id: str | int, enabled: bool) -> None:
49
+ """Enable or disable a question."""
50
+ self._ensure_toggles_loaded()
51
+ self._question_toggles[str(question_id)] = enabled # type: ignore[index]
52
+
53
+ @property
54
+ def values(self) -> dict:
55
+ """Include skip states in command values."""
56
+ values = super().values # type: ignore[misc]
57
+
58
+ # Get all current toggle states
59
+ all_toggles = self.question_toggles
60
+
61
+ # Add skip- prefix for the values dict
62
+ for question_id, enabled in all_toggles.items():
63
+ values[f"skip-{question_id}"] = enabled
64
+
65
+ return values
66
+
67
+
68
+ __exports__ = ()
@@ -1,7 +1,8 @@
1
1
  from canvas_sdk.commands.commands.questionnaire import QuestionnaireCommand
2
+ from canvas_sdk.commands.commands.questionnaire.toggle_questions import ToggleQuestionsMixin
2
3
 
3
4
 
4
- class ReviewOfSystemsCommand(QuestionnaireCommand):
5
+ class ReviewOfSystemsCommand(ToggleQuestionsMixin, QuestionnaireCommand):
5
6
  """A class for managing physical exam command."""
6
7
 
7
8
  class Meta:
@@ -5,7 +5,8 @@ from .banner_alert import BannerAlert
5
5
  from .billing import BillingLineItem, BillingLineItemModifier
6
6
  from .business_line import BusinessLine
7
7
  from .care_team import CareTeamMembership, CareTeamRole
8
- from .claim import Claim, ClaimCoverage, ClaimPatient, ClaimQueue
8
+ from .charge_description_master import ChargeDescriptionMaster
9
+ from .claim import Claim, ClaimCoverage, ClaimPatient, ClaimQueue, InstallmentPlan
9
10
  from .claim_line_item import ClaimLineItem
10
11
  from .command import Command
11
12
  from .condition import Condition, ConditionCoding
@@ -19,6 +20,8 @@ from .lab import (
19
20
  LabOrder,
20
21
  LabOrderReason,
21
22
  LabOrderReasonCondition,
23
+ LabPartner,
24
+ LabPartnerTest,
22
25
  LabReport,
23
26
  LabReview,
24
27
  LabTest,
@@ -32,7 +35,7 @@ from .line_item_transaction import (
32
35
  )
33
36
  from .medication import Medication, MedicationCoding
34
37
  from .message import Message, MessageAttachment, MessageTransmission
35
- from .note import Note, NoteType
38
+ from .note import CurrentNoteStateEvent, Note, NoteStateChangeEvent, NoteType
36
39
  from .observation import (
37
40
  Observation,
38
41
  ObservationCoding,
@@ -76,8 +79,9 @@ from .questionnaire import (
76
79
  ResponseOptionSet,
77
80
  )
78
81
  from .reason_for_visit import ReasonForVisitSettingCoding
79
- from .staff import Staff, StaffContactPoint
82
+ from .staff import Staff, StaffAddress, StaffContactPoint, StaffPhoto
80
83
  from .task import Task, TaskComment, TaskLabel, TaskTaskLabel
84
+ from .team import Team, TeamContactPoint
81
85
  from .user import CanvasUser
82
86
 
83
87
  __all__ = __exports__ = (
@@ -96,6 +100,7 @@ __all__ = __exports__ = (
96
100
  "CanvasUser",
97
101
  "CareTeamMembership",
98
102
  "CareTeamRole",
103
+ "ChargeDescriptionMaster",
99
104
  "Claim",
100
105
  "ClaimCoverage",
101
106
  "ClaimLineItem",
@@ -106,6 +111,7 @@ __all__ = __exports__ = (
106
111
  "ConditionCoding",
107
112
  "Coverage",
108
113
  "CoveragePosting",
114
+ "CurrentNoteStateEvent",
109
115
  "DetectedIssue",
110
116
  "DetectedIssueEvidence",
111
117
  "Device",
@@ -113,6 +119,7 @@ __all__ = __exports__ = (
113
119
  "ImagingOrder",
114
120
  "ImagingReport",
115
121
  "ImagingReview",
122
+ "InstallmentPlan",
116
123
  "Interview",
117
124
  "InterviewQuestionnaireMap",
118
125
  "InterviewQuestionResponse",
@@ -120,6 +127,8 @@ __all__ = __exports__ = (
120
127
  "LabOrder",
121
128
  "LabOrderReason",
122
129
  "LabOrderReasonCondition",
130
+ "LabPartner",
131
+ "LabPartnerTest",
123
132
  "LabReport",
124
133
  "LabReview",
125
134
  "LabTest",
@@ -134,6 +143,7 @@ __all__ = __exports__ = (
134
143
  "NewLineItemAdjustment",
135
144
  "NewLineItemPayment",
136
145
  "Note",
146
+ "NoteStateChangeEvent",
137
147
  "NoteType",
138
148
  "Observation",
139
149
  "ObservationCoding",
@@ -163,11 +173,15 @@ __all__ = __exports__ = (
163
173
  "ResponseOption",
164
174
  "ResponseOptionSet",
165
175
  "Staff",
176
+ "StaffAddress",
177
+ "StaffPhoto",
166
178
  "StaffContactPoint",
167
179
  "Task",
168
180
  "TaskComment",
169
181
  "TaskLabel",
170
182
  "TaskTaskLabel",
183
+ "Team",
184
+ "TeamContactPoint",
171
185
  "Transactor",
172
186
  "TransactorAddress",
173
187
  "TransactorPhone",
@@ -6,6 +6,8 @@ from canvas_sdk.v1.data.base import (
6
6
  BaseModelManager,
7
7
  CommittableQuerySetMixin,
8
8
  ForPatientQuerySetMixin,
9
+ IdentifiableModel,
10
+ Model,
9
11
  ValueSetLookupQuerySet,
10
12
  )
11
13
 
@@ -23,19 +25,16 @@ class AllergyIntoleranceQuerySet(
23
25
  AllergyIntoleranceManager = BaseModelManager.from_queryset(AllergyIntoleranceQuerySet)
24
26
 
25
27
 
26
- class AllergyIntolerance(models.Model):
28
+ class AllergyIntolerance(IdentifiableModel):
27
29
  """AllergyIntolerance."""
28
30
 
29
31
  class Meta:
30
- managed = False
31
32
  db_table = "canvas_sdk_data_api_allergyintolerance_001"
32
33
 
33
34
  objects = cast(AllergyIntoleranceQuerySet, AllergyIntoleranceManager())
34
35
 
35
- id = models.UUIDField()
36
- dbid = models.BigIntegerField(primary_key=True)
37
- created = models.DateTimeField()
38
- modified = models.DateTimeField()
36
+ created = models.DateTimeField(auto_now_add=True)
37
+ modified = models.DateTimeField(auto_now=True)
39
38
  deleted = models.BooleanField()
40
39
  committer = models.ForeignKey(
41
40
  "v1.CanvasUser", on_delete=models.DO_NOTHING, null=True, related_name="+"
@@ -50,30 +49,28 @@ class AllergyIntolerance(models.Model):
50
49
  null=True,
51
50
  )
52
51
  note_id = models.BigIntegerField()
53
- allergy_intolerance_type = models.CharField()
52
+ allergy_intolerance_type = models.CharField(max_length=1)
54
53
  category = models.IntegerField()
55
- status = models.CharField()
56
- severity = models.CharField()
54
+ status = models.CharField(max_length=20)
55
+ severity = models.CharField(max_length=20)
57
56
  onset_date = models.DateField()
58
- onset_date_original_input = models.CharField()
57
+ onset_date_original_input = models.CharField(max_length=255)
59
58
  last_occurrence = models.DateField()
60
- last_occurrence_original_input = models.CharField()
59
+ last_occurrence_original_input = models.CharField(max_length=255)
61
60
  recorded_date = models.DateTimeField()
62
- narrative = models.CharField()
61
+ narrative = models.CharField(max_length=512)
63
62
 
64
63
 
65
- class AllergyIntoleranceCoding(models.Model):
64
+ class AllergyIntoleranceCoding(Model):
66
65
  """AllergyIntoleranceCoding."""
67
66
 
68
67
  class Meta:
69
- managed = False
70
68
  db_table = "canvas_sdk_data_api_allergyintolerancecoding_001"
71
69
 
72
- dbid = models.BigIntegerField(primary_key=True)
73
- system = models.CharField()
74
- version = models.CharField()
75
- code = models.CharField()
76
- display = models.CharField()
70
+ system = models.CharField(max_length=255)
71
+ version = models.CharField(max_length=255)
72
+ code = models.CharField(max_length=255)
73
+ display = models.CharField(max_length=1000)
77
74
  user_selected = models.BooleanField()
78
75
  allergy_intolerance = models.ForeignKey(
79
76
  AllergyIntolerance,
@@ -1,5 +1,7 @@
1
1
  from django.db import models
2
2
 
3
+ from canvas_sdk.v1.data.base import IdentifiableModel
4
+
3
5
 
4
6
  class AppointmentProgressStatus(models.TextChoices):
5
7
  """AppointmentProgressStatus."""
@@ -14,15 +16,12 @@ class AppointmentProgressStatus(models.TextChoices):
14
16
  CANCELLED = "cancelled", "Cancelled"
15
17
 
16
18
 
17
- class Appointment(models.Model):
19
+ class Appointment(IdentifiableModel):
18
20
  """Appointment."""
19
21
 
20
22
  class Meta:
21
- managed = False
22
23
  db_table = "canvas_sdk_data_api_appointment_001"
23
24
 
24
- id = models.UUIDField()
25
- dbid = models.BigIntegerField(primary_key=True)
26
25
  entered_in_error = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
27
26
  patient = models.ForeignKey(
28
27
  "v1.Patient",
@@ -56,21 +55,18 @@ class Appointment(models.Model):
56
55
  description = models.TextField(null=True, blank=True)
57
56
 
58
57
 
59
- class AppointmentExternalIdentifier(models.Model):
58
+ class AppointmentExternalIdentifier(IdentifiableModel):
60
59
  """AppointmentExternalIdentifier."""
61
60
 
62
61
  class Meta:
63
- managed = False
64
62
  db_table = "canvas_sdk_data_api_appointmentexternalidentifier_001"
65
63
 
66
- id = models.UUIDField()
67
- dbid = models.BigIntegerField(primary_key=True)
68
- created = models.DateTimeField()
69
- modified = models.DateTimeField()
70
- use = models.CharField()
71
- identifier_type = models.CharField()
72
- system = models.CharField()
73
- value = models.CharField()
64
+ created = models.DateTimeField(auto_now_add=True)
65
+ modified = models.DateTimeField(auto_now=True)
66
+ use = models.CharField(max_length=255)
67
+ identifier_type = models.CharField(max_length=255)
68
+ system = models.CharField(max_length=255)
69
+ value = models.CharField(max_length=255)
74
70
  issued_date = models.DateField()
75
71
  expiration_date = models.DateField()
76
72
  appointment = models.ForeignKey(
@@ -1,5 +1,7 @@
1
1
  from django.db import models
2
2
 
3
+ from canvas_sdk.v1.data.base import IdentifiableModel
4
+
3
5
 
4
6
  class AssessmentStatus(models.TextChoices):
5
7
  """AssessmentStatus."""
@@ -9,17 +11,14 @@ class AssessmentStatus(models.TextChoices):
9
11
  STATUS_DETERIORATING = "deteriorated", "Deteriorated"
10
12
 
11
13
 
12
- class Assessment(models.Model):
14
+ class Assessment(IdentifiableModel):
13
15
  """Assessment."""
14
16
 
15
17
  class Meta:
16
- managed = False
17
18
  db_table = "canvas_sdk_data_api_assessment_001"
18
19
 
19
- id = models.UUIDField()
20
- dbid = models.BigIntegerField(primary_key=True)
21
- created = models.DateTimeField()
22
- modified = models.DateTimeField()
20
+ created = models.DateTimeField(auto_now_add=True)
21
+ modified = models.DateTimeField(auto_now=True)
23
22
  originator = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, related_name="+")
24
23
  committer = models.ForeignKey(
25
24
  "v1.CanvasUser", on_delete=models.DO_NOTHING, null=True, related_name="+"
@@ -38,10 +37,10 @@ class Assessment(models.Model):
38
37
  "v1.Condition", on_delete=models.CASCADE, related_name="assessments", null=True
39
38
  )
40
39
  interview = models.ForeignKey("v1.Interview", on_delete=models.DO_NOTHING, null=True)
41
- status = models.CharField(choices=AssessmentStatus.choices)
42
- narrative = models.CharField()
43
- background = models.CharField()
44
- care_team = models.CharField()
40
+ status = models.CharField(choices=AssessmentStatus.choices, max_length=20)
41
+ narrative = models.CharField(max_length=2048)
42
+ background = models.CharField(max_length=2048)
43
+ care_team = models.CharField(max_length=500)
45
44
 
46
45
 
47
46
  __exports__ = ("AssessmentStatus", "Assessment")
@@ -1,30 +1,30 @@
1
1
  from django.contrib.postgres.fields import ArrayField
2
2
  from django.db import models
3
3
 
4
+ from canvas_sdk.v1.data.base import Model
4
5
 
5
- class BannerAlert(models.Model):
6
+
7
+ class BannerAlert(Model):
6
8
  """BannerAlert."""
7
9
 
8
10
  class Meta:
9
- managed = False
10
11
  db_table = "canvas_sdk_data_api_banneralert_001"
11
12
 
12
- dbid = models.BigIntegerField(db_column="dbid", primary_key=True)
13
- created = models.DateTimeField()
14
- modified = models.DateTimeField()
13
+ created = models.DateTimeField(auto_now_add=True)
14
+ modified = models.DateTimeField(auto_now=True)
15
15
  patient = models.ForeignKey(
16
16
  "v1.Patient",
17
17
  on_delete=models.DO_NOTHING,
18
18
  related_name="banner_alerts",
19
19
  null=True,
20
20
  )
21
- plugin_name = models.CharField()
22
- key = models.CharField()
23
- narrative = models.CharField()
24
- placement = ArrayField(models.CharField())
25
- intent = models.CharField()
26
- href = models.CharField()
27
- status = models.CharField()
21
+ plugin_name = models.CharField(max_length=256)
22
+ key = models.CharField(max_length=255)
23
+ narrative = models.CharField(max_length=90)
24
+ placement = ArrayField(models.CharField(max_length=64))
25
+ intent = models.CharField(max_length=64)
26
+ href = models.CharField(max_length=255)
27
+ status = models.CharField(max_length=64)
28
28
 
29
29
 
30
30
  __exports__ = ("BannerAlert",)
@@ -1,14 +1,58 @@
1
+ import uuid
1
2
  from abc import abstractmethod
2
3
  from collections.abc import Container
3
4
  from typing import TYPE_CHECKING, Any, Protocol, Self, cast
4
5
 
5
- from django.db import models
6
+ from django.contrib.postgres.fields import ArrayField
7
+ from django.db import connection, models
6
8
  from django.db.models import Q
9
+ from django.db.models.base import ModelBase
7
10
 
8
11
  if TYPE_CHECKING:
9
12
  from canvas_sdk.protocols.timeframe import Timeframe
10
13
  from canvas_sdk.value_set.value_set import ValueSet
11
14
 
15
+ IS_SQLITE = connection.vendor == "sqlite"
16
+
17
+
18
+ class ModelMetaclass(ModelBase):
19
+ """A metaclass for configuring data models."""
20
+
21
+ def __new__(cls, name: str, bases: tuple, attrs: dict[str, Any], **kwargs: Any) -> type:
22
+ """Create a new model class."""
23
+ meta = attrs.get("Meta")
24
+
25
+ for field_name, field in list(attrs.items()):
26
+ if isinstance(field, ArrayField) and IS_SQLITE:
27
+ # Replace ArrayField(CharField(...)) with JSONField
28
+ attrs[field_name] = models.JSONField(default=list)
29
+
30
+ # set managed to True if database is SQLite and not explicitly set
31
+ if meta and not hasattr(meta, "managed") and not getattr(meta, "abstract", False):
32
+ meta.managed = IS_SQLITE
33
+
34
+ new_class = cast(type["Model"], super().__new__(cls, name, bases, attrs, **kwargs))
35
+
36
+ return new_class
37
+
38
+
39
+ class Model(models.Model, metaclass=ModelMetaclass):
40
+ """A base model."""
41
+
42
+ class Meta:
43
+ abstract = True
44
+
45
+ dbid = models.BigAutoField(primary_key=True)
46
+
47
+
48
+ class IdentifiableModel(Model):
49
+ """A model that includes an identifier."""
50
+
51
+ class Meta:
52
+ abstract = True
53
+
54
+ id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
55
+
12
56
 
13
57
  class BaseModelManager(models.Manager):
14
58
  """A base manager for models."""