Django and healthtech app

Where the widget has to stop on a Django health product

Health products get built as one Django project with a marketing front and a patient area behind a login, sharing one base template and one policy header. That arrangement makes two decisions for you unless you make them on purpose: whether the launcher appears on a page somebody has signed in to, and whether it is allowed to run anywhere at all.

Why this pairing is its own job

The Django install guide covers the tag, and the healthtech app guide covers what the assistant has to know. What follows is the part that belongs to neither.

  • The base template is shared, so a tag in it renders on the public pages and on every authenticated page that extends the same base, including anything showing a patient their own information.
  • A health product usually carries a content security policy written to be strict and reviewed by somebody outside the engineering team. Third party script and frame sources are the exact thing it exists to refuse, and on some projects the honest answer is that the widget does not run until that policy is changed deliberately.
  • Template inheritance is the control, which is the good news. Where the widget stops is a decision expressed in template names and block structure, so a reviewer can read it rather than trusting a runtime condition.
  • The governance material a buyer wants is written and publishable. The clinical material a patient might ask about is neither, and both live in the same repository under the same templates directory.

What changes about the install here

<script src="https://cdn.askably.xyz/w.js" data-key="pk_live_YOUR_KEY" defer></script>

The tag is the same one on the Django guide. Everything below is about where it goes on a healthtech app site specifically.

Two bases, or one base and a block you can override

The cleanest arrangement is a second base template for the authenticated area that simply does not contain the tag. Signed in views extend that one, public views extend the public base, and the boundary is legible from the template names instead of from a condition somebody has to reason about at review time.

Where a single base is genuinely shared, put the tag inside the scripts block and have the authenticated templates override that block without the block super call. It works, and it fails quietly in the other direction: any child template that defines its own scripts block for an unrelated reason drops the launcher from a public page too, and nothing reports it.

Whichever you choose, write down which templates sit on which side and check it after a template refactor. A widget that migrates into the patient area during a tidy up is not something anybody files a bug about.

The policy has to name both addresses, or you stop here

If the project sends a content security policy, the loader address has to be permitted as a script source and the panel address as a frame source. Two directives, both needed, because the launcher is a script and the conversation opens in its own frame.

A policy refusal and an origin refusal produce similar looking console messages and need entirely different fixes. A policy message names the directive that blocked. An origin message names the origin that was refused. Read which one you have before touching anything, because on a health project a policy change is a review rather than a commit.

If the policy cannot change, that is the end of the assessment and a legitimate thing to report back. There is nothing to configure on the other side and no workaround worth proposing, because a workaround here is a control being routed around.

The error templates a patient reaches when something breaks

Django resolves the 404 and 500 templates from the root of the template directories and renders them standalone unless you wrote them to extend a base. A patient who has just lost a page is one of the few visitors with an urgent question, and that is the page least likely to carry a launcher.

On a health product that is frequently the right outcome rather than an oversight. The error page a signed in patient reaches sits inside the boundary; the one a visitor hits on the marketing site does not. Decide per template, and put the crisis wording into the page itself rather than relying on a widget to be there.

Where the answerable material lives

Source material on a Django healthtech app site
The scope statementWhat the product is for, who it is for, and what it explicitly does not claim to do. Index this before anything else, because every other answer inherits its boundaries and an assistant reading a looser page will state the looser version in writing.
The information governance packImpact assessment, data flow description, retention schedule, subprocessor list. Uploaded rather than crawled, since these are documents rather than pages, and this is the material with the highest return on the whole site.
Access and eligibilityWhich countries, which routes in, and what somebody does when their organisation is not signed up. Administrative, high volume, and safe to answer in full.
Anything rendered only for an authenticated userNot material at all. A crawl reaches the login view and stops, there is no route from an indexed page to a record, and nothing behind that boundary can be read, connected or looked up.

The first thing to get right

Do this first
Put the crisis and emergency wording into the refusal message before the tag goes anywhere near the base template.

The tag reaches every page that extends the base in one commit, and you cannot stage which visitor arrives first. The refusal is the only part of the reply you get to write in advance, and on this product it has to be right on the first message rather than after a round of tuning.

The failure that belongs to this combination

The policy was widened to make the launcher appear and nobody wrote it down

The sequence is familiar on every project. The tag goes in, nothing renders, somebody finds the directive named in the console, widens the script source, and the launcher appears. On most products that is a five minute fix and the end of it.

On a health product it is a change to a control described in an assessment that buyers read and questionnaires ask about. Health organisations ask what third party code executes on the pages their patients reach, and an answer that does not match the header you are actually sending is the kind of discrepancy that stops a procurement.

Make the change narrowly and on purpose: name the two addresses rather than loosening the directive, record it wherever the policy is documented, and expect it in the next security questionnaire. That answer is worth more to a buyer than the launcher is.

Before you go live

  • The origin the page is served from has to be on the allowlist for that assistant, or nothing renders and the browser console says which origin was refused. An apex domain and its www are two different origins to a browser, so list both, along with any staging or preview host you want it to work on.
  • Open the site as a visitor would, on the pages a healthtech app visitor actually lands on, and ask it something only your own material could answer. A widget that renders is not the same as a widget that has read anything.

Questions

Our marketing site and product are separate deployments on separate subdomains. Does that change this?
It simplifies it considerably. Two origins means two allowlist decisions, and the boundary is enforced by the deployment rather than by template inheritance. Allowlist the marketing origin, leave the product origin off, and there is nothing left to review in the template layer.
Can it answer the governance questions our buyers send?
It can answer from the documents you indexed, and every answer carries numbered citations so a reviewer checks the source rather than trusting a chat message. What it cannot do is complete or sign an assessment, so a formal questionnaire should become a handover with the details landing in a real inbox.
A patient asked whether a reading was normal. What happens?
It refuses and signposts, provided the refusal was written for that. Set the match threshold high enough that a clinical question falls below it rather than reaching for the nearest explainer page, and put the emergency and crisis routes in the refusal text so they appear in the first reply rather than the third.

Keep reading

Try it on your own material

Upload a document or point it at your site, paste one line of HTML, then ask it something only your business could answer.