Skip to main content
Version: 3.0

Report & Templates

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 on EMRResource. These define the enums, validation, the structure of the JSON fields, and the read/write schemas the API exposes.

Source:

Models

ModelPurpose
ReportUploadAn uploaded, generated report file (PDF/HTML/etc.) produced from a Template and linked to a subject resource via associating_id
TemplateA 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 base.py).

ReportUpload fields

Core

FieldTypeNotes
templateFK → Template (PROTECT)Template the report was generated from. The template cannot be deleted while reports reference it
nameCharField(2000)Display name of the report. Required in every spec
internal_nameCharField(2000)Randomized storage key, auto-generated on save so the file name never leaks PII (see save behaviour). Exposed only in RetrieveSpec
associating_idCharField(100)Non-null. Free-form identifier linking the report to its subject resource (e.g. an encounter)
upload_completedBooleanFieldDefault False. Flips to True once the file upload to storage finishes
report_typeCharField(50)File/content type of the report; also exposed via the file_type property for S3FilesManager

Archival

FieldTypeNotes
is_archivedBooleanFieldDefault False
archive_reasonTextFieldBlank allowed
archived_datetimeDateTimeFieldNullable; set when the report is archived
archived_byFK → User (PROTECT)Nullable. related_name="archived_reports"

Inherited / metadata

FieldTypeNotes
metaJSONField (default {})Inherited from EMRBaseModel. Holds mime_type (surfaced by specs as mime_type) and other free-form metadata
historyJSONField (default {})Inherited audit history
created_by / updated_byFK → 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.

FieldType (model)Spec constraintNotes
facilityFK → Facility (PROTECT, nullable)UUID4 | None (write)Null → instance-wide template; set → facility-scoped. Excluded from base serialization (__exclude__); resolved server-side on write
slugCharField(255)written via slug_value: SlugType; read as slug + parsed slug_configStored prefixed (f-…/i-…). slug_value must be 5–50 chars, URL-safe, and start and end alphanumeric
nameCharField(255)str, required
statusCharField(255)TemplateStatusOptions enumSee status values
template_dataTextFieldstr, required (write)The report markup/body. Returned only by RetrieveSpec, not in list/read
template_typeCharField(255)str, validated against ReportTypeRegistryMust be a registered report type, and compatible with context (matching associating_model)
default_formatCharField(255)TemplateFormatOptions enumSee format values. Selects which generator validates options
contextCharField(100) (default "encounter_base")str, validated against DataPointRegistryDefaults to encounter_base, so reports are encounter-oriented unless overridden. Must be a registered context compatible with template_type
descriptionTextField (blank, default "")str = ""
optionsJSONField (default {})dict = {}, validated against the format generator's options_modelRender/config options; accepted keys depend on default_format (PDF vs HTML generator)

TemplateStatusOptions values

status is bound to this enum (care/emr/resources/report/template/spec.py):

ValueMeaning
draftTemplate being authored, not yet usable
activePublished, usable for generating reports
retiredWithdrawn from use

TemplateFormatOptions values

default_format is bound to this enum:

ValueMeaning
pdfRender to PDF (uses the PDF generator's options_model)
htmlRender 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 classRoleFields / behaviour
ReportUploadBaseSpecsharedid: UUID4 | None, name: str
ReportUploadListSpecread · listAdds 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
ReportUploadRetrieveSpecread · detailExtends list spec; adds signed_url, read_signed_url, internal_name

Server-maintained read behaviour (perform_extra_serialization):

  • idobj.external_id.
  • extensionobj.get_extension() (dotted extension parsed from internal_name).
  • mime_typeobj.meta["mime_type"].
  • template ← serialized via TemplateReadSpec (full nested template object).
  • created_by / updated_by populated from the user cache via serialize_audit_users.
  • Signed URLs (retrieve only): a freshly created object (_just_created) gets a write/upload signed_url via files_manager.signed_url(obj); any other read gets a read_signed_url via files_manager.read_signed_url(obj). Exactly one is populated per response.

ReportUpload has no CreateSpec/UpdateSpec in this module. Uploads come from the file-upload flow (File Upload), and the report metadata and internal_name are platform-maintained. See save behaviour.

Template specs (report/template/spec.py)

Spec classRoleFields / behaviour
TemplateBaseSpecsharedid, name, status (TemplateStatusOptions), default_format (TemplateFormatOptions), description = "", options = {}. __exclude__ = ["facility"]
TemplateCreateSpecwrite · createAdds facility: UUID4 | None, slug_value: SlugType, template_data: str, template_type: str, context: str
TemplateUpdateSpecwrite · updateIdentical to TemplateCreateSpec (pass subclass)
TemplateReadSpecread · listBase fields + slug_config: dict, slug: str, template_type: str, context: str (no template_data)
TemplateRetrieveSpecread · detailExtends read spec; adds nested facility (FacilityBareMinimumSpec) and template_data: str

Write-side validation and side effects (TemplateCreateSpec):

  • slug_valueSlugType: 5–50 chars, pattern ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$.
  • template_type (field_validator) — non-empty and resolvable via ReportTypeRegistry, else Invalid report type.
  • context (field_validator) — non-empty and resolvable via DataPointRegistry, else Invalid Context type.
  • validate_report_type_and_context (model_validator, after):
    • both template_type and context must resolve, else Invalid report type or context;
    • their associating_model must match (template_type.associating_model == context.__associating_model__), else Report Type and Context are not compatible;
    • options is validated against GeneratorRegistry.get(default_format).options_model, so the allowed options keys depend on whether default_format is pdf or html.
  • perform_extra_deserialization (side effects): resolves facility external ID to the Facility row (get_object_or_404) when provided, and sets obj.slug = self.slug_value (the raw value; the model prefixes it in calculate_slug).

Read-side behaviour:

  • TemplateReadSpec.perform_extra_serialization: id ← external_id; slug_config ← obj.parse_slug(obj.slug) (see slug_config shape).
  • TemplateRetrieveSpec: additionally serializes facility via FacilityBareMinimumSpec when 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=True to save() to bypass generation and keep an explicitly set internal_name.
  • If internal_name already held a value, its file extension is parsed (via parse_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, else i-<slug>.
  • parse_slug(slug) — the inverse; returns slug_config (used by TemplateReadSpec).

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_name is platform-maintained — never set it from a client. Reserve skip_internal_name for controlled migrations. It is exposed only via ReportUploadRetrieveSpec.
  • A freshly created report returns an upload signed_url; subsequent reads return a read_signed_url instead.
  • Treat a report as available only once upload_completed is True.
  • Populate associating_id when generating a report so the file can be retrieved in its subject resource's context.
  • Template.options is validated server-side against the chosen format's generator schema, not an arbitrary bag; valid keys depend on default_format (pdf/html).
  • template_type and context must form a compatible pair (same associating_model). The default context is encounter_base, so templates target encounters unless changed.
  • Template.facility is nullable: null means instance-wide (i- slug), set means facility-scoped (f- slug).