def clean(self): if self.cleaned_data.get("hiv_pos_date"): enrollment = SubjectScreening.objects.get( subject_identifier=self.cleaned_data.get( "subject_visit").subject_identifier) if (self.cleaned_data.get("hiv_pos_date") > enrollment.clinic_registration_date): formatted_date = enrollment.clinic_registration_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise forms.ValidationError({ "hiv_pos_date": (f"Cannot be after clinic enrollment date. " f"({formatted_date})") }) if self.cleaned_data.get("hiv_pos_date") > self.cleaned_data.get( "arv_initiation_date"): formatted_date = self.cleaned_data.get( "hiv_pos_date").strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise forms.ValidationError({ "arv_initiation_date": (f"Cannot be before HIV diagnosis date. " f"({formatted_date})") }) self.validate_other_specify(field="arv_regimen")
def check_consent_period_within_study_period(self, new_consent=None): """Raises if the start or end date of the consent period it not within the opening and closing dates of the protocol. """ edc_protocol_app_config = django_apps.get_app_config("edc_protocol") study_open_datetime = edc_protocol_app_config.study_open_datetime study_close_datetime = edc_protocol_app_config.study_close_datetime for index, dt in enumerate([new_consent.start, new_consent.end]): if not (study_open_datetime <= dt <= study_close_datetime): dt_label = "start" if index == 0 else "end" formatted_study_open_datetime = study_open_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) formatted_study_close_datetime = study_close_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) formatted_dt = dt.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) raise ConsentPeriodError( f"Invalid consent. Consent period for {new_consent.name} " "must be within study opening/closing dates of " f"{formatted_study_open_datetime} - " f"{formatted_study_close_datetime}. " f"Got {dt_label}={formatted_dt}." )
def get_changeform_initial_data(self, request): """Updates initial data with the description of the original AE. """ initial = super().get_changeform_initial_data(request) AeInitial = get_ae_model("aeinitial") try: ae_initial = AeInitial.objects.get( pk=request.GET.get("ae_initial")) except ObjectDoesNotExist: pass else: try: ae_classification = ae_initial.ae_classification.name except AttributeError: ae_classification = None else: if ae_initial.ae_classification.name == OTHER: other = ae_initial.ae_classification_other.rstrip() ae_classification = f"{ae_classification}: {other}" report_datetime = ae_initial.report_datetime.strftime( convert_php_dateformat(settings.SHORT_DATETIME_FORMAT)) initial.update( ae_classification=ae_classification, ae_description=( f"{ae_initial.ae_description} (reported: {report_datetime})" ), ) return initial
def validate_drawn_date_by_dx_date(self, prefix: str, dx_msg_label: str, drawn_date_fld: Optional[str] = None): drawn_date_fld = drawn_date_fld or "drawn_date" dx = Diagnoses( subject_visit=self.cleaned_data.get("subject_visit"), lte=True, limit_to_single_condition_prefix=prefix, ) try: dx_date = dx.get_dx_date(prefix) except InitialReviewRequired: dx_date = None if not dx_date: raise forms.ValidationError( f"A {dx_msg_label} diagnosis has not been reported for this subject." ) else: if dx_date > self.cleaned_data.get(drawn_date_fld): formatted_date = dx_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise forms.ValidationError({ "drawn_date": ("Invalid. Subject was diagnosed with " f"{dx_msg_label} on {formatted_date}.") })
def raise_on_invalid_dx_date(self, dx_date_fld, registration_date): formatted_date = registration_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) if self.history is True: if self.cleaned_data.get(dx_date_fld) > registration_date: raise forms.ValidationError( { dx_date_fld: ( f"Cannot be after clinic enrollment date. " f"({formatted_date})" ) } ) elif self.history is False: if self.cleaned_data.get(dx_date_fld) < registration_date: raise forms.ValidationError( { dx_date_fld: ( f"Cannot be before clinic enrollment date. " f"({formatted_date})" ) } ) else: raise ImproperlyConfigured( f"Expected `history` attribute to be True or False. See `{self.__class__.__name__}`" ) return None
def clean(self): super().clean() # confirm biomedical_history is complete try: BiomedicalHistory.objects.get( subject_visit=self.cleaned_data.get("subject_visit")) except ObjectDoesNotExist: raise forms.ValidationError( f"{BiomedicalHistory._meta.verbose_name} for this subject must be completed first." ) enrollment = SubjectScreening.objects.get( subject_identifier=self.cleaned_data.get( "subject_visit").subject_identifier) formatted_date = enrollment.clinic_registration_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) for fld in BiomedicalFollowup._meta.get_fields(): if fld.name.endswith("_date"): if self.cleaned_data.get(fld.name): if (self.cleaned_data.get(fld.name) <= enrollment.clinic_registration_date): raise forms.ValidationError({ fld.name: (f"Cannot be before the " f"enrollment date ({formatted_date}).") })
def validate_death_report_if_deceased(self): """Validates death report exists of termination_reason is "DEAD. Death "date" is the naive date of the settings.TIME_ZONE datetime. Note: uses __date field lookup. If using mysql don't forget to load timezone info. """ subject_identifier = ( self.cleaned_data.get("subject_identifier") or self.instance.subject_identifier ) try: death_report = self.death_report_model_cls.objects.get( subject_identifier=subject_identifier ) except ObjectDoesNotExist: if self.cleaned_data.get("termination_reason") == DEAD: raise forms.ValidationError( { "termination_reason": "Patient is deceased, please complete " "death report form first." } ) else: local_death_datetime = arrow.get( death_report.death_datetime, tz.gettz(settings.TIME_ZONE) ) if self.cleaned_data.get("death_date") and ( local_death_datetime.date() != self.cleaned_data.get("death_date") ): expected = local_death_datetime.date().strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) got = self.cleaned_data.get("death_date").strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) raise forms.ValidationError( { "death_date": "Date does not match Death Report. " f"Expected {expected}. Got {got}." } )
def match_date_of_death_or_raise(self): """Raises an exception if the death date reported here does not match that from the Death Report.""" try: death_date = self.cleaned_data.get(self.death_date_field).date() except AttributeError: death_date = self.cleaned_data.get(self.death_date_field) if self.death_report_date != death_date: expected = self.death_report_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) got = death_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise forms.ValidationError({ self.death_date_field: "Date does not match Death Report. " f"Expected {expected}. Got {got}." }) return None
def onschedule_or_raise(self, subject_identifier=None, report_datetime=None, compare_as_datetimes=None): """Raise an exception if subject is not on the schedule during the given date. """ compare_as_datetimes = True if compare_as_datetimes is None else compare_as_datetimes try: onschedule_obj = self.onschedule_model_cls.objects.get( subject_identifier=subject_identifier) except ObjectDoesNotExist: raise NotOnScheduleError( f"Subject has not been put on a schedule `{self.schedule_name}`. " f"Got subject_identifier=`{subject_identifier}`.") try: offschedule_datetime = self.offschedule_model_cls.objects.values_list( "offschedule_datetime", flat=True).get(subject_identifier=subject_identifier) except ObjectDoesNotExist: offschedule_datetime = None if compare_as_datetimes: in_date_range = (onschedule_obj.onschedule_datetime <= report_datetime <= (offschedule_datetime or get_utcnow())) else: in_date_range = (onschedule_obj.onschedule_datetime.date() <= report_datetime.date() <= (offschedule_datetime or get_utcnow()).date()) if offschedule_datetime and not in_date_range: formatted_offschedule_datetime = offschedule_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) formatted_report_datetime = report_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise NotOnScheduleForDateError( f"Subject not on schedule '{self.schedule_name}' for " f"report date '{formatted_report_datetime}'. " f"Got '{subject_identifier}' was taken " f"off this schedule on '{formatted_offschedule_datetime}'.") return None
def validate_is_singleton(model_cls, cleaned_data): try: model_cls.objects.get(log_date=cleaned_data.get("log_date"), site=cleaned_data.get("site")) except ObjectDoesNotExist: pass else: formatted_date = cleaned_data.get("log_date").strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise forms.ValidationError( f"A report for {formatted_date} has already been submitted.")
def get_consent( self, consent_model=None, report_datetime=None, version=None, consent_group=None, ): """Return consent object, not model, valid for the datetime.""" app_config = django_apps.get_app_config("edc_consent") consent_group = consent_group or app_config.default_consent_group registered_consents = self.registry.values() if consent_group: registered_consents = [c for c in registered_consents if c.group == consent_group] if not registered_consents: raise ConsentObjectDoesNotExist( f"No matching consent in site consents. " f"Got consent_group={consent_group}." ) if version: registered_consents = [c for c in registered_consents if c.version == version] if not registered_consents: raise ConsentObjectDoesNotExist( f"No matching consent in site consents. " f"Got consent_group={consent_group}, version={version}." ) if consent_model: registered_consents = [c for c in registered_consents if c.model == consent_model] if not registered_consents: raise ConsentObjectDoesNotExist( f"No matching consent in site consents. " f"Got consent_group={consent_group}, version={version}, " f"model={consent_model}." ) registered_consents = [ c for c in registered_consents if c.start <= report_datetime <= c.end ] if not registered_consents: raise ConsentObjectDoesNotExist( f"No matching consent in site consents. " f"Got consent_group={consent_group}, version={version}, " f"model={consent_model}, report_datetime={report_datetime}." ) elif len(registered_consents) > 1: consents = list(set([c.name for c in registered_consents])) formatted_report_datetime = report_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) raise ConsentError( f"Multiple consents found, using consent model={consent_model}, " f"date={formatted_report_datetime}, " f"consent_group={consent_group}, version={version}. " f"Got {consents}" ) return registered_consents[0]
def available_arw( self, suggested_datetime=None, forward_delta=None, reverse_delta=None, taken_datetimes=None, schedule_on_holidays=None, ): """Returns an arrow object for a datetime equal to or close to the suggested datetime. To exclude datetimes other than holidays, pass a list of datetimes in UTC to `taken_datetimes`. """ available_arw = None forward_delta = forward_delta or relativedelta(months=1) reverse_delta = reverse_delta or relativedelta(months=0) taken_arw = [self.to_arrow_utc(dt) for dt in taken_datetimes or []] if suggested_datetime: suggested_arw = arrow.Arrow.fromdatetime(suggested_datetime) else: suggested_arw = arrow.Arrow.fromdatetime(get_utcnow()) arw_span_range, min_arw, max_arw = self.get_arw_span( suggested_arw, forward_delta, reverse_delta, ) for arw in arw_span_range: # add back time to arrow object, r if arw.date().weekday() in self.weekdays and ( min_arw.date() <= arw.date() < max_arw.date()): is_holiday = False if schedule_on_holidays else self.is_holiday( arw) if (not is_holiday and arw.date() not in [a.date() for a in taken_arw] and self.open_slot_on(arw)): available_arw = arw break if not available_arw: if self.best_effort_available_datetime: available_arw = suggested_arw else: formatted_date = suggested_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise FacilityError( f"No available appointment dates at facility for period. " f"Got no available dates within {reverse_delta.days}-" f"{forward_delta.days} days of {formatted_date}. " f"Facility is {repr(self)}.") available_arw = arrow.Arrow.fromdatetime( datetime.combine(available_arw.date(), suggested_arw.time())) return available_arw
def consented_or_raise(self): try: self.consent_model_cls.objects.get( subject_identifier=self.subject_identifier, version=self.version) except ObjectDoesNotExist: formatted_report_datetime = self.report_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise NotConsentedError( f"Consent is required. Cannot find '{self.consent_model} " f"version {self.version}' when saving model '{self.model}' for " f"subject '{self.subject_identifier}' with date " f"'{formatted_report_datetime}' .")
def validate_subject_transferred(self): if self.cleaned_data.get("subject_identifier") or self.instance: subject_identifier = (self.cleaned_data.get("subject_identifier") or self.instance.subject_identifier) try: subject_transfer_obj = django_apps.get_model( self.subject_transfer_model).objects.get( subject_identifier=subject_identifier) except ObjectDoesNotExist: if (self.cleaned_data.get(self.offschedule_reason_field) and self.cleaned_data.get( self.offschedule_reason_field).name == self.subject_transfer_reason): msg = ( "Patient has been transferred, please complete " f"`{self.subject_transfer_model_cls._meta.verbose_name}` " "form first.") raise forms.ValidationError( {self.offschedule_reason_field: msg}) else: if self.cleaned_data.get( self.subject_transfer_date_field) and ( subject_transfer_obj.transfer_date != self.cleaned_data.get( self.subject_transfer_date_field)): expected = subject_transfer_obj.transfer_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) got = self.cleaned_data.get( self.subject_transfer_date_field).strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) raise forms.ValidationError({ self.subject_transfer_date_field: ("Date does not match " f"`{self.subject_transfer_model_cls._meta.verbose_name}` " f"form. Expected {expected}. Got {got}.") })
def validate_diagnosis_before_refill(self): """Assert subject has been diagnosed for the condition for which they require a medication refill, including for the current timepoint.""" diagnoses = Diagnoses( subject_identifier=self.subject_identifier, report_datetime=self.report_datetime, lte=True, ) try: diagnoses.get_initial_reviews() except InitialReviewRequired as e: raise forms.ValidationError(e) options = [] for prefix, label in settings.EDC_DX_LABELS.items(): options.append( ( f"refill_{prefix}", diagnoses.get_dx(prefix), diagnoses.get_dx_date(prefix), label, ), ) for fld, dx, dx_date, label in options: if self.cleaned_data.get(fld) == NOT_APPLICABLE and dx: formatted_date = dx_date.strftime(convert_php_dateformat(settings.DATE_FORMAT)) raise forms.ValidationError( { fld: ( f"Invalid. Subject was previously diagnosed with {label} " f"on {formatted_date}. Expected YES/NO." ) } ) elif self.cleaned_data.get(fld) != NOT_APPLICABLE and not dx: raise forms.ValidationError( { fld: ( "Invalid. Subject has not been diagnosed with " f"{label}. Expected N/A. See also the " f"`{ClinicalReview._meta.verbose_name}` CRF." ) } )
def validate_diagnosis_before_refill(self): """Assert subject has been diagnosed for the condition for which they require a medication refill, including for the current timepoint.""" diagnoses = Diagnoses( subject_identifier=self.subject_identifier, report_datetime=self.report_datetime, lte=True, ) try: diagnoses.initial_reviews except InitialReviewRequired as e: raise forms.ValidationError(e) options = [ ("refill_htn", diagnoses.get_dx(HTN), diagnoses.get_dx_date(HTN), "hypertension"), ("refill_dm", diagnoses.get_dx(DM), diagnoses.get_dx_date(DM), "diabetes"), ("refill_hiv", diagnoses.get_dx(HIV), diagnoses.get_dx_date(HIV), "HIV"), ] for fld, dx, dx_date, label in options: if self.cleaned_data.get(fld) == NOT_APPLICABLE and dx: formatted_date = dx_date.strftime(convert_php_dateformat(settings.DATE_FORMAT)) raise forms.ValidationError( { fld: ( f"Invalid. Subject was previously diagnosed with {label} " f"on {formatted_date}. Expected YES/NO." ) } ) elif self.cleaned_data.get(fld) != NOT_APPLICABLE and not dx: raise forms.ValidationError( { fld: ( "Invalid. Subject has not been diagnosed with " f"{label}. Expected N/A. See also the " f"`{ClinicalReview._meta.verbose_name}` CRF." ) } )
def validate_study_day_with_datetime( self, subject_identifier=None, study_day=None, compare_date=None, study_day_field=None, ): """Raises an exception if study day does not match calculation against pytz. Note: study-day is 1-based. """ if study_day is not None and compare_date is not None: try: compare_date = compare_date.date() except AttributeError: pass subject_identifier = (subject_identifier or self.cleaned_data.get("subject_identifier") or self.instance.subject_identifier) if not subject_identifier: raise ValueError( f"Subject identifier cannot be None. See {repr(self)}") registered_subject_model_cls = django_apps.get_model( "edc_registration.registeredsubject") randomization_datetime = registered_subject_model_cls.objects.get( subject_identifier=subject_identifier).randomization_datetime days_on_study = (compare_date - randomization_datetime.date()).days if study_day - 1 != days_on_study: tz = pytz.timezone(settings.TIME_ZONE) formatted_date = ( Arrow.fromdatetime(randomization_datetime).to(tz).strftime( convert_php_dateformat(settings.DATETIME_FORMAT))) message = { study_day_field: (f"Invalid. Expected {days_on_study + 1}. " f"Subject was registered on {formatted_date}") } print(message) self._errors.update(message) self._error_codes.append(INVALID_ERROR) raise forms.ValidationError(message, code=INVALID_ERROR)
def clean(self): super().clean() enrollment = SubjectScreening.objects.get( subject_identifier=self.cleaned_data.get( "subject_visit").subject_identifier) formatted_date = enrollment.clinic_registration_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) for fld in BiomedicalHistory._meta.get_fields(): if fld.name.endswith("_date"): if self.cleaned_data.get(fld.name): if self.cleaned_data.get( fld.name ) > enrollment.clinic_registration_date + relativedelta( months=12): raise forms.ValidationError({ fld.name: (f"Cannot be more than 12 months after the " f"enrollment date ({formatted_date}).") })
def clean(self): enrollment = SubjectScreening.objects.get( subject_identifier=self.cleaned_data.get("subject_visit").subject_identifier ) formatted_date = enrollment.clinic_registration_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) if self.cleaned_data.get("death_date"): if ( self.cleaned_data.get("death_date") <= enrollment.clinic_registration_date ): raise forms.ValidationError( { "death_date": ( f"Cannot be on or before the " f"enrollment date ({formatted_date})." ) } ) self.required_if(YES, field="death_cause_known", field_required="death_cause")
def validate_consent_datetime(self): """Validate consent datetime with the report_datetime instead of eligibility datetime. report_datetime must come first. Watchout for timezone, cleaned_data has local TZ. """ if (self.consent_datetime - self.subject_screening.report_datetime).total_seconds() < 0: local_dt = self.subject_screening.report_datetime.astimezone( self.tz) formatted = local_dt.strftime( convert_php_dateformat(settings.SHORT_DATETIME_FORMAT)) raise forms.ValidationError( { "consent_datetime": (f"Cannot be before the date and time screening " f"information was reported. Report datetime was " f"{formatted}.") }, )
def __init__(self, action_item=None, href=None, action_name=None, related_action_item=None): self._parent_reference_obj = None self._parent_reference_url = None self._reference_obj = None self._reference_url = None self._related_reference_obj = None self._related_reference_url = None self.href = href self.last_updated_text = "This action item has not been updated." if not action_item and action_name: self.action_identifier = None self.action_item = None self.action_cls = site_action_items.get(action_name) self.related_action_item = related_action_item if self.action_cls.related_reference_fk_attr and not related_action_item: raise ActionItemHelperError( f"Expected related_action_item. Got None. " f"Related field attribute is '" f"{self.action_cls.related_reference_fk_attr}'. " f"See {repr(self)}.") else: self.action_identifier = action_item.action_identifier self.action_item = action_item self.action_cls = action_item.reference_model_cls.get_action_cls() self.related_action_item = self.action_item.related_action_item if self.action_item.last_updated: # could also use action_item.linked_to_reference? date_format = convert_php_dateformat( settings.SHORT_DATE_FORMAT) last_updated = self.action_item.last_updated.strftime( date_format) user_last_updated = self.action_item.user_last_updated self.last_updated_text = ( f"Last updated on {last_updated} by {user_last_updated}.")
def validate_appt_datetime_in_window(self): if not self.instance.is_baseline_appt: baseline_timepoint_datetime = self.instance.__class__.objects.first_appointment( subject_identifier=self.instance.subject_identifier, visit_schedule_name=self.instance.visit_schedule_name, schedule_name=self.instance.schedule_name, ).timepoint_datetime datestring = convert_php_dateformat(settings.SHORT_DATE_FORMAT) self.instance.visit_from_schedule.timepoint_datetime = ( self.instance.timepoint_datetime) lower = self.instance.visit_from_schedule.dates.lower.strftime( datestring) try: self.instance.schedule.datetime_in_window( timepoint_datetime=self.instance.timepoint_datetime, dt=self.cleaned_data.get("appt_datetime"), visit_code=self.instance.visit_code, visit_code_sequence=self.instance.visit_code_sequence, baseline_timepoint_datetime=baseline_timepoint_datetime, ) except UnScheduledVisitWindowError: upper = self.instance.schedule.visits.next( self.instance.visit_code).dates.lower.strftime(datestring) raise forms.ValidationError({ "appt_datetime": (f"Invalid. Expected a date between {lower} and {upper} (U)." ) }) except ScheduledVisitWindowError: upper = self.instance.visit_from_schedule.dates.upper.strftime( datestring) raise forms.ValidationError({ "appt_datetime": (f"Invalid. Expected a date between {lower} and {upper} (S)." ) })
def __str__(self): return self.log_date.strftime( convert_php_dateformat(settings.DATE_FORMAT))
def description(obj): dob = obj.rx.registered_subject.dob.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT)) return f"{obj.rx.registered_subject.initials} {dob} {obj.rx.registered_subject.gender}"
def consented(obj): return obj.rx.registered_subject.consent_datetime.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT))
def __str__(self): formatted_date = timezone.localtime(self.report_datetime).strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT) ) return f"{self.subject_identifier} {formatted_date}"
def formatted_date(self): return self.local_date.strftime( convert_php_dateformat(settings.SHORT_DATE_FORMAT))