References/Report & Templates: Difference between revisions
More languages
More actions
OHC identity seed |
Add Navbox platform (via update-page on MediaWiki MCP Server) |
||
| Line 261: | Line 261: | ||
* <code>template_type</code> and <code>context</code> must form a compatible pair (same <code>associating_model</code>). The default <code>context</code> is <code>encounter_base</code>, so templates target encounters unless changed. |
* <code>template_type</code> and <code>context</code> must form a compatible pair (same <code>associating_model</code>). The default <code>context</code> is <code>encounter_base</code>, so templates target encounters unless changed. |
||
* <code>Template.facility</code> is nullable: null means instance-wide (<code>i-</code> slug), set means facility-scoped (<code>f-</code> slug). |
* <code>Template.facility</code> is nullable: null means instance-wide (<code>i-</code> slug), set means facility-scoped (<code>f-</code> slug). |
||
{{Navbox platform}} |
|||
{{Related}} |
{{Related}} |
||
Latest revision as of 09:53, 6 July 2026
A Template defines reusable report markup and render options; a ReportUpload is one generated file (PDF/HTML/etc.) produced from that template and linked to a subject resource. You author templates through the API; report uploads are created for you by the file-upload flow.
Two layers back this:
- Storage layer — the Django models (
care/emr/models/report/). Several fields are opaque (TextField/JSONField); their real shape lives in the spec layer, not the model. - API layer — the Pydantic resource specs (
care/emr/resources/report/), built onEMRResource. These define the enums, validation, the structure of the JSON fields, and the read/write schemas the API exposes.
Source:
- Models: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/report_upload.py
report_upload.py], [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/report/template.pytemplate.py] - Resource specs: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/report_upload/spec.py
report/report_upload/spec.py], [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/report/template/spec.pyreport/template/spec.py]
Models
| Model | Purpose |
|---|---|
ReportUpload
|
An uploaded, generated report file (PDF/HTML/etc.) produced from a Template and linked to a subject resource via associating_id
|
Template
|
A facility-scoped (or instance-wide) reusable report definition (markup + render options) used to generate reports |
ReportUpload extends EMRBaseModel, the shared Care EMR base providing external_id, meta/history JSON, audit FKs, and soft-delete semantics.
Template extends SlugBaseModel, the facility-scoped slug variant (FACILITY_SCOPED = True, which adds slug handling on top of the EMR base). Slugs carry a prefix in storage: f-<facility_external_id>-<slug> when facility-scoped, i-<slug> when instance-wide (see [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/base.py base.py]).
ReportUpload fields
Core
| Field | Type | Notes |
|---|---|---|
template |
FK → Template (PROTECT) |
Template the report was generated from. The template cannot be deleted while reports reference it |
name |
CharField(2000) |
Display name of the report. Required in every spec
{{Field|model=Report&Templates|section=Core|name=internal_name|type=CharField(2000)|notes=Randomized storage key, auto-generated on save so the file name never leaks PII (see [[#methods--save-behaviour}} |
associating_id |
CharField(100) |
Non-null. Free-form identifier linking the report to its subject resource (e.g. an encounter) |
upload_completed |
BooleanField |
Default False. Flips to True once the file upload to storage finishes
|
report_type |
CharField(50) |
File/content type of the report; also exposed via the file_type property for S3FilesManager
|
Archival
| Field | Type | Notes |
|---|---|---|
is_archived |
BooleanField |
Default False
|
archive_reason |
TextField |
Blank allowed |
archived_datetime |
DateTimeField |
Nullable; set when the report is archived |
archived_by |
FK → User (PROTECT) |
Nullable. related_name="archived_reports"
|
Inherited / metadata
| Field | Type | Notes |
|---|---|---|
meta |
JSONField (default {}) |
Inherited from EMRBaseModel. Holds mime_type (surfaced by specs as mime_type) and other free-form metadata
|
history |
JSONField (default {}) |
Inherited audit history |
created_by / updated_by |
FK → User (SET_NULL) |
Inherited audit FKs; serialized as created_by/updated_by UserSpec
|
Storage
ReportUpload declares a class-level files_manager = S3FilesManager(BucketType.REPORT). The file body lives in object storage (REPORT bucket), not the database — the model row holds only metadata and the internal_name storage key.
Template fields
The Django model stores most config as plain strings plus an opaque options JSONField. The constraints live in the spec layer: it pins status, default_format, and the slug, and validates the structure and mutual compatibility of template_type, context, and options.
| Field | Type (model) | Spec constraint | Notes |
|---|---|---|---|
facility |
FK → Facility (PROTECT, nullable) |
UUID4
| |
slug |
CharField(255) |
written via slug_value: SlugType; read as slug + parsed slug_config
| |
name |
CharField(255) |
str, required
| |
status |
CharField(255) |
TemplateStatusOptions enum
| |
template_data |
TextField |
str, required (write)
| |
template_type |
CharField(255) |
str, validated against ReportTypeRegistry
| |
default_format |
CharField(255) |
TemplateFormatOptions enum
| |
context |
CharField(100) (default "encounter_base") |
str, validated against DataPointRegistry
| |
description |
TextField (blank, default "") |
str = ""
| |
options |
JSONField (default {}) |
dict = {}, validated against the format generator's options_model
|
TemplateStatusOptions values
status is bound to this enum (care/emr/resources/report/template/spec.py):
| Value | Meaning |
|---|---|
draft
|
Template being authored, not yet usable |
active
|
Published, usable for generating reports |
retired
|
Withdrawn from use |
TemplateFormatOptions values
default_format is bound to this enum:
| Value | Meaning |
|---|---|
pdf
|
Render to PDF (uses the PDF generator's options_model)
|
html
|
Render to HTML (uses the HTML generator's options_model)
|
slug_config shape (read)
On read, TemplateReadSpec parses the stored prefixed slug back into a slug_config dict:
slug_config (facility-scoped) → { facility: <facility_external_id>, slug_value: <slug> }
slug_config (instance-wide) → { slug_value: <slug> } # parsed from "i-<slug>"
Resource specs (API schema)
All specs subclass EMRResource. Read runs serialize() → DB-field copy → perform_extra_serialization() hook. Write runs de_serialize() → DB-field copy → perform_extra_deserialization() hook.
ReportUpload specs (report/report_upload/spec.py)
| Spec class | Role | Fields / behaviour |
|---|---|---|
ReportUploadBaseSpec
|
shared | None, name: str
|
ReportUploadListSpec
|
read · list | Adds template (nested TemplateReadSpec), report_type, associating_id, archived_by (UserSpec), archived_datetime, upload_completed, is_archived, archive_reason, created_date, extension, uploaded_by, mime_type
|
ReportUploadRetrieveSpec
|
read · detail | Extends list spec; adds signed_url, read_signed_url, internal_name
|
Server-maintained read behaviour (perform_extra_serialization):
id←obj.external_id.extension←obj.get_extension()(dotted extension parsed frominternal_name).mime_type←obj.meta["mime_type"].template← serialized viaTemplateReadSpec(full nested template object).created_by/updated_bypopulated from the user cache viaserialize_audit_users.- Signed URLs (retrieve only): a freshly created object (
_just_created) gets a write/uploadsigned_urlviafiles_manager.signed_url(obj); any other read gets aread_signed_urlviafiles_manager.read_signed_url(obj). Exactly one is populated per response.
ReportUploadhas noCreateSpec/UpdateSpecin this module. Uploads come from the file-upload flow (References/File Upload), and the report metadata andinternal_nameare platform-maintained. See save behaviour.
Template specs (report/template/spec.py)
| Spec class | Role | Fields / behaviour |
|---|---|---|
TemplateBaseSpec
|
shared | id, name, status (TemplateStatusOptions), default_format (TemplateFormatOptions), description = "", options = {}. __exclude__ = ["facility"]
|
TemplateCreateSpec
|
write · create | None, slug_value: SlugType, template_data: str, template_type: str, context: str
|
TemplateUpdateSpec
|
write · update | Identical to TemplateCreateSpec (pass subclass)
|
TemplateReadSpec
|
read · list | Base fields + slug_config: dict, slug: str, template_type: str, context: str (no template_data)
|
TemplateRetrieveSpec
|
read · detail | Extends read spec; adds nested facility (FacilityBareMinimumSpec) and template_data: str
|
Write-side validation and side effects (TemplateCreateSpec):
slug_value—SlugType: 5–50 chars, pattern^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$.template_type(field_validator) — non-empty and resolvable viaReportTypeRegistry, elseInvalid report type.context(field_validator) — non-empty and resolvable viaDataPointRegistry, elseInvalid Context type.validate_report_type_and_context(model_validator, after):- both
template_typeandcontextmust resolve, elseInvalid report type or context; - their
associating_modelmust match (template_type.associating_model == context.__associating_model__), elseReport Type and Context are not compatible; optionsis validated againstGeneratorRegistry.get(default_format).options_model, so the allowedoptionskeys depend on whetherdefault_formatispdforhtml.
- both
perform_extra_deserialization(side effects): resolvesfacilityexternal ID to theFacilityrow (get_object_or_404) when provided, and setsobj.slug = self.slug_value(the raw value; the model prefixes it incalculate_slug).
Read-side behaviour:
TemplateReadSpec.perform_extra_serialization:id ← external_id;slug_config ← obj.parse_slug(obj.slug)(seeslug_configshape).TemplateRetrieveSpec: additionally serializesfacilityviaFacilityBareMinimumSpecwhen set.
Methods & save behaviour
ReportUpload.save()
On create (no id) or when internal_name is empty, save() generates a random internal_name from uuid4() plus the current epoch. This intermediate name decouples the stored object from the human-readable name, so a storage or data leak never exposes PII in file names.
- Pass
skip_internal_name=Truetosave()to bypass generation and keep an explicitly setinternal_name. - If
internal_namealready held a value, its file extension is parsed (viaparse_file_extension) and appended to the new randomized name.
ReportUpload.get_extension()
Returns the dotted extension(s) parsed from internal_name (e.g. .pdf), or "" if none. Surfaced as the extension field in read specs.
ReportUpload.file_type (property)
Alias for report_type, kept for compatibility with S3FilesManager.
Template slug methods (inherited from SlugBaseModel)
calculate_slug()— returns the prefixed slug:f-<facility.external_id>-<slug>when facility-scoped, elsei-<slug>.parse_slug(slug)— the inverse; returnsslug_config(used byTemplateReadSpec).
API integration notes
- Report files live in object storage (S3-compatible REPORT bucket) and are read through
S3FilesManager; the database row is metadata only. internal_nameis platform-maintained — never set it from a client. Reserveskip_internal_namefor controlled migrations. It is exposed only viaReportUploadRetrieveSpec.- A freshly created report returns an upload
signed_url; subsequent reads return aread_signed_urlinstead. - Treat a report as available only once
upload_completedisTrue. - Populate
associating_idwhen generating a report so the file can be retrieved in its subject resource's context. Template.optionsis validated server-side against the chosen format's generator schema, not an arbitrary bag; valid keys depend ondefault_format(pdf/html).template_typeandcontextmust form a compatible pair (sameassociating_model). The defaultcontextisencounter_base, so templates target encounters unless changed.Template.facilityis nullable: null means instance-wide (i-slug), set means facility-scoped (f-slug).
Related
- Reference: References/Base models & conventions