class MaternalLocator(LocatorMixin, ExportTrackingFieldsMixin, BaseUuidModel): """ A model completed by the user to capture locator information and the details of the infant caretaker. """ registered_subject = models.OneToOneField(RegisteredSubject, null=True) # appointment = models.ForeignKey(Appointment, null=True) report_datetime = models.DateTimeField( verbose_name="Report Date", validators=[ datetime_not_before_study_start, datetime_not_future, ], default=timezone.now, help_text=('If reporting today, use today\'s date/time, otherwise use ' 'the date/time this information was reported.')) care_clinic = OtherCharField( verbose_name= "Health clinic where your infant will receive their routine care ", max_length=35, ) has_caretaker = models.CharField(verbose_name=( "Has the participant identified someone who will be " "responsible for the care of the baby in case of her death, to whom the " "study team could share information about her baby's health?"), max_length=25, choices=YES_NO, help_text="") caretaker_name = EncryptedCharField( verbose_name="Full Name of the responsible person", max_length=35, help_text="include firstname and surname", blank=True, null=True) caretaker_cell = EncryptedCharField(verbose_name="Cell number", max_length=8, validators=[ CellNumber, ], blank=True, null=True) caretaker_tel = EncryptedCharField(verbose_name="Telephone number", max_length=8, validators=[ TelephoneNumber, ], blank=True, null=True) entry_meta_data_manager = LocalCrfMetaDataManager(MaternalVisit) class Meta: app_label = 'td_maternal' verbose_name = 'Maternal Locator' verbose_name_plural = 'Maternal Locator'
class SubjectWorkFieldsMixin(models.Model): may_call_work = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( "Has the participant given permission to contacted <b>at work</b> by telephone " "or cell by study staff for follow-up purposes during the study?"), ) subject_work_place = EncryptedTextField( verbose_name="Name and location of work place", max_length=250, blank=True, null=True, ) subject_work_phone = EncryptedCharField( verbose_name="Work contact telephone", validators=[telephone_number], blank=True, null=True, ) subject_work_cell = EncryptedCharField( verbose_name="Work contact cell number", validators=[cell_number], blank=True, null=True, ) class Meta: abstract = True
class SubjectContactFieldsMixin(models.Model): may_call = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given permission <b>to contacted by telephone ' 'or cell</b> by study staff for follow-up purposes during the study?' )) may_visit_home = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given permission for study ' 'staff <b>to make home visits</b> for follow-up purposes?')) may_sms = models.CharField( max_length=25, choices=YES_NO, null=True, blank=False, verbose_name=mark_safe( 'Has the participant given permission <b>to be contacted by SMS</b> ' 'by study staff for follow-up purposes during the study?')) mail_address = EncryptedTextField(verbose_name='Mailing address ', max_length=500, null=True, blank=True) physical_address = EncryptedTextField( verbose_name='Physical address with detailed description', max_length=500, blank=True, null=True, help_text='') subject_cell = EncryptedCharField(verbose_name='Cell number', blank=True, null=True, help_text='') subject_cell_alt = EncryptedCharField( verbose_name='Cell number (alternate)', blank=True, null=True) subject_phone = EncryptedCharField(verbose_name='Telephone', blank=True, null=True) subject_phone_alt = EncryptedCharField( verbose_name='Telephone (alternate)', blank=True, null=True) class Meta: abstract = True
class SubjectIndirectContactFieldsMixin(models.Model): may_contact_indirectly = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given permission for study staff ' '<b>to contact anyone else</b> for follow-up purposes during the study?' ), help_text='For example a partner, spouse, family member, neighbour ...' ) indirect_contact_name = EncryptedCharField( verbose_name='Full names of the contact person', blank=True, null=True) indirect_contact_relation = EncryptedCharField( verbose_name='Relationship to participant', blank=True, null=True) indirect_contact_physical_address = EncryptedTextField( verbose_name='Full physical address ', max_length=500, blank=True, null=True) indirect_contact_cell = EncryptedCharField(verbose_name='Cell number', validators=[ CellNumber, ], blank=True, null=True) indirect_contact_cell_alt = EncryptedCharField( verbose_name='Cell number (alternative)', validators=[ CellNumber, ], blank=True, null=True) indirect_contact_phone = EncryptedCharField( verbose_name='Telephone number', validators=[ TelephoneNumber, ], blank=True, null=True) class Meta: abstract = True
class HouseholdComposition(CrfModelMixin): physical_add = EncryptedCharField( verbose_name="Description of physical address: ", max_length=150, blank=True, null=True, ) coordinates = models.DecimalField( verbose_name="GPS coordinates", max_digits=10, decimal_places=4, help_text=" Record coordinates of the main gate to the household", ) contact = models.CharField( verbose_name="[To the respondent] Can we contact you by telephone?", max_length=3, choices=YES_NO, ) phone_number = models.IntegerField( verbose_name= "[To the respondent] What phone numbers can we use to reach you?", help_text="", ) history = HistoricalRecords() class Meta(CrfModelMixin.Meta): app_label = 'bcpp_subject' verbose_name = "Household Composition" verbose_name_plural = "Household Composition"
class CrfEncrypted(CrfModelMixin, ExportTrackingFieldsModelMixin, BaseUuidModel): subject_visit = models.ForeignKey(SubjectVisit, on_delete=PROTECT) encrypted1 = EncryptedCharField(null=True) export_history = ExportHistoryManager()
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 MemberEntryMixin(SurveyScheduleModelMixin, RequiresHouseholdLogEntryMixin, models.Model): """For absentee and undecided log models. """ household_member = models.ForeignKey(HouseholdMember, on_delete=models.PROTECT) report_date = models.DateField(editable=False) report_datetime = models.DateTimeField(verbose_name='Report date', validators=[datetime_not_future], default=get_utcnow) reason_other = OtherCharField() next_appt_datetime = models.DateTimeField( verbose_name='Follow-up appointment', help_text='The date and time to meet with the subject') next_appt_datetime_source = models.CharField( verbose_name='Appointment date suggested by?', max_length=25, choices=NEXT_APPOINTMENT_SOURCE, help_text='') contact_details = EncryptedCharField( null=True, blank=True, help_text='Information that can be used to contact someone, ' 'preferrably the subject, to confirm the appointment') comment = EncryptedTextField( verbose_name='Comments', max_length=250, blank=True, null=True, help_text=('IMPORTANT: Do not include any names or other personally ' 'identifying information in this comment')) def save(self, *args, **kwargs): self.survey_schedule = ( self.household_member.survey_schedule_object.field_value) if not self.id and self.report_date: raise ValueError( 'Expected report_date to be None. Got {}. Set report datetime, ' 'not report_date.'.format(self.report_date)) self.report_date = arrow.Arrow.fromdatetime( self.report_datetime, tzinfo=self.report_datetime.tzinfo).to('UTC').date() super().save(*args, **kwargs) class Meta: abstract = True unique_together = ('household_member', 'report_date')
class VaccinationDetails(NonUniqueSubjectIdentifierFieldMixin, SiteModelMixin, BaseUuidModel): report_datetime = models.DateTimeField( verbose_name='Report Date and Time', default=get_utcnow, help_text='Date and time of report.') date_of_vaccination = models.DateField( verbose_name="When did the participant receive their vaccination dose?", validators=[date_not_future, ]) vaccination_place = models.CharField( verbose_name="Where was the vaccination administered?", max_length=35, blank=True, null=True, ) vaccine_name = models.CharField( verbose_name="Name of the vaccination", max_length=35, blank=True, null=True, ) dosage_administered = models.IntegerField( verbose_name="Dosage administered", blank=True, null=True, ) batch_number = models.CharField( verbose_name="Vaccination batch number", max_length=35, blank=True, null=True, ) expiry_date = models.DateField( verbose_name="Vaccination expiry date", validators=[date_is_future, ]) provider_name = EncryptedCharField( verbose_name="Name of the provider", max_length=35, blank=True, null=True, ) next_vaccination = models.DateField( verbose_name="When is the participant scheduled for " "their next vaccination dose?", validators=[date_is_future, ]) class Meta: verbose_name = "Vaccine Details" verbose_name_plural = "Vaccine Details"
class SenaiteUser(BaseUuidModel, models.Model): username = models.CharField(verbose_name="Senaitte Username", null=True, max_length=200) contact = models.CharField(verbose_name="Contact", null=True, max_length=300) password = EncryptedCharField(verbose_name='Senaite LIMS Password', blank=False, null=True, help_text='Senaite LIMS password')
class MapitioAdditionalIdentifiersModelMixin(models.Model): hospital_identifier = EncryptedCharField( verbose_name="HMS Identifier", help_text="Hindu Mandal Hospital Identifier", unique=True, ) ctc_identifier = EncryptedCharField( verbose_name="CTC Identifier", null=True, blank=True, unique=True, ) file_number = EncryptedCharField( verbose_name="Patient File number", help_text=mark_safe("Patient file number from Hindu Mandal Hospital"), unique=True, ) class Meta: abstract = True
class AccountHolder(BaseUuidModel): first_name = EncryptedCharField(verbose_name=_("First name")) last_name = EncryptedCharField(verbose_name=_("Last name")) initials = models.CharField(max_length=3) comment = models.TextField( max_length=100, blank=True, ) def __unicode__(self): return '%s, %s' % (self.last_name, self.first_name) def get_absolute_url(self): return "/lab_account/accountholder/%s/" % self.id class Meta: ordering = ['last_name', 'first_name'] unique_together = ['last_name', 'first_name'] app_label = 'lab_account' db_table = 'bhp_lab_registration_accountholder'
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 HouseholdRefusalMixin(models.Model): household_structure = models.OneToOneField(HouseholdStructure, on_delete=models.PROTECT) report_datetime = models.DateTimeField(verbose_name="Report date", default=get_utcnow, validators=[datetime_not_future]) reason = models.CharField( verbose_name= 'Please indicate the reason the household cannot be enumerated', max_length=25, choices=HOUSEHOLD_REFUSAL) reason_other = EncryptedCharField(verbose_name='If Other, specify', max_length=100, blank=True, null=True) comment = EncryptedTextField( max_length=250, help_text="You may provide a comment here or leave BLANK.", blank=True, null=True) def common_clean(self): if self.household_structure.enrolled: raise HouseholdAlreadyEnrolledError( 'Household is already enrolled. Blocking attempt to ' 'add \'{}\'.'.format(self._meta.verbose_name)) super().common_clean() def save(self, *args, **kwargs): if self.household_structure.enrolled: raise ValidationError('Household is enrolled.') super(HouseholdRefusalMixin, self).save(*args, **kwargs) def __str__(self): return '{} ({})'.format(self.household_structure, self.report_datetime.strftime('%Y-%m-%d')) class Meta: abstract = True
class Respondent(CrfModelMixin): household_composition = models.ForeignKey(HouseholdComposition, on_delete=PROTECT) first_name = EncryptedCharField( verbose_name="First name or initials ", max_length=25, ) relation = models.CharField( verbose_name="Relation", choices=RELATION, max_length=25, ) relation_other = OtherCharField() gender = models.CharField( verbose_name="Gender", max_length=6, choices=GENDER, ) age = models.IntegerField(verbose_name="Age", ) present = models.CharField( verbose_name="Present Today", max_length=3, choices=YES_NO, ) nights_outside = models.IntegerField( verbose_name="Nights spent outside of this Community", ) history = HistoricalRecords() class Meta(CrfModelMixin.Meta): app_label = 'bcpp_subject' verbose_name = "Respondent Details" verbose_name_plural = "Respondent Details"
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 RandomizationListModelMixin(models.Model): """ A model mixin for the randomization list. The default expects and ACTIVE vs PLACEBO randomization. If yours differs, you need to re-declare field "assignment" and model method "treatment_description". The default `Randomizer` class MAY also need to be customized. """ assignment = EncryptedCharField() randomizer_name = models.CharField(max_length=50, default="default") subject_identifier = models.CharField(verbose_name="Subject Identifier", max_length=50, null=True, unique=True) sid = models.IntegerField(unique=True) site_name = models.CharField(max_length=100) allocation = EncryptedCharField(verbose_name="Original integer allocation", null=True) allocated = models.BooleanField(default=False) allocated_datetime = models.DateTimeField(null=True) allocated_user = models.CharField(max_length=50, null=True) allocated_site = models.ForeignKey(Site, null=True, on_delete=models.PROTECT, related_name="+") verified = models.BooleanField(default=False) verified_datetime = models.DateTimeField(null=True) verified_user = models.CharField(max_length=50, null=True) objects = RandomizationListManager() history = HistoricalRecords(inherit=True) on_site = CurrentSiteManager("allocated_site") def __str__(self): return f"{self.site_name}.{self.sid} subject={self.subject_identifier}" def save(self, *args, **kwargs): self.randomizer_name = self.randomizer_cls.name try: getattr(self, "assignment_description") except RandomizationError as e: raise RandomizationListModelError(e) try: Site.objects.get(name=self.site_name) except ObjectDoesNotExist: site_names = [obj.name for obj in Site.objects.all()] raise RandomizationListModelError( f"Invalid site name. Got {self.site_name}. " f"Expected one of {site_names}.") super().save(*args, **kwargs) @property def short_label(self): return f"{self.assignment} SID:{self.site_name}.{self.sid}" @property def randomizer_cls(self): return site_randomizers.get(self.randomizer_name) # customize if approriate @property def assignment_description(self): """May be overridden.""" if self.assignment not in self.randomizer_cls.assignment_map: raise RandomizationError( f"Invalid assignment. Expected one of " f"{list(self.randomizer_cls.assignment_map.keys())}. " f"Got `{self.assignment}`. See ") return self.assignment def natural_key(self): return (self.sid, ) class Meta: abstract = True ordering = ("site_name", "sid") unique_together = ("site_name", "sid") permissions = (("display_assignment", "Can display assignment"), )
class SubjectContactFieldsMixin(models.Model): may_call = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( "Has the participant given permission <b>to contacted by telephone " "or cell</b> by study staff for follow-up purposes during the study?" ), ) may_visit_home = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( "Has the participant given permission for study " "staff <b>to make home visits</b> for follow-up purposes?" ), ) may_sms = models.CharField( max_length=25, choices=YES_NO, null=True, blank=False, verbose_name=mark_safe( "Has the participant given permission <b>to be contacted by SMS</b> " "by study staff for follow-up purposes during the study?" ), ) mail_address = EncryptedTextField( verbose_name="Mailing address ", max_length=500, null=True, blank=True ) physical_address = EncryptedTextField( verbose_name="Physical address with detailed description", max_length=500, blank=True, null=True, help_text="", ) subject_cell = EncryptedCharField( verbose_name="Cell number", validators=[cell_number], blank=True, null=True, help_text="", ) subject_cell_alt = EncryptedCharField( verbose_name="Cell number (alternate)", validators=[cell_number], blank=True, null=True, ) subject_phone = EncryptedCharField( verbose_name="Telephone", validators=[telephone_number], blank=True, null=True ) subject_phone_alt = EncryptedCharField( verbose_name="Telephone (alternate)", validators=[telephone_number], blank=True, null=True, ) class Meta: abstract = True
class MaternalLocator(LocatorModelMixin, ActionModelMixin, RequiresConsentFieldsModelMixin, SiteModelMixin, NonUniqueSubjectIdentifierModelMixin, BaseUuidModel): action_name = MATERNAL_LOCATOR_ACTION tracking_identifier_prefix = 'SL' on_site = CurrentSiteManager() locator_date = models.DateField( verbose_name='Date Locator Form signed', validators=[date_not_future]) health_care_infant = models.CharField( verbose_name=('Health clinic where your infant will' ' receive their routine care'), max_length=35, blank=True, null=True) may_call = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given his/her permission for study ' 'staff to call her for follow-up purposes during the study?')) may_visit_home = models.CharField( max_length=25, choices=YES_NO, verbose_name=mark_safe( 'Has the participant given his/her permission for study staff <b>to ' 'make home visits</b> for follow-up purposes during the study??')) has_caretaker = models.CharField( verbose_name=( "Has the participant identified someone who will be " "responsible for the care of the baby in case of her death, to " "whom the study team could share information about her baby's " "health?"), max_length=25, choices=YES_NO, help_text="") caretaker_name = EncryptedCharField( verbose_name="Full Name of the responsible person", max_length=35, help_text="include firstname and surname", blank=True, null=True) may_call_work = models.CharField( max_length=25, choices=YES_NO_DOESNT_WORK, verbose_name=mark_safe( 'Has the participant given his/her permission for study staff ' 'to contact her at work for follow up purposes during the study?')) subject_work_phone = EncryptedCharField( verbose_name='Work contact number', blank=True, null=True) caretaker_cell = EncryptedCharField( verbose_name="Cell number", max_length=8, validators=[CellNumber, ], blank=True, null=True) caretaker_tel = EncryptedCharField( verbose_name="Telephone number", max_length=8, validators=[TelephoneNumber, ], blank=True, null=True) history = HistoricalRecords() class Meta: verbose_name = 'Maternal Locator'
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 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 SubjectScreening( ScreeningModelMixin, BaseUuidModel, ): identifier_cls = ScreeningIdentifier screening_consent = models.CharField( verbose_name=( "Has the subject given his/her verbal consent " "to be screened for the INTE Africa trial?" ), max_length=15, choices=YES_NO, ) selection_method = models.CharField( verbose_name="How was the patient selected for screening?", max_length=25, choices=SELECTION_METHOD, ) clinic_type = models.CharField( verbose_name="From which type of clinic was the patient selected?", max_length=25, choices=CLINIC_CHOICES, ) 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.", blank=False, ) qualifying_condition = models.CharField( verbose_name=( "Does the patient have at least one of the following " "conditions: HIV, Diabetes and/or Hypertension" ), max_length=15, choices=YES_NO, ) requires_acute_care = models.CharField( verbose_name=("Does the patient require acute care including in-patient admission"), max_length=25, choices=YES_NO, ) lives_nearby = models.CharField( verbose_name=( "Is the patient planning to remain in the catchment area " "for at least 6 months" ), max_length=15, choices=YES_NO, ) def save(self, *args, **kwargs): check_eligible_final(self) super().save(*args, **kwargs) class Meta: verbose_name = "Subject Screening" verbose_name_plural = "Subject Screening"
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 SubjectScreening( NonUniqueSubjectIdentifierModelMixin, MapitioAdditionalIdentifiersModelMixin, SearchSlugModelMixin, ScreeningMethodsModeMixin, ScreeningFieldsModeMixin, PersonalFieldsMixin, SiteModelMixin, edc_models.BaseUuidModel, ): screening_identifier = models.CharField( verbose_name="Enrollment ID", max_length=50, blank=True, unique=True, editable=False, ) 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.", blank=False, ) report_datetime = models.DateTimeField( verbose_name="Report Date and Time", default=get_utcnow, help_text="Date and time of this report.", ) gender = models.CharField( verbose_name="Gender", choices=GENDER, max_length=1, null=True, blank=False, ) age_in_years = models.IntegerField( validators=[MinValueValidator(0), MaxValueValidator(110)], ) dob = models.DateField(verbose_name="Date of birth", null=True, blank=False) is_dob_estimated = edc_models.IsDateEstimatedField( verbose_name="Is date of birth estimated?", null=True, blank=False ) confirm_hospital_identifier = EncryptedCharField( verbose_name="Confirm HMS Identifier", null=True, help_text="Retype the Hindu Mandal Hospital Identifier", ) confirm_ctc_identifier = EncryptedCharField( verbose_name="Confirm CTC Identifier", null=True, blank=True, ) clinic_registration_date = models.DateField( verbose_name="Date patient was <u>first</u> enrolled to this clinic", validators=[date_is_past, date_is_not_now], ) last_clinic_date = models.DateField( verbose_name="Date patient was <u>last</u> seen at this clinic", validators=[date_is_past, date_is_not_now], help_text="Date last seen according to information on the patient chart.", ) # not used, keep for compatability screening_consent = models.CharField( verbose_name=( "Has the subject given his/her verbal consent " "to be screened for the Mapitio trial?" ), max_length=15, choices=YES_NO_NA, default=NOT_APPLICABLE, ) # not used, keep for compatability selection_method = models.CharField( verbose_name="How was the patient selected for screening?", max_length=25, choices=SELECTION_METHOD, default=INTEGRATED_CLINIC, ) # not used, keep for compatability clinic_type = models.CharField( verbose_name="From which type of clinic was the patient selected", max_length=25, choices=CLINIC_CHOICES, default=INTEGRATED_CLINIC, ) on_site = CurrentSiteManager() objects = SubjectScreeningModelManager() def save(self, *args, **kwargs): """Screening Identifier is always allocated. """ self.screening_identifier = self.hospital_identifier check_eligible_final(self) super().save(*args, **kwargs) class Meta: verbose_name = "Enrollment" verbose_name_plural = "Enrollment" unique_together = ( ("first_name", "dob", "initials", "last_name"), ("hospital_identifier", "ctc_identifier"), )
class MaternalRando(CrfModelMixin): """ Stores a prepared infant randomization list. If you need to undo a randomization, here is an example of how:: >>> # To undo a randomization >>> subject_identifier = '056-1980294-0' >>> void_sid = '222222' >>> # clear rando record but set to void to not allow it to be used >>> maternal_rando = MaternalRando.objects.get( subject_identifier=subject_identifier, sid=void_sid) >>> if maternal_rando: >>> maternal_rando.subject_identifier='void' >>> maternal_rando.randomization_datetime = None >>> maternal_rando.initials = 'XX' >>> maternal_rando.feeding_choice=None >>> maternal_rando.infant_initials='XX' >>> maternal_rando.haart_status=None >>> maternal_rando.comment = "used in error by%s"%(subject_identifier,) >>> maternal_rando.save() >>> print "OK, SID %s is now void" % (void_sid,) >>> # clear SID from registered subject >>> rs = RegisteredSubject.object.get( subject_identifier=subject_identifier, sid=void_sid) >>> rs.sid = None >>> rs.registration_status = None >>> print "OK, RegisteredSubject SID set to None" >>> else: >>> print "Error" """ # TODO: Site brought in by the Site Model Mixin. sid = models.IntegerField( verbose_name='SID', unique=True) rx = EncryptedCharField( verbose_name="Treatment Assignment") randomization_datetime = models.DateTimeField( verbose_name='Randomization Datetime') initials = EncryptedCharField( validators=[RegexValidator( regex=r'^[A-Z]{2,3}$', message=('Ensure initials consist of letters ' 'only in upper case, no spaces.'))]) dispensed = models.CharField( verbose_name='Dispensed', max_length=10, default=NO, choices=YES_NO, help_text='To be confirmed by pharmacy staff only') comment = models.TextField( max_length=250, null=True, blank=True, help_text="Comment if any manual changes made to rando list") delivery_clinic = models.CharField( max_length=100, verbose_name="Which clinic does the mother plan to deliver at?", choices=DELIVERY_HEALTH_FACILITY) delivery_clinic_other = models.CharField( max_length=100, verbose_name="if other delivery clinic, specify...", blank=True, null=True, ) def __str__(self): return '{}'.format(self.sid, self.subject_identifier) def save(self, *args, **kwargs): if not self.id: randomization_helper = Randomization(self, ValidationError) (self.sid, self.rx, self.randomization_datetime, self.initials) = randomization_helper.randomize() super(MaternalRando, self).save(*args, **kwargs) class Meta(CrfModelMixin.Meta): app_label = "td_maternal" verbose_name = "Maternal Randomization" verbose_name_plural = "Maternal Randomization" ordering = ('sid',) unique_together = ('sid', 'rx')
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 Plot(MapperModelMixin, PlotIdentifierModelMixin, PlotEnrollmentMixin, PlotConfirmationMixin, CreateHouseholdsModelMixin, SearchSlugModelMixin, BaseUuidModel): """A model created by the system and updated by the user to represent a Plot in the community. """ def get_search_slug_fields(self): return ['plot_identifier', 'map_area', 'cso_number'] report_datetime = models.DateTimeField(validators=[datetime_not_future], default=get_utcnow) eligible_members = models.IntegerField( verbose_name="Approximate number of age eligible members", default=0, null=True, help_text=(("Provide an approximation of the number of people " "who live in this residence who are age eligible."))) cso_number = EncryptedCharField( verbose_name="CSO Number", blank=True, null=True, help_text=("provide the CSO number or leave BLANK.")) time_of_week = models.CharField(verbose_name=( 'Time of week when most of the eligible members will be available'), max_length=25, choices=TIME_OF_WEEK, blank=True, null=True) time_of_day = models.CharField(verbose_name=( 'Time of day when most of the eligible members will be available'), max_length=25, choices=TIME_OF_DAY, blank=True, null=True) status = models.CharField(verbose_name='Plot status', max_length=35, choices=PLOT_STATUS, null=True, blank=False) description = EncryptedTextField( verbose_name="Description of plot/residence", max_length=250, blank=True, null=True) comment = EncryptedTextField(verbose_name="Comment", max_length=250, blank=True, null=True) accessible = models.BooleanField(default=True, editable=False) access_attempts = models.IntegerField( default=0, help_text=( 'Number of attempts to access a plot to determine it\'s status.'), editable=False) objects = PlotManager() history = HistoricalRecords() def __str__(self): return '{} {}'.format(self.location_name or 'undetermined', self.plot_identifier) def save(self, *args, **kwargs): if self.id and not self.location_name: self.location_name = 'plot' if self.status == INACCESSIBLE: self.accessible = False else: if self.id: PlotLogEntry = django_apps.get_model( *'plot.plotlogentry'.split('.')) try: PlotLogEntry.objects.get(plot_log__plot__pk=self.id) except PlotLogEntry.DoesNotExist: self.accessible = True except MultipleObjectsReturned: pass super().save(*args, **kwargs) def natural_key(self): return (self.plot_identifier, ) def common_clean(self): """Asserts the plot map_area is a valid map_area and that an enrolled plot cannot be unconfirmed. """ if self.map_area not in site_mappers.map_areas: raise MapperError( f'Invalid map area. Got \'{self.map_area}\'. Site mapper expects one ' f'of map_areas={site_mappers.map_areas}.') elif self.id: try: self.get_confirmed() except MapperError: if self.enrolled: raise PlotEnrollmentError( 'Plot is enrolled and may not be unconfirmed') super().common_clean() @property def common_clean_exceptions(self): return (super().common_clean_exceptions + [PlotEnrollmentError, MapperError]) @property def identifier_segment(self): return self.plot_identifier[:-3] @property def community(self): return self.map_area class Meta(DeviceModelMixin.Meta): ordering = [ '-plot_identifier', ] unique_together = (('gps_target_lat', 'gps_target_lon'), ) household_model = 'household.household' device_permissions = DevicePermissions( PlotDeviceAddPermission(device_roles=[CENTRAL_SERVER]))
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 PartOneFieldsModelMixin(models.Model): screening_consent = models.CharField( verbose_name=("Has the subject given his/her verbal consent " "to be screened for the META trial?"), max_length=15, choices=YES_NO, ) selection_method = models.CharField( verbose_name="How was the patient selected from the outpatients CTC?", max_length=25, choices=SELECTION_METHOD, ) hospital_identifier = EncryptedCharField(unique=True, blank=False) 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.", blank=False, ) ethnicity = models.CharField(max_length=15, choices=ETHNICITY, help_text="Used for eGFR calculation") hiv_pos = models.CharField(verbose_name="Is the patient HIV positive", max_length=15, choices=YES_NO) art_six_months = models.CharField( verbose_name= ("Has the patient been on anti-retroviral therapy for at least 6 months" ), max_length=15, choices=YES_NO_NA, ) on_rx_stable = models.CharField( verbose_name="Is the patient considered to be stable on treatment ", max_length=15, choices=YES_NO_NA, help_text="in regular attendance for care", ) lives_nearby = models.CharField( verbose_name= ("Is the patient living within the catchment population of the facility" ), max_length=15, choices=YES_NO, ) staying_nearby = models.CharField( verbose_name=( "Is the patient planning to remain in the catchment area " "for at least 6 months"), max_length=15, choices=YES_NO, ) pregnant = models.CharField(verbose_name="Is the patient pregnant?", max_length=15, choices=PREG_YES_NO_NA) continue_part_two = models.CharField( verbose_name=mark_safe( "Continue with <U>part two</U> of the screening process?"), max_length=15, choices=YESDEFAULT_NO, default=YES, help_text=mark_safe( "<B>Important</B>: This response will be be automatically " "set to YES if:<BR><BR>" "- the participant meets the eligibility criteria for part one, or;<BR><BR>" "- the eligibility criteria for part two is already complete.<BR>" ), ) class Meta: abstract = True
class SubjectLocator(LocatorModelMixin, RequiresConsentFieldsModelMixin, ActionModelMixin, SiteModelMixin, BaseUuidModel): """A model completed by the user to that captures participant locator information and permission to contact. """ action_name = SUBJECT_LOCATOR_ACTION tracking_identifier_prefix = 'SL' site = models.ForeignKey(Site, on_delete=models.PROTECT, null=True, editable=False, related_name='subject_locator_site') date_signed = models.DateField( verbose_name="Date Locator Form signed ", default=timezone.now, help_text="", ) local_clinic = models.CharField( verbose_name=("When you stay in the village, what clinic/health " "post do you normally go to?"), max_length=75, validators=[ RegexValidator( regex=r'^[0-9]{2}[-][0-9]{1}[-][0-9]{2}$', message='The correct clinic code format is XX-X-XX'), ], help_text="Please give clinic code.", ) home_village = models.CharField( verbose_name=("Where is your home village?"), max_length=75, help_text="", ) has_alt_contact = models.CharField( max_length=25, choices=YES_NO, verbose_name=( "If we are unable to contact the person indicated above, is " "there another individual (including next of kin) with whom " "the study team can get in contact with?"), help_text="", ) alt_contact_name = EncryptedCharField( max_length=35, verbose_name="Full Name of the responsible person", help_text="include firstname and surname", blank=True, null=True, ) alt_contact_rel = EncryptedCharField( max_length=35, verbose_name="Relationship to participant", blank=True, null=True, help_text="", ) alt_contact_cell = EncryptedCharField( max_length=8, verbose_name="Cell number", validators=[ CellNumber, ], help_text="", blank=True, null=True, ) other_alt_contact_cell = EncryptedCharField( max_length=8, verbose_name="Cell number (alternate)", validators=[ CellNumber, ], help_text="", blank=True, null=True, ) alt_contact_tel = EncryptedCharField( max_length=8, verbose_name="Telephone number", validators=[ TelephoneNumber, ], help_text="", blank=True, null=True, ) history = HistoricalRecords() on_site = CurrentSiteManager() objects = LocatorManager() history = HistoricalRecords() def __str__(self): return '{}'.format(self.subject_identifier) def natural_key(self): return (self.subject_identifier, ) natural_key.dependencies = ['sites.Site'] class Meta: verbose_name = 'Subject Locator'