class RegisteredSubject(BaseUuidModel): subject_identifier = models.CharField(max_length=25) first_name = FirstnameField(null=True) last_name = LastnameField(verbose_name="Last name") gender = models.CharField(max_length=1, choices=GENDER)
class ChildBirth(UniqueSubjectIdentifierFieldMixin, SiteModelMixin, CryptoMixin, BaseUuidModel): """ A model completed by the user on the infant's birth. """ report_datetime = models.DateTimeField( verbose_name="Date and Time infant enrolled", validators=[ datetime_not_future, ]) first_name = FirstnameField( max_length=25, verbose_name="Infant's first name", help_text="If infant name is unknown or not yet determined, " "use Baby + birth order + mother's last name, e.g. 'Baby1Malane'") last_name = LastnameField( max_length=25, verbose_name="Infant's last name", help_text="If infant name is unknown or not yet determined, " "use Baby + birth order + mother's last name, e.g. 'Baby1Malane'") initials = EncryptedCharField(validators=[ RegexValidator(regex=r'^[A-Z]{2,3}$', message=('Ensure initials consist of letters ' 'only in upper case, no spaces.')) ], ) dob = models.DateField(verbose_name='Date of Birth', help_text="Must match labour and delivery report.", validators=[ date_not_future, ]) gender = models.CharField(max_length=10, choices=GENDER_UNDETERMINED) def __str__(self): return f'{self.first_name}, {self.initials}, {self.gender}' @property def registered_subject(self): """Return infant registered subject. """ registered_subject_cls = django_apps.get_model( 'edc_registration.registeredsubject') try: registered_subject = registered_subject_cls.objects.get( subject_identifier=self.subject_identifier) except registered_subject_cls.DoesNotExist: raise ValidationError( f'Registered Subject is missing for {self.subject_identifier}') else: return registered_subject class Meta: app_label = 'flourish_child' verbose_name = "Child Birth"
class PersonalFieldsMixin(CryptoMixin, models.Model): first_name = FirstnameField( null=True, blank=False) last_name = LastnameField( verbose_name="Last name", null=True, blank=False) initials = EncryptedCharField( validators=[RegexValidator( regex=r'^[A-Z]{2,3}$', message=('Ensure initials consist of letters ' 'only in upper case, no spaces.'))], null=True, blank=False) dob = models.DateField( verbose_name="Date of birth", null=True, blank=False) is_dob_estimated = IsDateEstimatedField( verbose_name="Is date of birth estimated?", null=True, blank=False) gender = models.CharField( verbose_name="Gender", choices=GENDER_UNDETERMINED, max_length=1, null=True, blank=False) guardian_name = LastnameField( verbose_name=('Guardian\'s last and first name'), validators=[FullNameValidator()], blank=True, null=True, help_text=mark_safe( 'Required only if participant is a minor.<BR>' 'Format is \'LASTNAME, FIRSTNAME\'. ' 'All uppercase separated by a comma.')) subject_type = models.CharField( max_length=25) class Meta: abstract = True
class InformedConsent(SiteModelMixin, BaseUuidModel): consent_datetime = models.DateTimeField( verbose_name='Consent datetime', default=get_utcnow, help_text='Date and time of consent.') first_name = FirstnameField(blank=False) last_name = LastnameField(blank=False) language = models.CharField( verbose_name='Language of consent', max_length=50, choices=settings.LANGUAGES, null=True, blank=True, help_text=('The language used for the consent process will ' 'also be used during data collection.')) is_literate = models.CharField( verbose_name='Is the participant literate?', max_length=5, choices=YES_NO, help_text='If No provide witness name on this form and signature' 'on the paper document') witness_fname = FirstnameField( verbose_name='Witness first name', max_length=5, ) witness_lname = LastnameField( verbose_name='Witness last name', max_length=5, ) gender = models.CharField(verbose_name="Gender", choices=GENDER, max_length=1) date_of_birth = models.DateField(verbose_name="Date of birth", validators=[ date_not_future, ]) is_estimated = IsDateEstimatedField( verbose_name="Is date of birth estimated?", null=True, blank=False) national_identity = IdentityField( verbose_name='Patient ID number (Omang)', # validators=[identity_check, ], unique=True) identity_type = models.CharField( verbose_name='What type of identity number is this?', max_length=25, choices=IDENTITY_TYPE) """"Review Questions""" reviewed_consent = models.CharField( verbose_name='I have reviewed the consent with the participant', max_length=3, choices=YES_NO, help_text='If no, participant is not eligible.') answered_all_questions = models.CharField( verbose_name='I have answered all questions the participant' ' had about the study', max_length=3, choices=YES_NO, help_text='If no, participant is not eligible.') asked_questions = models.CharField( verbose_name='I have asked the participant questions about' ' this study and the participant has demonstrated ' 'understanding', max_length=3, choices=YES_NO, help_text='If no, participant is not eligible.') have_verified = models.CharField( verbose_name='I have verified that the participant has' 'signed the consent form', max_length=3, choices=YES_NO, help_text='If no, participant is not eligible.') copy_of_consent = models.CharField( verbose_name='I have provided the participant with a copy' ' of their signed informed consent', max_length=3, choices=YES_NO, help_text='if declined, return copy with the consent') class Meta: verbose_name = "Informed Consent" verbose_name_plural = "Informed Consent"
class PersonalFieldsMixin(CryptoMixin, models.Model): first_name = FirstnameField( null=True, blank=False, validators=[ RegexValidator( regex=r"^([A-Z]+$|[A-Z]+\ [A-Z]+)$", message="Ensure name consist of letters only in upper case", ) ], help_text="Use UPPERCASE letters only.", ) last_name = LastnameField( verbose_name="Surname", null=True, blank=False, validators=[ RegexValidator( regex=r"^([A-Z]+$|[A-Z]+\ [A-Z]+)$", message="Ensure name consist of letters only in upper case", ) ], help_text="Use UPPERCASE letters only.", ) initials = EncryptedCharField( validators=[ RegexValidator( regex=r"^[A-Z]{2,3}$", message= "Ensure initials consist of letters only in upper case, no spaces.", ) ], null=True, blank=False, ) dob = models.DateField(verbose_name="Date of birth", null=True, blank=False) is_dob_estimated = IsDateEstimatedField( verbose_name="Is date of birth estimated?", null=True, blank=False) gender = models.CharField( verbose_name="Gender", choices=GENDER_UNDETERMINED, max_length=1, null=True, blank=False, ) guardian_name = LastnameField( verbose_name="Guardian's last and first name", validators=[FullNameValidator()], blank=True, null=True, help_text=mark_safe("Required only if participant is a minor.<BR>" "Format is 'LASTNAME, FIRSTNAME'. " "All uppercase separated by a comma."), ) subject_type = models.CharField(max_length=25) class Meta: abstract = True
class CallModelMixin(models.Model): subject_identifier = models.CharField(max_length=50) label = models.CharField(max_length=50) scheduled = models.DateField(default=date.today) repeats = models.BooleanField(default=False) call_datetime = models.DateTimeField( null=True, editable=False, help_text='last call datetime updated by call log entry') first_name = FirstnameField(verbose_name='First name', editable=False, null=True) initials = models.CharField(verbose_name='Initials', max_length=3, editable=False, null=True) consent_datetime = models.DateTimeField( verbose_name="Consent date and time", validators=[ datetime_not_before_study_start, datetime_not_future, ], help_text="From Subject Consent.", null=True) call_attempts = models.IntegerField(default=0) call_outcome = models.TextField(max_length=150, null=True) call_status = models.CharField(max_length=15, choices=((NEW_CALL, 'New'), (OPEN_CALL, 'Open'), (CLOSED, 'Closed')), default=NEW_CALL) auto_closed = models.BooleanField( default=False, editable=False, help_text='If True call status was changed to CLOSED by EDC.') objects = CallManager() def natural_key(self): return (self.subject_identifier, self.label, self.scheduled) def __str__(self): return '{} {} ({}) {}'.format(self.subject_identifier, self.first_name or '??', self.initials or '??', self.get_call_status_display(), ' by EDC' if self.auto_closed else '') class Meta: unique_together = ( 'subject_identifier', 'label', 'scheduled', ) abstract = True
class SubjectLocator(UniqueSubjectIdentifierFieldMixin, SiteModelMixin, SubjectContactFieldsMixin, SearchSlugModelMixin, BaseUuidModel): """A model completed by the user to that captures participant locator information and permission to contact. """ loc_admin = models.CharField(verbose_name='Administered by', max_length=50) first_name = FirstnameField(verbose_name='First Names', max_length=50) last_name = LastnameField(verbose_name='Surname', max_length=50) initials = EncryptedCharField(validators=[ RegexValidator(regex=r'^[A-Z]{2,3}$', message=('Ensure initials consist of letters ' 'only in upper case, no spaces.')) ], null=True, blank=False) loc_date = models.DateField( verbose_name='Date Completed', default=get_utcnow, validators=[date_not_before_study_start, date_not_future]) may_call = models.CharField( max_length=3, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given permission <b>to be contacted on this ' 'cell number</b>?'), blank=True, null=True) may_call_alt = models.CharField( max_length=3, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given permission <b>to be contacted on this ' 'cell number</b>?'), blank=True, null=True) subject_cell_alt_3 = EncryptedCharField( verbose_name='Cell number (second alternate)', blank=True, null=True) may_call_tel = models.CharField( max_length=3, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given permission <b>to be contacted on this ' 'telephone number</b>?'), blank=True, null=True) loc_email = models.EmailField(blank=True, null=True, help_text='If no email, write None') may_contact_email = models.CharField( max_length=3, choices=YES_NO_NA, verbose_name=mark_safe( 'Has the participant given permission <b>to be contacted by ' 'email</b>?'), blank=True, null=True) loc_village = EncryptedTextField(verbose_name='Home Village', max_length=500, help_text='') loc_address = EncryptedTextField( verbose_name='Physical address with detailed description', max_length=500, blank=True, null=True, help_text='') may_visit_home = models.CharField( max_length=25, choices=YES_NO, blank=True, null=True, verbose_name=mark_safe( 'Has the participant given permission for study ' 'staff <b>to make home visits</b> for follow-up purposes?')) idcc_clinic = models.CharField(verbose_name='Name of IDCC Clinic', max_length=25, blank=True, null=True) may_contact_idcc = models.CharField(verbose_name=( 'Has the participant given permission to be contacted, ' 'through their IDCC clinic?, if unable to contact phone numbers'), max_length=3, choices=YES_NO_NA, blank=True, null=True) loc_workplace = models.CharField(verbose_name='Name of workplace', max_length=25, blank=True, null=True, help_text='(for those who are working)') loc_workphone = EncryptedCharField(verbose_name='Work Telephone', blank=True, null=True) may_contact_work = models.CharField(verbose_name=( 'Has participant given permission to be contacted at their ' 'workplace?, if unable to contact phone numbers'), max_length=3, choices=YES_NO_NA, blank=True, null=True) loc_kincontact = EncryptedTextField(verbose_name=( 'Name and contact details of next of kin or any individuals ' 'participant allows us to contact if they can\'t be reached.' '(can list multiple people)'), max_length=500, blank=True, null=True) may_contact_kin = models.CharField(verbose_name=( 'Has participant given permission to contact anyone else?' ' , if unable to contact phone numbers'), max_length=3, choices=YES_NO_NA, blank=True, null=True) date_followup = models.DateField(verbose_name='Date of follow-up visit') initial_call_date = models.DateField(verbose_name='Initial call date', default=get_utcnow) review_locator = models.CharField( verbose_name=('Did you review this form with the participant to ' 'find out if there are any updates?'), max_length=3, choices=YES_NO) history = HistoricalRecords() objects = LocatorManager() def __str__(self): return '{}'.format(self.subject_identifier) def natural_key(self): return (self.subject_identifier, ) natural_key.dependencies = ['sites.Site'] def save(self, *args, **kwargs): if not self.initials: self.initials = f'{self.first_name[:1]}{self.last_name[:1]}' super().save(*args, **kwargs) class Meta: app_label = 'motheo_call_manager' verbose_name = 'Subject Locator'
class ChildDataset(NonUniqueSubjectIdentifierFieldMixin, SiteModelMixin, BaseUuidModel): study_child_identifier = models.CharField( max_length=150, unique=True, verbose_name='Study Child Subject Identifier') study_maternal_identifier = models.CharField( verbose_name="Study maternal Subject Identifier", max_length=50) first_name = FirstnameField(verbose_name='Firstname', null=True, blank=False) last_name = LastnameField(null=True, blank=False) dob = models.DateField(null=True, blank=True) age_today = models.DecimalField(verbose_name='Age today', decimal_places=2, max_digits=10, blank=True, null=True) age_calculation_date = models.DateField( verbose_name='Age calculation date', blank=True, null=True) infant_enrolldate = models.DateField(verbose_name='Infant enrollment date') infant_randdt = models.DateField( verbose_name='Date of infant randomization', blank=True, null=True) infant_sex = models.CharField(verbose_name='Infant gender', max_length=7) infant_azt_birth = models.CharField(verbose_name='Infant started AZT', max_length=15, blank=True, null=True) infant_azt_days = models.IntegerField( verbose_name='Duration of infant AZT (days)', blank=True, null=True) infant_azt_startdate = models.DateField(verbose_name='AZT start date', blank=True, null=True) infant_azt_stopdate = models.DateField(verbose_name='AZT stop date', blank=True, null=True) infant_sdnvp_birth = models.CharField(verbose_name='Infant received sdNVP', max_length=15, null=True) infant_hiv_exposed = models.CharField( verbose_name='Infant HIV exposure status', max_length=150) infant_hiv_status = models.CharField( verbose_name='Infant HIV infection status', max_length=150) infant_breastfed = models.CharField(verbose_name='Infant breastfed', max_length=150, blank=True, null=True) infant_breastfed_days = models.IntegerField( verbose_name='Breastfeeding duration (days)', blank=True, null=True) weaned = models.CharField(verbose_name='Weaning indicator', max_length=150, blank=True, null=True) weandt = models.DateField(verbose_name='Weaning date', blank=True, null=True) weancat = models.CharField(verbose_name='Weaning by category', max_length=150, blank=True, null=True) birthweight = models.DecimalField(verbose_name='Birth weight (kg)', decimal_places=2, max_digits=10, blank=True, null=True) birthwtcat = models.CharField(verbose_name='Birth weight (kg) by category', max_length=150, blank=True, null=True) height_0 = models.DecimalField(verbose_name='Height (cm) at delivery', decimal_places=2, max_digits=10, blank=True, null=True) headcirc_0 = models.DecimalField( verbose_name='Head circumference (cm) at delivery', decimal_places=2, max_digits=10, blank=True, null=True) apgarscore_1min = models.IntegerField(verbose_name='APGAR score at 1min', blank=True, null=True) apgarscore_5min = models.IntegerField(verbose_name='APGAR score at 5min', blank=True, null=True) apgarscore_10min = models.IntegerField(verbose_name='APGAR score at 10min', blank=True, null=True) low_birthweight = models.CharField( verbose_name='Infant born low birth weight', max_length=150, blank=True, null=True) infant_premature = models.CharField( verbose_name='Infant born premature (<37 weeks)', max_length=150, blank=True, null=True) height_6mo = models.DecimalField(verbose_name='Height (cm) at 6 months', decimal_places=2, max_digits=10, blank=True, null=True) height_18mo = models.DecimalField(verbose_name='Height (cm) at 18 months', decimal_places=2, max_digits=10, blank=True, null=True) height_24mo = models.DecimalField(verbose_name='Height (cm) at 24 months', decimal_places=2, max_digits=10, blank=True, null=True) headcirc_18mo = models.DecimalField( verbose_name='Head circumference (cm) at 18 months', decimal_places=2, max_digits=10, blank=True, null=True) headcirc_24mo = models.DecimalField( verbose_name='Head circumference (cm) at 24 months', decimal_places=2, max_digits=10, blank=True, null=True) weight_18mo = models.DecimalField(verbose_name='Weight (kg) at 18 months', decimal_places=2, max_digits=10, blank=True, null=True) weight_24mo = models.DecimalField(verbose_name='Weight (kg) at 18 months', decimal_places=2, max_digits=10, blank=True, null=True) infant_vitalstatus_final = models.CharField( verbose_name='Final infant vital status', max_length=150) deathdt = models.DateField(verbose_name='Death date', blank=True, null=True) deathcause = models.CharField(verbose_name='Cause of death', max_length=150, blank=True, null=True) firsthospdt = models.DateField( verbose_name='Date of first hospitalization', blank=True, null=True) hospnum = models.IntegerField(verbose_name='Number of times hospitalized', blank=True, null=True) idth = models.IntegerField( verbose_name='Infant death indicator (0= censored)', blank=True, null=True) idth_days = models.IntegerField( verbose_name='Days from infant birth to death (censored)', blank=True, null=True) ihiv = models.IntegerField( verbose_name='Indicator of HIV endpoint (0=censored)', blank=True, null=True) ihiv_days = models.IntegerField( verbose_name='Time to HIV endpoint (days from birth)', blank=True, null=True) ihosp = models.IntegerField( verbose_name='Infant hospitalization indicator (0= censored)', blank=True, null=True) ihosp_days = models.IntegerField( verbose_name= 'Days from infant birth to first hospitalization (censored)', blank=True, null=True) infantvacc_bcg = models.CharField(verbose_name='BCG vaccine received?', max_length=150, blank=True, null=True) infantvacc_dtap = models.CharField(verbose_name='DTAP vaccine received?', max_length=150, blank=True, null=True) infantvacc_hbv = models.CharField(verbose_name='HBV vaccine received?', max_length=150, blank=True, null=True) infantvacc_hiv = models.CharField(verbose_name='HIV vaccine received?', max_length=150, blank=True, null=True) infantvacc_measles = models.CharField( verbose_name='Measles vaccine received?', max_length=150, blank=True, null=True) infantvacc_mmr = models.CharField(verbose_name='MMR vaccine received?', max_length=150, blank=True, null=True) infantvacc_pneum = models.CharField( verbose_name='Pneumoccal vaccine received?', max_length=150, blank=True, null=True) infantvacc_polio = models.CharField(verbose_name='Polio vaccine received?', max_length=150, blank=True, null=True) infantvacc_rota = models.CharField( verbose_name='Rotavirus vaccine received?', max_length=150, blank=True, null=True) infant_offstudydate = models.DateField(blank=True, null=True) infant_lastcontactdt = models.DateField(blank=True, null=True) infant_onstudy_days = models.IntegerField(blank=True, null=True) infant_offstudy_reason = models.CharField( verbose_name='Days infant on-study', max_length=200) curr_age = models.DecimalField(verbose_name='Current Age', decimal_places=2, max_digits=10, blank=True, null=True) age_gt17_5 = models.IntegerField(verbose_name='Age greater than 17.5') infant_offstudy_complete = models.IntegerField( verbose_name='Infant Offstudy Complete') # today = models.DateField( # blank=True, null=True) offstrs = models.CharField(verbose_name='Offstrs', max_length=150, blank=True, null=True) offstcd = models.CharField(verbose_name='Offstcd', max_length=50, blank=True, null=True) twin_triplet = models.BooleanField(default=False, editable=False) class Meta: app_label = 'flourish_child' verbose_name = 'Infant Dataset'
class CaregiverChildConsent(SiteModelMixin, NonUniqueSubjectIdentifierFieldMixin, IdentityFieldsMixin, ReviewFieldsMixin, PersonalFieldsMixin, VerificationFieldsMixin, BaseUuidModel): """Inline table for caregiver's children""" subject_consent = models.ForeignKey( SubjectConsent, on_delete=models.PROTECT) subject_identifier = models.CharField( verbose_name="Subject Identifier", max_length=50) first_name = FirstnameField( null=True, blank=True) last_name = LastnameField( verbose_name="Last name", null=True, blank=True) study_child_identifier = models.CharField( verbose_name='Previous study identifier', max_length=50, null=True, blank=True) gender = models.CharField( verbose_name="Gender", choices=GENDER, max_length=1, null=True, blank=True) identity = IdentityField( verbose_name='Identity number', null=True, blank=True) identity_type = models.CharField( verbose_name='What type of identity number is this?', max_length=25, choices=CHILD_IDENTITY_TYPE, null=True, blank=True) confirm_identity = IdentityField( help_text='Retype the identity number', null=True, blank=True) child_dob = models.DateField( verbose_name="Date of birth", validators=[date_not_future, ], null=True, blank=True) child_test = models.CharField( verbose_name='Will you allow for HIV testing and counselling of ' 'your Child', max_length=5, choices=YES_NO, help_text='If no, participant is not eligible.') child_remain_in_study = models.CharField( verbose_name='Is your child willing to remain in the study area until ' '2025?', max_length=5, choices=YES_NO, help_text='If no, participant is not eligible.') child_preg_test = models.CharField( verbose_name='If your child is female and will be 12 years or older ' 'prior to 30-Jun-2025, will you allow the female child ' 'to undergo pregnancy testing?', max_length=5, choices=YES_NO_NA, help_text='If no, participant is not eligible.') child_knows_status = models.CharField( verbose_name='If your child is ≥ 16 years, have they been told about ' 'your HIV?', max_length=5, choices=YES_NO_NA, help_text='If no, participant is not eligible.') future_studies_contact = models.CharField( verbose_name=('Do you give us permission for us to contact you or your child' ' for future studies?'), max_length=3, choices=YES_NO,) specimen_consent = models.CharField( verbose_name=('Do you give us permission for us to use your child\'s blood ' 'samples for future studies?'), max_length=3, choices=YES_NO,) child_age_at_enrollment = models.DecimalField( decimal_places=2, max_digits=4) consent_datetime = models.DateTimeField( verbose_name='Consent date and time', validators=[ datetime_not_before_study_start, datetime_not_future]) version = models.CharField( verbose_name='Consent version', max_length=4, choices=CHILD_CONSENT_VERSION, blank=True) cohort = models.CharField( max_length=12, choices=COHORTS, blank=True, null=True) caregiver_visit_count = models.IntegerField( validators=[MinValueValidator(1), MaxValueValidator(3)], blank=True, null=True) is_eligible = models.BooleanField( default=False, editable=False) preg_enroll = models.BooleanField( default=False, editable=False) ineligibility = models.TextField( verbose_name="Reason not eligible", max_length=150, null=True, editable=False) def save(self, *args, **kwargs): self.preg_enroll = self.is_preg eligibility_criteria = CaregiverChildConsentEligibility( self.child_test, self.child_remain_in_study, self.child_preg_test, self.child_knows_status) self.is_eligible = eligibility_criteria.is_eligible self.ineligibility = eligibility_criteria.error_message self.child_age_at_enrollment = ( self.get_child_age_at_enrollment() if self.child_dob else 0) self.set_defaults() if self.is_eligible and (not self.subject_identifier or not self.version): # if self.consent_datetime >= self.version = '2.1' if self.preg_enroll: self.duplicate_subject_identifier_preg() if not self.subject_identifier: self.subject_identifier = InfantIdentifier( maternal_identifier=self.subject_consent.subject_identifier, birth_order=self.birth_order, live_infants=self.live_infants, registration_status=self.registration_status, registration_datetime=self.consent_datetime, subject_type=INFANT, supplied_infant_suffix=self.subject_identifier_sufix).identifier super().save(*args, **kwargs) def set_defaults(self): if (not self.preg_enroll and self.study_child_identifier): child_dataset = self.get_child_dataset(self.study_child_identifier) if child_dataset: self.child_dob = child_dataset.dob self.gender = child_dataset.infant_sex.upper()[0] def get_child_dataset(self, study_child_identifier): child_dataset_cls = django_apps.get_model( 'flourish_child.childdataset') try: child_dataset_obj = child_dataset_cls.objects.get( study_child_identifier=study_child_identifier) except child_dataset_cls.DoesNotExist: pass else: return child_dataset_obj def duplicate_subject_identifier_preg(self): try: child_consent = self._meta.model.objects.get( preg_enroll=True, subject_identifier__startswith=self.subject_consent.subject_identifier) except self._meta.model.DoesNotExist: pass else: self.subject_identifier = child_consent.subject_identifier @property def child_consent_version(self): consent_version_cls = django_apps.get_model( 'flourish_caregiver.flourishconsentversion') try: consent_version_obj = consent_version_cls.objects.get( screening_identifier=self.subject_consent.screening_identifier) except consent_version_cls.DoesNotExist: pass else: return consent_version_obj.child_version @property def is_preg(self): if not self.study_child_identifier: return (self.child_dob and self.child_dob > self.consent_datetime.date() or self.child_dob is None) return False @property def live_infants(self): child_dummy_consent_cls = django_apps.get_model( 'flourish_child.childdummysubjectconsent') return child_dummy_consent_cls.objects.filter( subject_identifier__icontains=self.subject_consent.subject_identifier).exclude( identity=self.identity).count() + 1 @property def subject_identifier_sufix(self): caregiver_child_consent_cls = django_apps.get_model(self._meta.label_lower) child_identifier_postfix = '' if self.child_dataset: if self.subject_consent.multiple_birth: if (self.subject_consent.multiple_births == 'twins' and self.child_dataset.twin_triplet): twin_id = self.subject_consent.subject_identifier + '-' + '25' try: caregiver_child_consent_cls.objects.get( subject_identifier=twin_id) except caregiver_child_consent_cls.DoesNotExist: child_identifier_postfix = '25' else: child_identifier_postfix = '35' elif (self.subject_consent.multiple_births == 'triplets' and self.child_dataset.twin_triplet): twin_id = self.subject_consent.subject_identifier + '-' + '36' try: caregiver_child_consent_cls.objects.get( subject_identifier=twin_id) except caregiver_child_consent_cls.DoesNotExist: child_identifier_postfix = '36' else: twin_id = self.subject_consent.subject_identifier + '-' + '46' try: caregiver_child_consent_cls.objects.get( subject_identifier=twin_id) except caregiver_child_consent_cls.DoesNotExist: child_identifier_postfix = '46' else: child_identifier_postfix = '56' else: children_count = caregiver_child_consent_cls.objects.filter( subject_identifier__startswith=self.subject_consent.subject_identifier).exclude( child_dob=self.child_dob, first_name=self.first_name).count() if children_count: child_identifier_postfix = str((children_count + 5) * 10) else: child_identifier_postfix = 10 else: children_count = caregiver_child_consent_cls.objects.filter( subject_identifier__startswith=self.subject_consent.subject_identifier).exclude( child_dob=self.child_dob, first_name=self.first_name).count() if children_count: child_identifier_postfix = str((children_count + 5) * 10) else: child_identifier_postfix = 10 return child_identifier_postfix @property def child_dataset(self): child_dataset_cls = django_apps.get_model('flourish_child.childdataset') try: child_dataset = child_dataset_cls.objects.get( study_child_identifier=self.study_child_identifier) except child_dataset_cls.DoesNotExist: pass else: return child_dataset return None @property def registration_status(self): return 'REGISTERED' @property def birth_order(self): caregiver_child_consent_cls = django_apps.get_model(self._meta.label_lower) return caregiver_child_consent_cls.objects.filter( subject_identifier__icontains=self.subject_consent.subject_identifier).exclude( identity=self.identity).count() + 1 def get_child_age_at_enrollment(self): return Cohort().age_at_enrollment( child_dob=self.child_dob, check_date=self.created.date()) class Meta: app_label = 'flourish_caregiver' verbose_name = 'Caregiver Consent On Behalf Of Child' verbose_name_plural = 'Caregiver Consent On Behalf Of Child'
class InfantBirth(UniqueSubjectIdentifierFieldMixin, SiteModelMixin, SearchSlugModelMixin, CryptoMixin, BaseUuidModel): """ A model completed by the user on the infant's birth. """ report_datetime = models.DateTimeField( verbose_name="Date and Time infant enrolled", validators=[ datetime_not_future, ], help_text='') first_name = FirstnameField( max_length=25, verbose_name="Infant's first name", help_text="If infant name is unknown or not yet determined, " "use Baby + birth order + mother's last name, e.g. 'Baby1Malane'") initials = EncryptedCharField(validators=[ RegexValidator(regex=r'^[A-Z]{2,3}$', message=('Ensure initials consist of letters ' 'only in upper case, no spaces.')) ], ) dob = models.DateField(verbose_name='Date of Birth', help_text="Must match labour and delivery report.", validators=[ date_not_future, ]) gender = models.CharField(max_length=10, choices=GENDER_UNDETERMINED) def __str__(self): return f'{self.first_name}, {self.initials}, {self.gender}' def save(self, *args, **kwargs): self.consent_version = self.get_consent_version() super(InfantBirth, self).save(*args, **kwargs) def get_consent_version(self): subject_consent_cls = django_apps.get_model( 'td_infant.infantdummysubjectconsent') subject_consent_objs = subject_consent_cls.objects.filter( subject_identifier=self.subject_identifier).order_by( '-consent_datetime') if subject_consent_objs: return subject_consent_objs.first().version else: raise ValidationError( 'Missing Infant Dummy Consent form. Cannot proceed.') @property def schedule_name(self): """Return a visit schedule name. """ schedule_name = None subject_consent = django_apps.get_model( 'td_maternal.subjectconsent').objects.filter( subject_identifier=self.registered_subject.relative_identifier ).order_by('version').last() if subject_consent.version == '1': schedule_name = 'infant_schedule_v1' elif subject_consent.version == '3': schedule_name = 'infant_schedule_v3' return schedule_name @property def registered_subject(self): """Return infant registered subject. """ registered_subject_cls = django_apps.get_model( 'edc_registration.registeredsubject') try: registered_subject = registered_subject_cls.objects.get( subject_identifier=self.subject_identifier) except registered_subject_cls.DoesNotExist: raise ValidationError( f'Registered Subject is missing for {self.subject_identifier}') else: return registered_subject class Meta: app_label = 'td_infant' verbose_name = "Infant Birth"
class CorrectConsent(CorrectConsentMixin, BaseUuidModel): """A model linked to the subject consent to record corrections.""" subject_consent = models.OneToOneField( SubjectConsent, on_delete=PROTECT) report_datetime = models.DateTimeField( verbose_name="Correction report date ad time", null=True, validators=[ datetime_not_future], ) old_first_name = FirstnameField( null=True, blank=True, ) new_first_name = FirstnameField( null=True, blank=True, ) old_last_name = LastnameField( null=True, blank=True, ) new_last_name = LastnameField( null=True, blank=True, ) old_initials = EncryptedCharField( blank=True, null=True, validators=[RegexValidator( regex=r'^[A-Z]{2,3}$', message='Ensure initials consist of letters only in upper case, no spaces.'), ], ) new_initials = EncryptedCharField( validators=[RegexValidator( regex=r'^[A-Z]{2,3}$', message='Ensure initials consist of letters only in upper case, no spaces.'), ], null=True, blank=True, ) old_dob = models.DateField( verbose_name="Old Date of birth", null=True, blank=True, help_text="Format is YYYY-MM-DD", ) new_dob = models.DateField( verbose_name="New Date of birth", null=True, blank=True, help_text="Format is YYYY-MM-DD", ) old_gender = models.CharField( choices=GENDER_UNDETERMINED, blank=True, null=True, max_length=1) new_gender = models.CharField( choices=GENDER_UNDETERMINED, max_length=1, null=True, blank=True, ) old_guardian_name = LastnameField( validators=[ RegexValidator('^[A-Z]{1,50}\, [A-Z]{1,50}$', 'Invalid format. Format is ' '\'LASTNAME, FIRSTNAME\'. All uppercase separated by a comma')], blank=True, null=True, ) new_guardian_name = LastnameField( validators=[ RegexValidator('^[A-Z]{1,50}\, [A-Z]{1,50}$', 'Invalid format. Format is \'LASTNAME, FIRSTNAME\'. ' 'All uppercase separated by a comma')], blank=True, null=True, ) old_may_store_samples = models.CharField( verbose_name="Old Sample storage", max_length=3, blank=True, null=True, choices=YES_NO, ) new_may_store_samples = models.CharField( verbose_name="New Sample storage", max_length=3, blank=True, null=True, choices=YES_NO, ) old_is_literate = models.CharField( verbose_name="(Old) Is the participant LITERATE?", max_length=3, blank=True, null=True, choices=YES_NO, ) new_is_literate = models.CharField( verbose_name="(New) Is the participant LITERATE?", max_length=3, blank=True, null=True, choices=YES_NO, ) old_witness_name = LastnameField( verbose_name="Witness\'s Last and first name (illiterates only)", validators=[ RegexValidator( '^[A-Z]{1,50}\, [A-Z]{1,50}$', 'Invalid format. Format ' 'is \'LASTNAME, FIRSTNAME\'. All uppercase separated by a comma')], blank=True, null=True, help_text=('Required only if subject is illiterate. ' 'Format is \'LASTNAME, FIRSTNAME\'. ' 'All uppercase separated by a comma'), ) new_witness_name = LastnameField( verbose_name="Witness\'s Last and first name (illiterates only)", validators=[ RegexValidator( '^[A-Z]{1,50}\, [A-Z]{1,50}$', 'Invalid format. Format is \'LASTNAME, FIRSTNAME\'. ' 'All uppercase separated by a comma')], blank=True, null=True, help_text=('Required only if subject is illiterate. ' 'Format is \'LASTNAME, FIRSTNAME\'. ' 'All uppercase separated by a comma'), ) objects = CorrectConsentManager() history = HistoricalRecords() def __str__(self): return str(self.subject_consent,) def natural_key(self): return self.subject_consent.natural_key() natural_key.dependencies = ['bcpp_subject.subject_consent'] def dashboard(self): ret = None dashboard_type = self.subject_consent.registered_subject.subject_type.lower() if self.appointment: url = reverse('bcpp_subject_dashboard:dashboard_url', kwargs={'dashboard_type': dashboard_type, 'dashboard_model': 'appointment', 'dashboard_id': self.appointment.pk, 'show': 'appointments'}) ret = """<a href="{url}" />dashboard</a>""".format(url=url) return ret dashboard.allow_tags = True class Meta: app_label = 'bcpp_subject'
class KaraboSubjectConsent(CryptoMixin, VerificationFieldsMixin, SiteModelMixin, BaseUuidModel): subject_identifier = models.CharField(verbose_name="Subject Identifier", max_length=50) screening_identifier = models.CharField( verbose_name="Screening Identifier", max_length=36, unique=True) report_datetime = models.DateTimeField( verbose_name="Report Date", validators=[datetime_not_before_study_start, datetime_not_future], default=get_utcnow, help_text=('If reporting today, use today\'s date/time, otherwise use ' 'the date/time this information was reported.')) first_name = FirstnameField(verbose_name='First Name', help_text=('(Must match name on file' ' with Tshilo Dikotla Study)')) last_name = LastnameField(verbose_name='Surname', help_text=('(Must match name on file ' 'with Tshilo Dikotla Study)')) initials = EncryptedCharField(verbose_name='Initials ', help_text=('(Must match initials on file ' 'with Tshilo Dikotla Study)')) dob = models.DateField(verbose_name="Date of birth", null=True, blank=False) is_dob_estimated = IsDateEstimatedField( verbose_name="Is date of birth estimated?", null=True, blank=False) language = models.CharField(verbose_name='Language of consent:', max_length=25, choices=settings.LANGUAGES) is_literate = models.CharField(verbose_name='Is the participant literate?', max_length=25, choices=YES_NO) guardian_name = LastnameField( verbose_name='Witness\'s last and first name', validators=[FullNameValidator()], blank=True, null=True, help_text=mark_safe('Required only if participant is illiterate.<br>' 'Format is \'LASTNAME, FIRSTNAME\'. ' 'All uppercase separated by a comma.')) consent_datetime = models.DateTimeField( verbose_name='Consent date and time', validators=[datetime_not_before_study_start, datetime_not_future]) identity = IdentityField(verbose_name='Omang of consenting woman', help_text=mark_safe( '(must match Omang on file ' 'with Tshilo Dikotla Study)' ' (Confirm that all Tshilo Dikotla ' 'women have enrolled with an Omang, ' 'otherwise will need additional ID ' 'questions.)')) consent_reviewed = models.CharField( verbose_name='I have reviewed the Karabo study ' 'consent with the client.', max_length=3, choices=YES_NO) study_questions = models.CharField( verbose_name='I have answered all questions the client' ' had about the Karabo study consent.', max_length=3, choices=YES_NO) assessment_score = models.CharField( verbose_name='I have asked the client questions' ' about the Karabo study and they have demonstrated an' ' understanding of the study by their answers.', max_length=3, choices=YES_NO) consent_signature = models.CharField( verbose_name='The client has signed the consent form.', max_length=3, choices=YES_NO) consent_copy = models.CharField( verbose_name='I have offered the client a signed copy' ' of the consent form.', max_length=25, choices=ANSWERS) def __str__(self): return f'{self.subject_identifier} {self.screening_identifier}' def natural_key(self): return (self.subject_identifier, self.screening_identifier) objects = KaraboSubjectConsentManager() history = HistoricalRecords() on_site = CurrentSiteManager() class Meta: app_label = 'td_maternal' verbose_name = "Karabo Subject Consent" verbose_name_plural = "Karabo Subject Consents"
class HouseholdMember(UpdatesOrCreatesRegistrationModelMixin, RepresentativeModelMixin, CloneModelMixin, NextMemberModelMixin, ConsentModelMixin, MemberStatusModelMixin, MemberEligibilityModelMixin, MemberIdentifierModelMixin, RequiresHouseholdLogEntryMixin, SurveyScheduleModelMixin, SearchSlugModelMixin, BaseUuidModel): """A model completed by the user to represent an enumerated household member. """ household_structure = models.ForeignKey( HouseholdStructure, on_delete=models.PROTECT) household_identifier = models.CharField( max_length=25, help_text='updated on save from household') internal_identifier = models.UUIDField( default=uuid4, editable=False, help_text='Identifier to track member between surveys, ' 'is the id of the member\'s first appearance in the table.') report_datetime = models.DateTimeField( verbose_name='Report date', default=get_utcnow, validators=[datetime_not_future]) first_name = FirstnameField( verbose_name='First name', validators=[RegexValidator( '^[A-Z]{1,250}$', ( 'Ensure first name is only CAPS and does not ' 'contain any spaces or numbers'))]) initials = models.CharField( verbose_name='Initials', max_length=3, validators=[ MinLengthValidator(2), MaxLengthValidator(3), RegexValidator( '^[A-Z]{1,3}$', ( 'Must be Only CAPS and 2 or 3 letters. ' 'No spaces or numbers allowed.'))]) gender = models.CharField( verbose_name='Gender', max_length=1, choices=GENDER) age_in_years = models.IntegerField( verbose_name='Age in years', validators=[MinValueValidator(0), MaxValueValidator(120)], help_text=( 'If age is unknown, enter 0. If member is ' 'less than one year old, enter 1')) survival_status = models.CharField( verbose_name='Survival status', max_length=10, default=ALIVE, choices=ALIVE_DEAD_UNKNOWN, null=True, blank=False) present_today = models.CharField( verbose_name='Is the member present today?', max_length=3, choices=YES_NO_NA, null=True, blank=False) has_moved = models.CharField( verbose_name='Has the member moved out of this household?', max_length=3, default=NOT_APPLICABLE, choices=YES_NO_NA, null=True, blank=False) inability_to_participate = models.CharField( verbose_name='Do any of the following reasons apply to the participant?', max_length=17, null=True, choices=INABILITY_TO_PARTICIPATE_REASON, help_text=('Participant can only participate if ABLE is selected. ' '(Any other reason make the participant unable to take ' 'part in the informed consent process)')) inability_to_participate_other = OtherCharField( null=True) study_resident = models.CharField( verbose_name='In the past 12 months, have you typically spent 3 or ' 'more nights per month in this community? ', max_length=17, choices=YES_NO_NA_DWTA, null=True, blank=False, help_text=('If participant has moved into the ' 'community in the past 12 months, then ' 'since moving in has the participant typically ' 'spent 3 or more nights per month in this community.')) visit_attempts = models.IntegerField( default=0, help_text='') eligible_htc = models.BooleanField( default=False, editable=False, help_text='') refused_htc = models.BooleanField( default=False, editable=False, help_text='updated by subject HTC save method only') htc = models.BooleanField( default=False, editable=False, help_text='updated by the subject HTC save method only') target = models.IntegerField( default=0, editable=False, ) additional_key = models.CharField( max_length=36, verbose_name='-', editable=False, default=None, null=True, help_text=( 'A uuid to be added to bypass the ' 'unique constraint for firstname, initials, household_structure. ' 'Should remain as the default value for normal enumeration. ' 'Is needed for Members added to the data from the clinic ' 'section where household_structure is always the same value.'), ) objects = Manager() history = HistoricalRecords() def __str__(self): return (f'{self.first_name} {self.initials} {self.age_in_years}{self.gender} ' f'{self.household_structure.survey_schedule}') def save(self, *args, **kwargs): self.household_identifier = ( self.household_structure.household.household_identifier) if not self.id and not self.internal_identifier: self.internal_identifier = uuid4() self.survey_schedule = self.household_structure.survey_schedule super().save(*args, **kwargs) def natural_key(self): return ((self.internal_identifier,) + self.household_structure.natural_key()) natural_key.dependencies = ['household.householdstructure'] def update_subject_identifier_on_save(self): """Overridden to not set the subject identifier on save. """ if not self.subject_identifier: self.subject_identifier = self.subject_identifier_as_pk.hex self.subject_identifier_aka = self.subject_identifier_as_pk.hex return self.subject_identifier @property def registration_unique_field(self): return 'internal_identifier' @property def registered_subject_unique_field(self): return 'registration_identifier' @property def registration_options(self): options = super().registration_options options.update(registration_identifier=self.internal_identifier.hex) return options @property def anonymous(self): """Returns True if this member resides on the anonymous plot. """ plot = get_anonymous_plot() if self.household_structure.household.plot == plot: return True return False def common_clean(self): if self.survival_status == DEAD and self.present_today == YES: raise MemberValidationError( 'Invalid combination. Got member status == {} but ' 'present today == {}'.format( self.survival_status, self.present_today)) super().common_clean() @property def common_clean_exceptions(self): return super().common_clean_exceptions + [MemberValidationError] class Meta: app_label = 'member' ordering = ['-created'] unique_together = ( ('internal_identifier', 'household_structure'), ('first_name', 'initials', 'household_structure')) index_together = [['internal_identifier', 'subject_identifier', 'created'], ]
class MaternalDataset(NonUniqueSubjectIdentifierFieldMixin, SiteModelMixin, SearchSlugModelMixin, BaseUuidModel): identifier_cls = ScreeningIdentifier screening_identifier = models.CharField( verbose_name="Eligibility Identifier", max_length=36, blank=True, null=True, unique=True) study_maternal_identifier = models.CharField( verbose_name="Study maternal Subject Identifier", max_length=50, unique=True) first_name = FirstnameField(verbose_name='Firstname', null=True, blank=False) last_name = LastnameField(verbose_name='Lastname', null=True, blank=False) protocol = models.CharField(max_length=150) delivdt = models.DateField(verbose_name='Delivery date', blank=True, null=True) site_name = models.CharField(max_length=150) mom_enrolldate = models.DateField(verbose_name='Maternal enrollment date', blank=True, null=True) delivmeth = models.CharField(verbose_name='Method of delivery', max_length=150, blank=True, null=True) delivery_location = models.CharField(verbose_name='Delivery location', max_length=150, blank=True, null=True) ega_delivery = models.IntegerField(verbose_name='EGA at delivery', blank=True, null=True) mom_age_enrollment = models.CharField( verbose_name='Mother\'s age at enrollment', max_length=150, blank=True, null=True) mom_hivstatus = models.CharField( verbose_name='Maternal HIV infection status', max_length=150) parity = models.IntegerField(blank=True, null=True) gravida = models.IntegerField(blank=True, null=True) mom_education = models.CharField(verbose_name='Maternal education level', max_length=150, blank=True, null=True) mom_maritalstatus = models.CharField( verbose_name='Maternal marital status', max_length=150, blank=True, null=True) mom_personal_earnings = models.CharField( verbose_name='Mother\'s personal earnings', max_length=150, blank=True, null=True) mom_moneysource = models.CharField( verbose_name='Maternal source of income', max_length=150, blank=True, null=True) mom_occupation = models.CharField(verbose_name='Mother\'s occupation', max_length=150, blank=True, null=True) mom_pregarv_strat = models.CharField( verbose_name='Maternal ARVs during pregnancy', max_length=150, blank=True, null=True) mom_arvstart_date = models.DateField( verbose_name='Date mother started HAART', blank=True, null=True) mom_baseline_cd4 = models.IntegerField( verbose_name='Maternal baseline CD4 count', blank=True, null=True) mom_baseline_cd4date = models.DateField( verbose_name='Draw data of mother\'s baseline CD4', blank=True, null=True) mom_baseline_vl = models.IntegerField( verbose_name='Maternal baseline viral load', blank=True, null=True) mom_baseline_vldate = models.DateField( verbose_name='Draw date of mother\'s baseline VL', blank=True, null=True) mom_baseline_hgb = models.DecimalField( verbose_name='Maternal baseline HGB', decimal_places=1, max_digits=10, blank=True, null=True) mom_baseline_hgbdt = models.DateField( verbose_name='Date of maternal baseline HGB', blank=True, null=True) mom_deathdate = models.DateField(verbose_name='Date mother died', blank=True, null=True) cooking_method = models.CharField(verbose_name='Primary cooking method', max_length=200, blank=True, null=True) home_eletrified = models.CharField(verbose_name='Electricity in home', max_length=150, blank=True, null=True) house_type = models.CharField(verbose_name='Type of dwelling', max_length=150, blank=True, null=True) toilet = models.CharField(verbose_name='Toilet facilities', max_length=150, blank=True, null=True) toilet_indoors = models.CharField(verbose_name='House has indoor toilet', max_length=150, blank=True, null=True) toilet_private = models.CharField( verbose_name='Private toilet for compound', max_length=150, blank=True, null=True) piped_water = models.CharField(verbose_name='Water piped into home', max_length=150, blank=True, null=True) home_refridgeration = models.CharField( verbose_name='Refrigeration in home', max_length=150, blank=True, null=True) drinking_water = models.CharField(verbose_name='Source of drinking water', max_length=150, blank=True, null=True) live_inhouse_number = models.IntegerField( verbose_name='Number of people living in household', blank=True, null=True) twin_triplet = models.IntegerField(verbose_name='Twins or thiplets', blank=True, null=True) preg_dtg = models.IntegerField(verbose_name='Preg DTG', blank=True, null=True) preg_pi = models.IntegerField(verbose_name='Preg PI', blank=True, null=True) preg_efv = models.IntegerField(verbose_name='Preg EFV', blank=True, null=True) on_worklist = models.BooleanField(default=False, blank=True, null=True) objects = MaternalDatasetManager() def __str__(self): return self.study_maternal_identifier def save(self, *args, **kwargs): if not self.screening_identifier: self.screening_identifier = self.identifier_cls().identifier super().save(*args, **kwargs) def get_search_slug_fields(self): fields = super().get_search_slug_fields() fields.append('screening_identifier') fields.append('study_maternal_identifier') fields.append('first_name') return fields class Meta: app_label = 'flourish_caregiver' verbose_name = 'Maternal Dataset'
class HomeVisitAttempt(BaseUuidModel): home_visit = models.ForeignKey( HomeVisit, on_delete=PROTECT) contact_attempted = models.CharField( verbose_name='Was a home visit attempt made?', choices=YES_NO, max_length=3) contact_staff = FirstnameField( verbose_name='Name(s) of staff member who visited the participant', blank=True, null=True) contact_date = models.DateField( verbose_name='Date of home visit attempt', validators=[date_not_future, ], blank=True, null=True) contact_loc = EncryptedTextField( verbose_name='Which address was used for contact attempt?', max_length=500, help_text='Provide a detailed description of the physical address.', blank=True, null=True) contact_outcome = models.TextField( verbose_name='What was the outcome of the in person visit.', max_length=500, null=True, blank=True) appt = models.CharField( verbose_name='Is the participant willing to schedule an appointment', max_length=7, choices=YES_NO, null=True, blank=True) appt_date = models.DateField( verbose_name='Appointment Date', validators=[date_is_future], null=True, blank=True, help_text='This can only come from the participant.') offstudy = models.CharField( verbose_name='Is the participant going offstudy?', choices=YES_NO, max_length=3, blank=True, null=True) comment = models.TextField( verbose_name='Additional Comments', blank=True, null=True) class Meta: app_label = 'motheo_call_manager'
class RegisteredSubject(UniqueSubjectIdentifierModelMixin, SiteModelMixin, edc_models.BaseUuidModel): """A model mixin for the RegisteredSubject model (only).""" # may not be available when instance created (e.g. infants prior to birth # report) first_name = FirstnameField(null=True) # may not be available when instance created (e.g. infants or household # subject before consent) last_name = LastnameField(verbose_name="Last name", null=True) # may not be available when instance created (e.g. infants) initials = EncryptedCharField( validators=[ RegexValidator( regex=r"^[A-Z]{2,3}$", message=("Ensure initials consist of letters " "only in upper case, no spaces."), ) ], null=True, ) dob = models.DateField( verbose_name=_("Date of birth"), null=True, blank=False, help_text=_("Format is YYYY-MM-DD"), ) is_dob_estimated = IsDateEstimatedField( verbose_name=_("Is date of birth estimated?"), null=True, blank=False) gender = models.CharField(verbose_name="Gender", max_length=1, choices=GENDER, null=True, blank=False) subject_consent_id = models.CharField(max_length=100, null=True, blank=True) registration_identifier = models.CharField(max_length=36, null=True, blank=True) sid = models.CharField(verbose_name="SID", max_length=15, null=True, blank=True) subject_type = models.CharField(max_length=25, null=True, blank=True) relative_identifier = models.CharField( verbose_name="Identifier of immediate relation", max_length=36, null=True, blank=True, help_text= "For example, mother's identifier, if available / appropriate", ) identity = IdentityField(null=True, blank=True) identity_type = IdentityTypeField(null=True, blank=True) screening_identifier = models.CharField(max_length=36, null=True, blank=True) screening_datetime = models.DateTimeField(null=True, blank=True) screening_age_in_years = models.IntegerField(null=True, blank=True) registration_datetime = models.DateTimeField(null=True, blank=True) # For simplicity, if going straight from screen to rando, # update both registration date and randomization date randomization_datetime = models.DateTimeField(null=True, blank=True) registration_status = models.CharField(verbose_name="Registration status", max_length=25, null=True, blank=True) consent_datetime = models.DateTimeField(null=True, blank=True) comment = models.TextField(verbose_name="Comment", max_length=250, null=True, blank=True) additional_key = models.CharField( max_length=36, verbose_name="-", editable=False, default=None, null=True, help_text=( "A uuid (or some other text value) to be added to bypass the " "unique constraint of just firstname, initials, and dob." "The default constraint proves limiting since the source " "model usually has some other attribute in additional to " "first_name, initials and dob which is not captured in " "this model"), ) dm_comment = models.CharField( verbose_name="Data Management comment", max_length=150, null=True, editable=False, ) randomization_list_model = models.CharField(max_length=150, null=True) on_site = CurrentSiteManager() history = edc_models.HistoricalRecords() objects = RegisteredSubjectManager() def save(self, *args, **kwargs): if self.identity: self.additional_key = None self.set_uuid_as_subject_identifier_if_none() self.raise_on_duplicate("subject_identifier") self.raise_on_duplicate("identity") self.raise_on_changed_subject_identifier() super().save(*args, **kwargs) def natural_key(self): return tuple(self.subject_identifier_as_pk) def __str__(self): return self.masked_subject_identifier natural_key.dependencies = ["sites.Site"] def update_subject_identifier_on_save(self): """Overridden to not set the subject identifier on save.""" if not self.subject_identifier: self.subject_identifier = self.subject_identifier_as_pk.hex elif re.match(UUID_PATTERN, self.subject_identifier): pass return self.subject_identifier def make_new_identifier(self): return self.subject_identifier_as_pk.hex @property def masked_subject_identifier(self): """Returns the subject identifier, if set, otherwise the string '<identifier not set>'. """ if not self.subject_identifier_is_set: return "<identifier not set>" return self.subject_identifier @property def subject_identifier_is_set(self): """Returns True if subject identifier has been set to a subject identifier; that is, no longer the default UUID. """ is_set = True try: obj = self.__class__.objects.get(pk=self.id) except ObjectDoesNotExist: is_set = False else: if re.match(UUID_PATTERN, obj.subject_identifier): return False return is_set def raise_on_changed_subject_identifier(self): """Raises an exception if there is an attempt to change the subject identifier for an existing instance if the subject identifier is already set. """ if self.id and self.subject_identifier_is_set: with transaction.atomic(): obj = self.__class__.objects.get(pk=self.id) if obj.subject_identifier != self.subject_identifier_as_pk.hex: if self.subject_identifier != obj.subject_identifier: raise RegisteredSubjectError( "Subject identifier cannot be changed for " "existing registered subject. " f"Got {self.subject_identifier} <> {obj.subject_identifier}." ) def raise_on_duplicate(self, attrname): """Checks if the subject identifier (or other attr) is in use, for new and existing instances. """ if getattr(self, attrname): with transaction.atomic(): error_msg = ( f"Cannot {{action}} registered subject with a duplicate " f"'{attrname}'. Got {getattr(self, attrname)}.") try: obj = self.__class__.objects.exclude(**{ "pk": self.pk } if self.id else {}).get( **{attrname: getattr(self, attrname)}) if not self.id: raise RegisteredSubjectError( error_msg.format(action="insert")) elif self.subject_identifier_is_set and obj.id != self.id: raise RegisteredSubjectError( error_msg.format(action="update")) else: raise RegisteredSubjectError( error_msg.format(action="update")) except ObjectDoesNotExist: pass def set_uuid_as_subject_identifier_if_none(self): """Inserts a random uuid as a dummy identifier for a new instance. Model uses subject_identifier_as_pk as a natural key for serialization/deserialization. Value must not change once set. """ if not self.subject_identifier: self.subject_identifier = self.subject_identifier_as_pk.hex class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Registered Subject" ordering = ["subject_identifier"] unique_together = ("first_name", "dob", "initials", "additional_key") indexes = [ models.Index( fields=["first_name", "dob", "initials", "additional_key"]), models.Index(fields=[ "identity", "subject_identifier", "screening_identifier" ]), ] permissions = ( ("display_firstname", "Can display first name"), ("display_lastname", "Can display last name"), ("display_dob", "Can display DOB"), ("display_identity", "Can display identity number"), ("display_initials", "Can display initials"), )
class MoccaRegister(SiteModelMixin, BaseUuidModel): report_datetime = models.DateTimeField(default=get_utcnow) screening_identifier = models.CharField( verbose_name="MOCCA (ext) screening identifier", max_length=15, null=True, ) mocca_screening_identifier = models.CharField( verbose_name="MOCCA (original) screening identifier", max_length=15, null=True, blank=True, help_text="If known", ) mocca_study_identifier = models.CharField( verbose_name="MOCCA (original) study identifier", max_length=25, validators=[ RegexValidator( r"0[0-9]{1}\-0[0-9]{3}|[0-9]{6}", "Invalid format. Expected 12-3456 for UG, 123456 for TZ", ) ], help_text="Format must match original identifier. e.g. 12-3456 for UG, 123456 for TZ", ) mocca_country = models.CharField( max_length=25, choices=(("uganda", "Uganda"), ("tanzania", "Tanzania")) ) mocca_site = models.ForeignKey( MoccaOriginalSites, on_delete=models.PROTECT, limit_choices_to=get_mocca_site_limited_to, ) first_name = FirstnameField(null=True) last_name = LastnameField(null=True) initials = EncryptedCharField( validators=[ RegexValidator("[A-Z]{1,3}", "Invalid format"), MinLengthValidator(2), MaxLengthValidator(3), ], help_text="Use UPPERCASE letters only. May be 2 or 3 letters.", null=True, blank=False, ) gender = models.CharField(max_length=10, choices=GENDER, null=True, blank=False) age_in_years = models.IntegerField( validators=[MinValueValidator(18), MaxValueValidator(110)], null=True, blank=False, ) birth_year = models.IntegerField( validators=[MinValueValidator(1900), MaxValueValidator(2002)], null=True, blank=False, ) dob = models.DateField(null=True, blank=True) survival_status = models.CharField( max_length=25, choices=ALIVE_DEAD_UNKNOWN, default=UNKNOWN ) contact_attempts = models.IntegerField(default=0, help_text="auto-updated") call = models.CharField(verbose_name="Call?", max_length=15, choices=YES_NO, default=YES) subject_present = models.CharField( verbose_name="Patient is present. Screen now instead of calling?", max_length=15, choices=YES_NO, default=NO, help_text="Only select 'yes' if the patient is present in the clinic now.", ) date_last_called = models.DateField(null=True, help_text="auto-updated") next_appt_date = models.DateField( verbose_name="Appt", null=True, blank=True, help_text="auto-updated" ) notes = EncryptedTextField(verbose_name="General notes", null=True, blank=True) tel_one = EncryptedCharField("Tel/Mobile(1)", max_length=15, null=True) tel_two = EncryptedCharField("Tel/Mobile(2)", max_length=15, null=True) tel_three = EncryptedCharField("Tel/Mobile(3)", max_length=15, null=True) best_tel = models.CharField( verbose_name="Prefered Telephone / Mobile", max_length=15, choices=TEL_CHOICES, null=True, blank=True, help_text="If any, select the best telephone/mobile from above", ) on_site = CurrentSiteManager() objects = Manager() history = HistoricalRecords() def __str__(self): return ( f"{self.mocca_study_identifier} {self.initials} {self.age_in_years} {self.gender}" ) def save(self, *args, **kwargs): if self.screening_identifier: self.call = NO super().save(*args, **kwargs) def natural_key(self): return (self.mocca_study_identifier,) natural_key.dependencies = [ "sites.Site", "mocca_lists.MoccaOriginalSites", ] class Meta(BaseUuidModel.Meta): verbose_name = "MOCCA Patient Register" verbose_name_plural = "MOCCA Patient Register" ordering = ["mocca_country", "mocca_site"] indexes = [ Index(fields=["mocca_country", "mocca_site"]), Index(fields=["mocca_study_identifier", "initials", "gender"]), ] constraints = [ UniqueConstraint( fields=["mocca_screening_identifier"], name="unique_mocca_screening_identifier", ), UniqueConstraint( fields=["mocca_study_identifier"], name="unique_mocca_study_identifier" ), ]