Skip to content

Commit

Permalink
get consent from site_consents, check consent date after query and be…
Browse files Browse the repository at this point in the history
…fore returning model instance, update tests, add tests for validity period
  • Loading branch information
erikvw committed Mar 26, 2024
1 parent c237df6 commit 14ccf6c
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 132 deletions.
9 changes: 8 additions & 1 deletion consent_app/baker_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
from faker import Faker
from model_bakery.recipe import Recipe, seq

from .models import SubjectConsent, SubjectConsentV1, SubjectConsentV2, SubjectConsentV3
from .models import (
SubjectConsent,
SubjectConsentV1,
SubjectConsentV2,
SubjectConsentV3,
SubjectConsentV4,
)

fake = Faker()

Expand Down Expand Up @@ -42,3 +48,4 @@ def get_opts():
subjectconsentv1 = Recipe(SubjectConsentV1, **get_opts())
subjectconsentv2 = Recipe(SubjectConsentV2, **get_opts())
subjectconsentv3 = Recipe(SubjectConsentV3, **get_opts())
subjectconsentv4 = Recipe(SubjectConsentV4, **get_opts())
35 changes: 8 additions & 27 deletions edc_consent/consent_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from edc_screening.utils import get_subject_screening_model
from edc_sites import site_sites
from edc_utils import floor_secs, formatted_date, formatted_datetime
from edc_utils.date import ceil_datetime, floor_datetime, to_local, to_utc
from edc_utils.date import ceil_datetime, floor_datetime, to_local

from .exceptions import (
ConsentDefinitionError,
Expand Down Expand Up @@ -120,42 +120,23 @@ def sites(self):
def get_consent_for(
self,
subject_identifier: str = None,
report_datetime: datetime | None = None,
site_id: int | None = None,
raise_if_not_consented: bool | None = None,
) -> ConsentLikeModel | None:
"""Returns a subject consent using this consent_definition's
`model_cls` and `version`.
If it does not exist and this consent_definition updates a
previous (`update_cdef`), will try again with the `update_cdef's`
model_cls and version.
Finally, if the subject consent does not exist raises a
`NotConsentedError`.
"""
consent_obj = None
raise_if_not_consented = (
True if raise_if_not_consented is None else raise_if_not_consented
)
opts: dict[str, str | datetime] = dict(
subject_identifier=subject_identifier, version=self.version
)
if report_datetime:
opts.update(consent_datetime__lte=to_utc(report_datetime))
opts = dict(subject_identifier=subject_identifier, version=self.version)
if site_id:
opts.update(site_id=site_id)
try:
consent_obj = self.model_cls.objects.get(**opts)
except ObjectDoesNotExist:
if self.updates:
opts.update(version=self.updates.version)
try:
consent_obj = self.updates.model_cls.objects.get(**opts)
except ObjectDoesNotExist:
pass
consent_obj = None
if not consent_obj and raise_if_not_consented:
dte = formatted_date(report_datetime)
raise NotConsentedError(
f"Consent not found. Has subject '{subject_identifier}' "
f"completed version '{self.version}' of consent on or after '{dte}'?"
f"Consent not found for this version. Has subject '{subject_identifier}' "
f"completed a version '{self.version}' consent?"
)
return consent_obj

Expand Down
4 changes: 4 additions & 0 deletions edc_consent/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ class AlreadyRegistered(Exception):

class SiteConsentError(Exception):
pass


class ConsentDefinitionNotConfiguredForUpdate(Exception):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

from edc_consent import ConsentDefinitionDoesNotExist, site_consents
from edc_consent.consent_definition import ConsentDefinition
from edc_consent.exceptions import NotConsentedError, SiteConsentError
from edc_consent.exceptions import (
ConsentDefinitionNotConfiguredForUpdate,
NotConsentedError,
SiteConsentError,
)


class ConsentDefinitionFormValidatorMixin:
Expand Down Expand Up @@ -38,18 +42,14 @@ def get_consent_or_raise(
Wraps func `consent_datetime_or_raise` to re-raise exceptions
as ValidationError.
"""

fldname = fldname or "report_datetime"
error_code = error_code or INVALID_ERROR
consent_definition = self.get_consent_definition(
report_datetime=report_datetime, fldname=fldname, error_code=error_code
)
# use the consent_definition to get the subject consent model instance
try:
consent_obj = consent_definition.get_consent_for(
subject_identifier=self.subject_identifier,
report_datetime=report_datetime,
consent_obj = site_consents.get_consent_or_raise(
subject_identifier=self.subject_consent, report_datetime=report_datetime
)
except NotConsentedError as e:
except (NotConsentedError, ConsentDefinitionNotConfiguredForUpdate) as e:
self.raise_validation_error({fldname: str(e)}, error_code)
return consent_obj

Expand Down
23 changes: 2 additions & 21 deletions edc_consent/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,7 @@ class ConsentObjectsByCdefManager(ConsentObjectsManager):
def get_queryset(self):
qs = super().get_queryset()
cdef = site_consents.get_consent_definition(model=qs.model._meta.label_lower)
# return qs.filter(
# version=cdef.version,
# consent_datetime__gte=cdef.start,
# consent_datetime__lte=cdef.end,
# )
return qs.filter(
version=cdef.version,
consent_datetime__gte=cdef.start,
consent_datetime__lte=cdef.end,
).exclude(consent_datetime__lt=cdef.start, consent_datetime__gt=cdef.end)
return qs.filter(version=cdef.version)


class CurrentSiteByCdefManager(CurrentSiteManager):
Expand All @@ -46,14 +37,4 @@ class CurrentSiteByCdefManager(CurrentSiteManager):
def get_queryset(self):
qs = super().get_queryset()
cdef = site_consents.get_consent_definition(model=qs.model._meta.label_lower)
# return qs.filter(
# version=cdef.version,
# consent_datetime__gte=cdef.start,
# consent_datetime__lte=cdef.end,
# )
return qs.filter(
site_id=cdef.site.site_id,
version=cdef.version,
consent_datetime__gte=cdef.start,
consent_datetime__lte=cdef.end,
).exclude(consent_datetime__lt=cdef.start, consent_datetime__gt=cdef.end)
return qs.filter(site_id=cdef.site.site_id, version=cdef.version)
44 changes: 25 additions & 19 deletions edc_consent/modelform_mixins/requires_consent_modelform_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

from django import forms
from edc_sites import site_sites
from edc_utils import floor_secs, formatted_date, formatted_datetime
from edc_utils.date import to_local, to_utc
from edc_utils import formatted_date
from edc_utils.date import to_utc

from .. import NotConsentedError
from ..consent_definition import ConsentDefinition
from ..exceptions import ConsentDefinitionDoesNotExist
from ..exceptions import (
ConsentDefinitionDoesNotExist,
ConsentDefinitionNotConfiguredForUpdate,
NotConsentedError,
)
from ..site_consents import site_consents

__all__ = ["RequiresConsentModelFormMixin"]

Expand All @@ -20,33 +24,35 @@ class RequiresConsentModelFormMixin:

def clean(self):
cleaned_data = super().clean()
self.validate_against_consent()
consent_obj = self.validate_against_consent()
self.validate_against_dob(consent_obj)
return cleaned_data

def validate_against_dob(self, consent_obj):
if to_utc(self.report_datetime).date() < consent_obj.dob:
dte_str = formatted_date(consent_obj.dob)
raise forms.ValidationError(f"Report datetime cannot be before DOB. Got {dte_str}")

def validate_against_consent(self) -> None:
"""Raise an exception if the report datetime doesn't make
sense relative to the consent.
"""
if self.report_datetime:
try:
model_obj = self.consent_definition.get_consent_for(
consent_obj = site_consents.get_consent_or_raise(
subject_identifier=self.get_subject_identifier(),
report_datetime=self.report_datetime,
)
except NotConsentedError as e:
except (NotConsentedError, ConsentDefinitionNotConfiguredForUpdate) as e:
raise forms.ValidationError({"__all__": str(e)})
if floor_secs(to_utc(self.report_datetime)) < floor_secs(
model_obj.consent_datetime
):
dte_str = formatted_datetime(to_local(model_obj.consent_datetime))
raise forms.ValidationError(
f"Report datetime cannot be before consent datetime. Got {dte_str}."
)
if to_utc(self.report_datetime).date() < model_obj.dob:
dte_str = formatted_date(model_obj.dob)
raise forms.ValidationError(
f"Report datetime cannot be before DOB. Got {dte_str}"
)
# if floor_secs(to_utc(self.report_datetime)) < floor_secs(
# model_obj.consent_datetime
# ):
# dte_str = formatted_datetime(to_local(model_obj.consent_datetime))
# raise forms.ValidationError(
# f"Report datetime cannot be before consent datetime. Got {dte_str}."
# )
return consent_obj

@property
def consent_definition(self) -> ConsentDefinition:
Expand Down
3 changes: 2 additions & 1 deletion edc_consent/models/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ def requires_consent_on_pre_save(instance, raw, using, update_fields, **kwargs):
consent_definition = site_consents.get_consent_definition(
site=site_sites.get(site.id), report_datetime=instance.report_datetime
)
consent_definition.get_consent_for(
site_consents.get_consent_or_raise(
subject_identifier=subject_identifier,
report_datetime=instance.report_datetime,
site_id=site.id,
)
instance.consent_version = consent_definition.version
instance.consent_model = consent_definition.model
51 changes: 48 additions & 3 deletions edc_consent/site_consents.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
AlreadyRegistered,
ConsentDefinitionDoesNotExist,
ConsentDefinitionError,
ConsentDefinitionNotConfiguredForUpdate,
SiteConsentError,
)

Expand Down Expand Up @@ -99,14 +100,58 @@ def validate_period_overlap_or_raise(self, cdef: ConsentDefinition):
f"Got {cdef.name}."
)

def get_consent_for(
self, subject_identifier: str, report_datetime: datetime, site_id: int
def get_consents(self, subject_identifier: str, site_id: int | None) -> list:
consents = []
for cdef in self.all():
if consent_obj := cdef.get_consent_for(
subject_identifier=subject_identifier,
site_id=site_id,
raise_if_not_consented=False,
):
consents.append(consent_obj)
return consents

def get_consent_or_raise(
self,
subject_identifier: str,
report_datetime: datetime,
site_id: int,
raise_if_not_consented: bool | None = None,
):
"""Returns a subject consent using this consent_definition's
`model_cls` and `version`.
If it does not exist and this consent_definition updates a
previous (`update_cdef`), will try again with the `update_cdef's`
model_cls and version.
Finally, if the subject consent does not exist raises a
`NotConsentedError`.
"""
from edc_sites.site import sites as site_sites # avoid circular import

raise_if_not_consented = (
True if raise_if_not_consented is None else raise_if_not_consented
)
single_site = site_sites.get(site_id)
cdef = self.get_consent_definition(report_datetime=report_datetime, site=single_site)
return cdef.get_consent_for(subject_identifier)
consent_obj = cdef.get_consent_for(
subject_identifier, raise_if_not_consented=raise_if_not_consented
)
if consent_obj and report_datetime < consent_obj.consent_datetime:
if not cdef.updates:
dte = formatted_date(report_datetime)
raise ConsentDefinitionNotConfiguredForUpdate(
f"Consent not configured to update any previous versions. "
f"Got '{cdef.version}'. "
f"Has subject '{subject_identifier}' completed version '{cdef.version}' "
f"of consent on or after report_datetime='{dte}'?"
)
else:
return cdef.updates.get_consent_for(
subject_identifier, raise_if_not_consented=raise_if_not_consented
)
return consent_obj

def get_consent_definition(
self,
Expand Down
Loading

0 comments on commit 14ccf6c

Please sign in to comment.