|pypi| |actions| |codecov| |downloads|
edc-visit-tracking
------------------
Track study participant visit reports.
Declaring a visit model
+++++++++++++++++++++++
A **visit_model** is declared using the model mixin `VisitModelMixin`. Normally, a **visit_model** will be declared with additional model mixins, but `VisitModelMixin` must be there.
.. code-block:: python
class SubjectVisit(VisitModelMixin, BaseUuidModel):
...
Also, ensure the `Meta` class attributes of `VisitModelMixin` are inherited. These include required constraints and ordering.
.. code-block:: python
class SubjectVisit(VisitModelMixin, BaseUuidModel):
...
class Meta(VisitModelMixin.Meta):
pass
Among other features, `VisitModelMixin` adds a `OneToOneField` foreign key to the **visit_model** that points to `edc_appointment.Appointment`.
Important: A **visit model** is a special model in the EDC. A model declared with the model mixin, `VisitModelMixin`, is the definition of a **visit model**. CRFs and Requisitions have a foreign key pointing to a **visit model**. A number of methods on CRFs and Requisitions detect their **visit model** foreign key name, model class and value by looking for the FK declared with `VisitModelMixin`.
For a subject that requires ICF the **visit model** would use the `RequiresConsentModelMixin`:
.. code-block:: python
class SubjectVisit(
VisitModelMixin,
RequiresConsentFieldsModelMixin,
BaseUuidModel,
):
class Meta(VisitModelMixin.Meta, BaseUuidModel.Meta):
pass
If the subject does not require ICF, such as an infant, don't include the `RequiresConsentModelMixin`:
.. code-block:: python
class InfantVisit(
VisitModelMixin,
BaseUuidModel
):
class Meta(VisitModelMixin.Meta, , BaseUuidModel.Meta):
pass
A more complete declaration will include model mixins from other libraries. For example:
.. code-block:: python
from edc_consent.model_mixins import RequiresConsentFieldsModelMixin
from edc_metadata.model_mixins.creates import CreatesMetadataModelMixin
from edc_model.models import BaseUuidModel
from edc_offstudy.model_mixins import OffstudyVisitModelMixin
from edc_reference.model_mixins import ReferenceModelMixin
from edc_sites.models import CurrentSiteManager
from edc_sites.models import SiteModelMixin
from edc_visit_tracking.managers import VisitModelManager
from edc_visit_tracking.model_mixins import VisitModelMixin
class SubjectVisit(
SiteModelMixin,
VisitModelMixin,
ReferenceModelMixin,
CreatesMetadataModelMixin,
RequiresConsentFieldsModelMixin,
OffstudyNonCrfModelMixin,
BaseUuidModel,
):
on_site = CurrentSiteManager()
objects = VisitModelManager()
history = edc_models.HistoricalRecords()
class Meta(VisitModelMixin.Meta, BaseUuidModel.Meta):
pass
Declaring a CRF
+++++++++++++++
The `CrfModelMixin` is required for all CRF models. CRF models have a `OneToOneField` key to a **visit model**.
.. code-block:: python
class CrfOne(CrfModelMixin, OffstudyCrfModelMixin, RequiresConsentModelMixin,
UpdatesCrfMetadataModelMixin, BaseUuidModel):
subject_visit = models.OneToOneField(SubjectVisit)
f1 = models.CharField(max_length=10, default='erik')
vl = models.CharField(max_length=10, default=NO)
rdb = models.CharField(max_length=10, default=NO)
class Meta:
consent_model = 'myapp.subjectconsent' # for RequiresConsentModelMixin
Declaring forms:
++++++++++++++++
The `VisitFormMixin` includes a number of common validations in the `clean` method:
.. code-block:: python
class SubjectVisitForm(VisitFormMixin, FormValidatorMixin, forms.ModelForm):
form_validator_cls = VisitFormValidator
class Meta:
model = SubjectVisit
`PreviousVisitModelMixin`
+++++++++++++++++++++++++
The `PreviousVisitModelMixin` ensures that visits are entered in sequence. It is included with the `VisitModelMixin`.
`VisitTrackingModelFormMixin`
+++++++++++++++++++++++++++++
see `DEFAULT_REPORT_DATETIME_ALLOWANCE`
Missed Visit Report
+++++++++++++++++++
A detail report should be submitted for scheduled visits that are missed.
By selecting the reason ``missed visit`` on ``SubjectVisit``, only the missed visit CRF will be required
for the timepoint. All other CRFs and requisitions will be excluded.
Unscheduled visits cannot be missed. (To change this behaviour see `settings` attrubute `EDC_VISIT_TRACKING_ALLOW_MISSED_UNSCHEDULED`)
The model mixin ``SubjectVisitMissedModelMixin`` provides the basic features of a `SubjectVisitMissed` model.
In your subject app declare:
.. code-block:: python
from django.db.models import PROTECT
from edc_crf.model_mixins import CrfWithActionModelMixin
from edc_model import models as edc_models
from edc_visit_tracking.model_mixins import SubjectVisitMissedModelMixin
class SubjectVisitMissed(SubjectVisitMissedModelMixin, edc_models.BaseUuidModel):
missed_reasons = models.ManyToManyField(
SubjectVisitMissedReasons, blank=True, related_name="+"
)
class Meta(CrfWithActionModelMixin.Meta, edc_models.BaseUuidModel.Meta):
verbose_name = "Missed Visit Report"
verbose_name_plural = "Missed Visit Report"
In your list model app, e.g. ``meta_lists``, declare the list model:
.. code-block:: python
class SubjectVisitMissedReasons(ListModelMixin):
class Meta(ListModelMixin.Meta):
verbose_name = "Subject Missed Visit Reasons"
verbose_name_plural = "Subject Missed Visit Reasons"
... and update the ``list_data`` dictionary, for example:
.. code-block:: python
list_data = {
...
"meta_lists.subjectvisitmissedreasons": [
("forgot", "Forgot / Can’t remember being told about appointment"),
("family_emergency", "Family emergency (e.g. funeral) and was away"),
("travelling", "Away travelling/visiting"),
("working_schooling", "Away working/schooling"),
("too_sick", "Too sick or weak to come to the centre"),
("lack_of_transport", "Transportation difficulty"),
(OTHER, "Other reason (specify below)",),
],
...
}
Window period
+++++++++++++
By default, the visit `report_datetime` is validated to stay within the same window period as the appointment.
This may be too restrictive in some cases.
To bypass this override ```validate_visit_datetime_in_window_period``` in the ```VisitFormValidator```
.. code-block:: python
from edc_visit_tracking.form_validators import VisitFormValidator as BaseVisitFormValidator
class VisitFormValidator(BaseVisitFormValidator):
...
def validate_visit_datetime_in_window_period():
pass
...
Be sure that your appointment form validator is enforcing window periods before
bypassing this check.
See also `edc_appointment`.
.. |pypi| image:: https://img.shields.io/pypi/v/edc-visit-tracking.svg
:target: https://pypi.python.org/pypi/edc-visit-tracking
.. |actions| image:: https://github.com/clinicedc/edc-visit-tracking/workflows/build/badge.svg?branch=develop
:target: https://github.com/clinicedc/edc-visit-tracking/actions?query=workflow:build
.. |codecov| image:: https://codecov.io/gh/clinicedc/edc-visit-tracking/branch/develop/graph/badge.svg
:target: https://codecov.io/gh/clinicedc/edc-visit-tracking
.. |downloads| image:: https://pepy.tech/badge/edc-visit-tracking
:target: https://pepy.tech/project/edc-visit-tracking