class MedicationProduct(SiteModelMixin, edc_models.BaseUuidModel): product_identifier = models.CharField(max_length=36, default=uuid4, unique=True) name = models.CharField(max_length=250, unique=True, editable=False) container = models.ForeignKey(Container, on_delete=PROTECT) count_per_container = models.DecimalField( verbose_name="Items per container", max_digits=6, decimal_places=1) formulation = models.ForeignKey(Formulation, on_delete=PROTECT) lot_no = models.ForeignKey(MedicationLot, on_delete=PROTECT) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return self.name def save(self, *args, **kwargs): self.name = ( f"{self.formulation.medication.display_name} {self.formulation.strength}{self.formulation.units}. " f"LOT#: {self.lot_no.lot_no}. {self.container} of {self.count_per_container}" ) super().save(*args, **kwargs) class Meta(SiteModelMixin.Meta, edc_models.BaseUuidModel.Meta): verbose_name = "Medication product" verbose_name_plural = "Medication products"
class ReturnHistory(edc_models.BaseUuidModel): rx_refill = models.ForeignKey(RxRefill, on_delete=PROTECT) return_datetime = models.DateTimeField(default=get_utcnow) returned = models.DecimalField(max_digits=6, decimal_places=1) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return f"{str(self.rx_refill)}" def natural_key(self): return ( self.rx_refill, self.return_datetime, ) # TODO: calculate to verify number of returns makes sense # def save(self, *args, **kwargs): # if self.prescription_item.get_remaining(exclude_id=self.id) < self.returned: # raise ReturnError("Attempt to return more than prescribed.") # super().save(*args, **kwargs) @property def return_date(self): return self.return_datetime.date() class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Return history" verbose_name_plural = "Return history" unique_together = ["rx_refill", "return_datetime"]
class Medication(edc_models.BaseUuidModel): name = models.CharField(max_length=35, unique=True) display_name = models.CharField(max_length=50, unique=True) notes = models.TextField(max_length=250, null=True, blank=True) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return self.name def save(self, *args, **kwargs): self.name = self.name.strip().lower().replace(" ", "_") super().save(*args, **kwargs) def natural_key(self): return self.name class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Medication" verbose_name_plural = "Medications"
class MedicationStockReceiving(edc_models.BaseUuidModel): medication_product = models.ForeignKey(MedicationProduct, on_delete=PROTECT) qty = models.IntegerField() stock_identifiers = models.TextField() received = models.BooleanField(default=False) received_datetime = models.DateTimeField(null=True, blank=True) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return ( f"{self.medication_product}: {self.qty} recv'd on {self.received_datetime}" ) class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Medication stock: Receiving" verbose_name_plural = "Medication stock: Receiving"
class Labels(edc_models.BaseUuidModel): medication_stock_create_labels = models.ForeignKey( MedicationStockCreateLabels, on_delete=PROTECT) stock_identifier = models.CharField(max_length=36, default=uuid4, unique=True) printed = models.BooleanField(default=False) printed_datetime = models.DateTimeField(null=True, blank=True) in_stock = models.BooleanField(default=False) in_stock_datetime = models.DateTimeField(null=True, blank=True) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return f"{self.medication_stock_labels}: {self.stock_identifier} " class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Label" verbose_name_plural = "Labels"
class Aliquot( AliquotModelMixin, AliquotIdentifierModelMixin, AliquotTypeModelMixin, AliquotShippingMixin, SearchSlugModelMixin, edc_models.BaseUuidModel, ): def get_search_slug_fields(self): return [ "aliquot_identifier", "human_readable_identifier", "subject_identifier", "parent_identifier", "requisition_identifier", ] on_site = CurrentSiteManager() objects = Manager() history = edc_models.HistoricalRecords() @property def human_readable_identifier(self): """Returns a human readable aliquot identifier.""" x = self.aliquot_identifier return "{}-{}-{}-{}-{}".format(x[0:3], x[3:6], x[6:10], x[10:14], x[14:18]) class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Aliquot"
class Formulation(edc_models.BaseUuidModel): medication = models.ForeignKey(Medication, on_delete=PROTECT, null=True, blank=False) strength = models.DecimalField(max_digits=6, decimal_places=1) units = models.ForeignKey(Units, on_delete=PROTECT) formulation_type = models.ForeignKey(FormulationType, on_delete=PROTECT) route = models.ForeignKey(Route, on_delete=PROTECT) notes = models.TextField(max_length=250, null=True, blank=True) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return self.description def natural_key(self): return ( self.medication, self.strength, self.units, self.formulation_type, ) @property def description(self): return ( f"{self.medication} {round(self.strength, 0)}{self.get_units_display()} " f"{self.get_formulation_type_display()} " f"{self.get_route_display()}") def get_formulation_type_display(self): return self.formulation_type.display_name def get_units_display(self): return self.units.display_name def get_route_display(self): return self.route.display_name class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Formulation" verbose_name_plural = "Formulations" unique_together = [ "medication", "strength", "units", "formulation_type" ]
class SubjectVisit( VisitModelMixin, ReferenceModelMixin, CreatesMetadataModelMixin, SiteModelMixin, RequiresConsentFieldsModelMixin, edc_models.BaseUuidModel, ): """A model completed by the user that captures the covering information for the data collected for this timepoint/appointment, e.g.report_datetime. """ reason = models.CharField( verbose_name="What is the reason for this visit report?", max_length=25, choices=VISIT_REASON, help_text=( "Only baseline (0m), 6m and 12m are considered " "`scheduled` visits as per the MOCCA protocol." f"If `missed' complete CRF {SubjectVisitMissed._meta.verbose_name}." ), ) reason_unscheduled = models.CharField( verbose_name="If 'unscheduled', provide reason for the unscheduled visit", max_length=25, choices=VISIT_UNSCHEDULED_REASON, default=NOT_APPLICABLE, ) clinic_services = models.ManyToManyField( ClinicServices, verbose_name="Why is the patient at the clinic today?", related_name="visit_clinic_services", ) clinic_services_other = edc_models.OtherCharField() info_source = models.CharField( verbose_name="What is the main source of this information?", max_length=25, choices=INFO_SOURCE, ) on_site = CurrentSiteManager() objects = VisitModelManager() history = edc_models.HistoricalRecords() class Meta(VisitModelMixin.Meta, edc_models.BaseUuidModel.Meta): pass
class VisitSchedule(VisitScheduleMethodsModelMixin, edc_models.BaseUuidModel): visit_schedule_name = models.CharField(max_length=150) schedule_name = models.CharField(max_length=150) visit_code = models.CharField(max_length=150) visit_name = models.CharField(max_length=150) visit_title = models.CharField(max_length=150) timepoint = models.DecimalField(null=True, decimal_places=1, max_digits=6) active = models.BooleanField(default=False) objects = VisitScheduleManager() history = edc_models.HistoricalRecords() def __str__(self): return (f"{self.visit_code}@{self.timepoint}: {self.visit_title} " f"({self.visit_schedule_name}.{self.schedule_name})") def natural_key(self): return ( self.visit_schedule_name, self.schedule_name, self.visit_code, ) class Meta(edc_models.BaseUuidModel.Meta): ordering = ("visit_schedule_name", "schedule_name", "visit_code") unique_together = ( ("visit_schedule_name", "schedule_name", "visit_code"), ("visit_schedule_name", "schedule_name", "timepoint"), ) indexes = [ models.Index(fields=[ "visit_schedule_name", "schedule_name", "visit_code", "visit_name", "visit_title", ]), models.Index( fields=["visit_schedule_name", "schedule_name", "timepoint"]), ]
class ResultItem(ResultItemModelMixin, edc_models.BaseUuidModel): result = models.ForeignKey(Result, on_delete=PROTECT) on_site = CurrentSiteManager() objects = ResultItemManager() history = edc_models.HistoricalRecords() def natural_key(self): return (self.report_datetime,) + self.result.natural_key() natural_key.dependencies = ["edc_lab.result", "sites.Site"] class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Result Item"
class Shipper(edc_models.AddressMixin, edc_models.BaseUuidModel): name = models.CharField(unique=True, max_length=50) objects = ShipperManager() history = edc_models.HistoricalRecords() def natural_key(self): return (self.name,) def __str__(self): return self.name class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Shipper" ordering = ("name",)
class Result(ResultModelMixin, edc_models.BaseUuidModel): order = models.ForeignKey(Order, on_delete=PROTECT) on_site = CurrentSiteManager() objects = ResultManager() history = edc_models.HistoricalRecords() def natural_key(self): return (self.report_datetime, self.order.order_identifier) natural_key.dependencies = ["edc_lab.order", "edc_lab.panel", "sites.Site"] class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Result"
class MedicationLot(edc_models.BaseUuidModel): lot_no = models.CharField(max_length=50, unique=True) expiration_date = models.DateField() formulation = models.ForeignKey(Formulation, on_delete=PROTECT) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return self.lot_no class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Medication lot" verbose_name_plural = "Medication lots"
class Consignee(edc_models.AddressMixin, edc_models.BaseUuidModel): name = models.CharField(unique=True, max_length=50, help_text="Company name") objects = ConsigneeManager() history = edc_models.HistoricalRecords() def natural_key(self): return (self.name, ) def __str__(self): return self.name class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Consignee" ordering = ("name", )
class MedicationStockCreateLabels(edc_models.BaseUuidModel): medication_product = models.ForeignKey(MedicationProduct, on_delete=PROTECT) qty = models.IntegerField(verbose_name="Number of labels to print") printed = models.BooleanField(default=False) printed_datetime = models.DateTimeField(null=True, blank=True) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return f"{self.medication_product}: {self.qty} " class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Medication stock: Create labels" verbose_name_plural = "Medication stock: Create labels"
class DispensingHistory(edc_models.BaseUuidModel): rx_refill = models.ForeignKey(RxRefill, on_delete=PROTECT) dispensed_datetime = models.DateTimeField(default=get_utcnow) dispensed = models.DecimalField(max_digits=6, decimal_places=1) status = models.CharField(verbose_name="Status", max_length=25, default=DISPENSED, choices=DISPENSE_STATUS) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return f"{str(self.rx_refill)}" def natural_key(self): return ( self.rx_refill, self.dispensed_datetime, ) def save(self, *args, **kwargs): Dispensing(rx_refill=self.rx_refill, dispensed=self.dispensed, exclude_id=self.id) super().save(*args, **kwargs) @property def dispensed_date(self): return self.dispensed_datetime.date() class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Dispensing history" verbose_name_plural = "Dispensing history" unique_together = ["rx_refill", "dispensed_datetime"]
class Order(SiteModelMixin, edc_models.BaseUuidModel): aliquot = models.ForeignKey(Aliquot, on_delete=PROTECT) order_identifier = models.CharField(max_length=25, editable=False, unique=True) order_datetime = models.DateTimeField(default=get_utcnow, validators=[datetime_not_future]) panel_name = models.CharField(max_length=25) on_site = CurrentSiteManager() objects = OrderManager() history = edc_models.HistoricalRecords() def natural_key(self): return (self.report_datetime,) + self.aliquot.natural_key() natural_key.dependencies = ["edc_lab.aliquot", "sites.Site"] class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Order"
class Manifest(ManifestModelMixin, SearchSlugModelMixin, edc_models.BaseUuidModel): def get_search_slug_fields(self): return [ "manifest_identifier", "human_readable_identifier", "shipper.name", "consignee.name", ] consignee = models.ForeignKey(Consignee, verbose_name="Consignee", on_delete=PROTECT) shipper = models.ForeignKey(Shipper, verbose_name="Shipper/Exporter", on_delete=PROTECT) on_site = CurrentSiteManager() objects = Manager() history = edc_models.HistoricalRecords() def natural_key(self): return (self.manifest_identifier,) natural_key.dependencies = ["edc_lab.shipper", "edc_lab.consignee"] def __str__(self): return "{} created on {} by {}".format( self.manifest_identifier, self.manifest_datetime.strftime("%Y-%m-%d"), self.user_created, ) @property def count(self): return self.manifestitem_set.all().count() class Meta(ManifestModelMixin.Meta, edc_models.BaseUuidModel.Meta): verbose_name = "Manifest"
class BoxItem(SearchSlugModelMixin, VerifyModelMixin, edc_models.BaseUuidModel): box = models.ForeignKey(Box, on_delete=PROTECT) position = models.IntegerField() identifier = models.CharField(max_length=25) comment = models.CharField(max_length=25, null=True, blank=True) objects = BoxItemManager() history = edc_models.HistoricalRecords() def natural_key(self): return (self.position, self.identifier) + self.box.natural_key() natural_key.dependencies = ["edc_lab.box", "edc_lab.boxtype", "sites.Site"] @property def human_readable_identifier(self): """Returns a human readable identifier.""" if self.identifier: x = self.identifier if re.match(aliquot_pattern, self.identifier): return "{}-{}-{}-{}-{}".format(x[0:3], x[3:6], x[6:10], x[10:14], x[14:18]) return self.identifier def get_slugs(self): slugs = [self.identifier, self.human_readable_identifier] return slugs class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Box Item" ordering = ("position",) unique_together = (("box", "position"), ("box", "identifier"))
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 RxRefill( MedicationOrderModelMixin, VisitCodeFieldsModelMixin, SiteModelMixin, edc_models.BaseUuidModel, ): rx = models.ForeignKey(Rx, on_delete=PROTECT) dosage_guideline = models.ForeignKey(DosageGuideline, on_delete=PROTECT) formulation = models.ForeignKey(Formulation, on_delete=PROTECT, null=True) dose = models.DecimalField( max_digits=6, decimal_places=1, null=True, blank=True, help_text="dose per frequency if NOT considering weight", ) calculate_dose = models.BooleanField(default=True) frequency = models.IntegerField( validators=[MinValueValidator(1)], null=True, blank=True, ) frequency_units = models.ForeignKey( FrequencyUnits, verbose_name="per", on_delete=PROTECT, null=True, blank=True, ) weight_in_kgs = models.DecimalField(max_digits=6, decimal_places=1, null=True, blank=True) refill_date = models.DateField(verbose_name="Refill date", default=get_utcnow_as_date, help_text="") number_of_days = models.IntegerField(null=True) total = models.DecimalField( max_digits=6, decimal_places=1, null=True, blank=True, help_text="Leave blank to auto-calculate", ) remaining = models.DecimalField( max_digits=6, decimal_places=1, null=True, blank=True, help_text="Leave blank to auto-calculate", ) notes = models.TextField( max_length=250, null=True, blank=True, help_text="Additional information for patient", ) active = models.BooleanField(default=False) verified = models.BooleanField(default=False) verified_datetime = models.DateTimeField(null=True, blank=True) as_string = models.CharField(max_length=150, editable=False) on_site = CurrentSiteManager() objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return ( f"{self.rx} " f"Take {self.dose} {self.formulation.formulation_type.display_name} {self.formulation.route.display_name} " # f"{self.frequency} {self.frequency_units.display_name}" ) def natural_key(self): return ( self.rx, self.medication, self.refill_date, ) def save(self, *args, **kwargs): if self.active: opts = dict(id=self.id) if self.id else {} if (self.__class__.objects.filter( rx__subject_identifier=self.rx.subject_identifier, dosage_guideline=self.dosage_guideline, active=True, ).exclude(**opts).exists()): raise ActivePrescriptionRefillExists( f"Unable to save as an active refill. An active refill already exists." ) self.medication = self.dosage_guideline.medication # if not self.dose and self.calculate_dose: self.dose = dosage_per_day( self.dosage_guideline, weight_in_kgs=self.weight_in_kgs, strength=self.formulation.strength, strength_units=self.formulation.units.name, ) self.frequency = self.dosage_guideline.frequency self.frequency_units = self.dosage_guideline.frequency_units self.total = float(self.dose) * float(self.number_of_days) if not self.id: self.remaining = self.total self.as_string = str(self) super().save(*args, **kwargs) @property def subject_identifier(self): return self.rx.subject_identifier class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "RX refill" verbose_name_plural = "RX refills" unique_together = [ ["rx", "dosage_guideline", "refill_date"], ["rx", "visit_code", "visit_code_sequence"], ]
class DosageGuideline(edc_models.BaseUuidModel): """Dosage guidelines.""" medication = models.ForeignKey( Medication, on_delete=PROTECT, null=True, blank=False ) dose = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, help_text="dose per 'frequency unit' if NOT considering subject's weight", ) dose_per_kg = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, help_text="dose per 'frequency unit' if considering subject's weight", ) dose_units = models.ForeignKey(Units, on_delete=PROTECT) frequency = models.DecimalField( verbose_name="Frequency", max_digits=6, decimal_places=2, validators=[MinValueValidator(1.0)], default=1, help_text="number of times per 'frequency unit'", ) frequency_units = models.ForeignKey( FrequencyUnits, verbose_name="Frequency unit", on_delete=PROTECT, ) objects = Manager() history = edc_models.HistoricalRecords() def __str__(self): return ( f"{self.medication.name} {round(self.dose or 0, 0)}{self.dose_units} " f"{round((self.frequency or 0), 0)} " f"{self.get_frequency_units_display()}{' (per kg)' if self.dose_per_kg else ''}" ) def natural_key(self): return ( self.medication, self.dose, self.dose_units, self.dose_per_kg, ) def get_dose_units_display(self): return self.dose_units.display_name def get_frequency_units_display(self): return self.frequency_units.display_name class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Dosage Guideline" verbose_name_plural = "Dosage Guidelines" unique_together = ["medication", "dose", "dose_units", "dose_per_kg"]
class Box(SearchSlugModelMixin, VerifyBoxModelMixin, SiteModelMixin, edc_models.BaseUuidModel): search_slug_fields = [ "box_identifier", "human_readable_identifier", "name" ] box_identifier = models.CharField(max_length=25, editable=False, unique=True) name = models.CharField(max_length=25, null=True, blank=True) box_datetime = models.DateTimeField(default=timezone.now) box_type = models.ForeignKey(BoxType, on_delete=PROTECT) category = models.CharField(max_length=25, default=TESTING, choices=BOX_CATEGORY) category_other = models.CharField(max_length=25, null=True, blank=True) specimen_types = models.CharField( max_length=25, help_text=("List of specimen types in this box. Use two-digit numeric " "codes separated by commas."), ) status = models.CharField(max_length=15, default=OPEN, choices=STATUS) accept_primary = models.BooleanField( default=False, help_text="Tick to allow 'primary' specimens to be added to this box", ) comment = models.TextField(null=True, blank=True) on_site = CurrentSiteManager() objects = BoxManager() history = edc_models.HistoricalRecords() def save(self, *args, **kwargs): if not self.box_identifier: identifier = BoxIdentifier() self.box_identifier = identifier.identifier if not self.name: self.name = self.box_identifier self.update_verified() super().save(*args, **kwargs) def __str__(self): return self.name def natural_key(self): return (self.box_identifier, ) natural_key.dependencies = ["edc_lab.boxtype", "sites.Site"] @property def count(self): return self.boxitem_set.all().count() @property def items(self): return self.boxitem_set.all().order_by("position") @property def human_readable_identifier(self): x = self.box_identifier return "{}-{}-{}".format(x[0:4], x[4:8], x[8:12]) @property def next_position(self): """Returns an integer or None.""" last_obj = self.boxitem_set.all().order_by("position").last() if not last_obj: next_position = 1 else: next_position = last_obj.position + 1 if next_position > self.box_type.total: raise BoxIsFullError( f"Box is full. Box {self.human_readable_identifier} has " f"{self.box_type.total} specimens.") return next_position @property def max_position(self): return class Meta(edc_models.BaseUuidModel.Meta): verbose_name = "Box" ordering = ("-box_datetime", ) verbose_name_plural = "Boxes"