15.3.18. crate_anon.crateweb.consent.models


Copyright (C) 2015-2018 Rudolf Cardinal (rudolf@pobox.com).

This file is part of CRATE.

CRATE is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

CRATE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with CRATE. If not, see <http://www.gnu.org/licenses/>.


class crate_anon.crateweb.consent.models.CharityPaymentRecord(id, created_at, payee, amount)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.ClinicianResponse(id, created_at, contact_request, token, responded, responded_at, response_route, email_choice, response, veto_reason, ineligible_reason, pt_uncontactable_reason, clinician_confirm_name, charity_amount_due, processed, processed_at)[source]
exception DoesNotExist
exception MultipleObjectsReturned
finalize_a() → None[source]

Call this when the clinician completes their response. Part A: immediate, for acknowledgement.

finalize_b() → None[source]

Call this when the clinician completes their response. Part B: background.

class crate_anon.crateweb.consent.models.ConsentMode(id, decision_signed_by_patient, decision_otherwise_directly_authorized_by_patient, decision_under16_signed_by_parent, decision_under16_signed_by_clinician, decision_lack_capacity_signed_by_representative, decision_lack_capacity_signed_by_clinician, nhs_number, current, created_at, created_by, exclude_entirely, consent_mode, consent_after_discharge, max_approaches_per_year, other_requests, prefers_email, changed_by_clinician_override, source, skip_letter_to_patient, needs_processing, processed, processed_at)[source]
exception DoesNotExist
exception MultipleObjectsReturned
consider_withdrawal() → None[source]

If required, withdraw consent for other studies.

Note that as per Major Amendment 1 to 12/EE/0407, this happens automatically, rather than having a special flag to control it.

get_confirm_traffic_to_patient_letter_html() → str[source]

REC DOCUMENT 07. Confirming patient’s traffic-light choice.

classmethod get_or_create(nhs_number: int, created_by: auth.User) → ConsentMode[source]

Fetches the current ConsentMode for this patient. If there isn’t one, creates a default one and returns that.

classmethod get_or_none(nhs_number: int) → Union[ConsentMode, NoneType][source]

Fetches the current ConsentMode for this patient. If there isn’t one, returns None

process_change() → None[source]

Called upon saving. Will create a letter to patient. May create a withdrawal-of-consent letter to researcher.

Major Amendment 1 (Oct 2014) to 12/EE/0407: always withdraw consent and tell researchers, i.e. “active cancellation” of ongoing permission, where the researchers have not yet made contact.

classmethod refresh_from_primary_clinical_record(nhs_number: int, created_by: auth.User, source_db: str = None) → List[str][source]

Checks the primary clinical record and CRATE’s own records for consent modes for this patient. If the most recent one is in the external database, copies it to CRATE’s database and marks that one as current.

This has the effect that external primary clinical records (e.g. RiO) take priority, but if there’s no record in RiO, we can still proceed.

Returns a list of human-readable decisions.

Internally, we do this:

  • Fetch the most recent record.
  • If its date is later than the most recent CRATE record:
    • create a new ConsentMode with (…, source=source_db)
    • save it

Todo

also: use celery beat to refresh regularly +/- trigger withdrawal of consent if consent mode changed; http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html

Todo

also make automatic opt-out list

save(*args, **kwargs) → None[source]

Custom save method. Ensures that only one ConsentMode has current == True for a given patient.

Better than a get_latest_by clause, because with a flag like this, we can have a simple query that says “get the current records for all patients” – harder if done by date (group by patient, order by patient/date, pick last one for each patient…).

class crate_anon.crateweb.consent.models.ContactRequest(id, created_at, request_by, study, request_direct_approach, lookup_nhs_number, lookup_rid, lookup_mrid, processed, processed_at, nhs_number, patient_lookup, consent_mode, approaches_in_past_year, decisions, decided_no_action, decided_send_to_researcher, decided_send_to_clinician, clinician_involvement, consent_withdrawn, consent_withdrawn_at)[source]
exception DoesNotExist
exception MultipleObjectsReturned
classmethod create(request: django.http.request.HttpRequest, study: crate_anon.crateweb.consent.models.Study, request_direct_approach: bool, lookup_nhs_number: int = None, lookup_rid: str = None, lookup_mrid: str = None) → crate_anon.crateweb.consent.models.ContactRequest[source]

Create a contact request and act on it.

get_approval_email_html() → str[source]

Simple e-mail to researcher attaching letter.

get_approval_letter_html() → str[source]

REC DOCUMENT 15. Letter to researcher approving contact.

get_clinician_email_html(save: bool = True) → str[source]

REC DOCUMENTS 09, 11, 13 (A): E-mail to clinician E-mail to clinician asking them to pass on contact request.

URL method (path, querystring, both?): see notes in core/utils.py

In this case, decision: since we are creating a ClinicianResponse, we should use its ModelForm.

  • URL path for PK
  • querystring for other parameters, with form-based validation
get_letter_clinician_to_pt_re_study() → str[source]

REC DOCUMENTS 10, 12, 14: draft letters from clinician to patient, with decision form.

get_permission_date() → Union[datetime.datetime, NoneType][source]

When was the researcher given permission? Used for the letter withdrawing permission.

get_withdrawal_email_html() → str[source]

Simple e-mail to researcher attaching letter.

get_withdrawal_letter_html() → str[source]

REC DOCUMENT 16. Letter to researcher notifying them of withdrawal of consent.

process_request_main() → None[source]

Act on a contact request and store the decisions made. CORE DECISION-MAKING FUNCTION FOR THE CONSENT-TO-CONTACT PROCESS.

The decisions parameter is a list that’s appended to.

class crate_anon.crateweb.consent.models.DummyPatientSourceInfo(id, pt_local_id_description, pt_local_id_number, pt_dob, pt_dod, pt_dead, pt_discharged, pt_discharge_date, pt_sex, pt_title, pt_first_name, pt_last_name, pt_address_1, pt_address_2, pt_address_3, pt_address_4, pt_address_5, pt_address_6, pt_address_7, pt_telephone, pt_email, gp_title, gp_first_name, gp_last_name, gp_address_1, gp_address_2, gp_address_3, gp_address_4, gp_address_5, gp_address_6, gp_address_7, gp_telephone, gp_email, clinician_title, clinician_first_name, clinician_last_name, clinician_address_1, clinician_address_2, clinician_address_3, clinician_address_4, clinician_address_5, clinician_address_6, clinician_address_7, clinician_telephone, clinician_email, clinician_is_consultant, clinician_signatory_title, nhs_number)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.Email(id, created_at, sender, recipient, subject, msg_text, msg_html, to_clinician, to_researcher, to_patient, study, contact_request, letter)[source]
exception DoesNotExist
exception MultipleObjectsReturned
send(user: auth.User = None, resend: bool = False) → Union[EmailTransmission, NoneType][source]

Sends the e-mail.

Parameters:
  • user – the sender.
  • resend – say that it’s OK to resend one that’s already been sent.
Returns:

an EmailTransmission object.

class crate_anon.crateweb.consent.models.EmailAttachment(*args, **kwargs)[source]

E-mail attachment class that does NOT manage its own files, i.e. if the attachment object is deleted, the files won’t be. Use this for referencing files already stored elsewhere in the database.

exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.EmailTransmission(id, email, at, by, sent, failure_reason)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.Leaflet(id, name, pdf)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.Letter(id, created_at, pdf, to_clinician, to_researcher, to_patient, rdbm_may_view, study, contact_request, sent_manually_at)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.PatientLookup(*args, **kwargs)[source]

Represents a moment of lookup up identifiable data about patient, GP, and clinician from the relevant clinical database.

Inherits from PatientLookupBase so it has the same fields, and more.

exception DoesNotExist
exception MultipleObjectsReturned
get_first_traffic_light_letter_html() → str[source]

REC DOCUMENT 06. Covering letter to patient for first enquiry about research preference

class crate_anon.crateweb.consent.models.PatientLookupBase(*args, **kwargs)[source]

Base class for PatientLookup and DummyPatientSourceInfo. Must be able to be instantiate with defaults, for the “not found” situation.

days_since_discharge() → Union[int, NoneType][source]

Returns days since discharge, or None if the patient is not discharged (or unknown).

set_gp_name_components(name: str, decisions: List[str], secret_decisions: List[str]) → None[source]

Takes name, and stores it in the gp_title, gp_first_name, and gp_last_name fields.

class crate_anon.crateweb.consent.models.PatientResponse(id, decision_signed_by_patient, decision_otherwise_directly_authorized_by_patient, decision_under16_signed_by_parent, decision_under16_signed_by_clinician, decision_lack_capacity_signed_by_representative, decision_lack_capacity_signed_by_clinician, created_at, contact_request, recorded_by, response, processed, processed_at)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.Study(id, institutional_id, title, lead_researcher, registered_at, summary, search_methods_planned, patient_contact, include_under_16s, include_lack_capacity, clinical_trial, include_discharged, request_direct_approach, approved_by_rec, rec_reference, approved_locally, local_approval_at, study_details_pdf, subject_form_template_pdf)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.consent.models.TeamInfo[source]

Class only exists to be able to use @cached_property.

class crate_anon.crateweb.consent.models.TeamRep(*args, **kwargs)[source]

Clinical team representatives are recorded in CRATE.

exception DoesNotExist
exception MultipleObjectsReturned
crate_anon.crateweb.consent.models.auto_delete_emailattachment_files_on_change(sender: Type[crate_anon.crateweb.consent.models.EmailAttachment], instance: crate_anon.crateweb.consent.models.EmailAttachment, **kwargs) → None[source]

Deletes files from filesystem when EmailAttachment object is changed.

crate_anon.crateweb.consent.models.auto_delete_emailattachment_files_on_delete(sender: Type[crate_anon.crateweb.consent.models.EmailAttachment], instance: crate_anon.crateweb.consent.models.EmailAttachment, **kwargs) → None[source]

Deletes files from filesystem when EmailAttachment object is deleted.

crate_anon.crateweb.consent.models.auto_delete_leaflet_files_on_change(sender: Type[crate_anon.crateweb.consent.models.Leaflet], instance: crate_anon.crateweb.consent.models.Leaflet, **kwargs) → None[source]

Deletes files from filesystem when Leaflet object is changed.

crate_anon.crateweb.consent.models.auto_delete_leaflet_files_on_delete(sender: Type[crate_anon.crateweb.consent.models.Leaflet], instance: crate_anon.crateweb.consent.models.Leaflet, **kwargs) → None[source]

Deletes files from filesystem when Leaflet object is deleted.

crate_anon.crateweb.consent.models.auto_delete_letter_files_on_change(sender: Type[crate_anon.crateweb.consent.models.Letter], instance: crate_anon.crateweb.consent.models.Letter, **kwargs) → None[source]

Deletes files from filesystem when Letter object is changed.

crate_anon.crateweb.consent.models.auto_delete_letter_files_on_delete(sender: Type[crate_anon.crateweb.consent.models.Letter], instance: crate_anon.crateweb.consent.models.Letter, **kwargs) → None[source]

Deletes files from filesystem when Letter object is deleted.

crate_anon.crateweb.consent.models.auto_delete_study_files_on_change(sender: Type[crate_anon.crateweb.consent.models.Study], instance: crate_anon.crateweb.consent.models.Study, **kwargs) → None[source]

Deletes files from filesystem when Study object is changed.

crate_anon.crateweb.consent.models.auto_delete_study_files_on_delete(sender: Type[crate_anon.crateweb.consent.models.Study], instance: crate_anon.crateweb.consent.models.Study, **kwargs) → None[source]

Deletes files from filesystem when Study object is deleted.

crate_anon.crateweb.consent.models.leaflet_upload_to(instance: crate_anon.crateweb.consent.models.Leaflet, filename: str) → str[source]

Determines the filename used for leaflet uploads.

Parameters:
  • instance – instance of Leaflet (potentially unsaved) … and you can’t call save(); it goes into infinite recursion
  • filename – uploaded filename
crate_anon.crateweb.consent.models.make_dummy_objects(request: django.http.request.HttpRequest) → crate_anon.crateweb.consent.models.DummyObjectCollection[source]

We want to create these objects in memory, without saving to the DB. However, Django is less good at SQLAlchemy for this, and saves.

A simple method works for an SQLite backend database but fails with an IntegrityError for MySQL/SQL Server. For example:

IntegrityError at /draft_traffic_light_decision_form/-1/html/ (1452, ‘Cannot add or update a child row: a foreign key constraint fails (crate_django.`consent_study_researchers`, CONSTRAINT consent_study_researchers_study_id_19bb255f_fk_consent_study_id FOREIGN KEY (study_id) REFERENCES consent_study (id))’)

This occurs in the first creation, of a Study, and only if you specify ‘researchers’.

The reason for the crash is that ‘researchers’ is a ManyToManyField, and Django is trying to set the user.studies_as_researcher back-reference, but can’t do so because the Study doesn’t have a PK yet.

Since this is a minor thing, and templates are unaffected, and this is only for debugging, let’s ignore it.

crate_anon.crateweb.consent.models.study_details_upload_to(instance: crate_anon.crateweb.consent.models.Study, filename: str) → str[source]

Determines the filename used for study information PDF uploads.

Parameters:
  • instance – instance of Study (potentially unsaved) … and you can’t call save(); it goes into infinite recursion
  • filename – uploaded filename
crate_anon.crateweb.consent.models.study_form_upload_to(instance: crate_anon.crateweb.consent.models.Study, filename: str) → str[source]

Determines the filename used for study clinician-form PDF uploads.

Parameters:
  • instance – instance of Study (potentially unsaved)
  • filename – uploaded filename