clinicedc 2.0.34__py3-none-any.whl → 2.0.35__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clinicedc might be problematic. Click here for more details.
- {clinicedc-2.0.34.dist-info → clinicedc-2.0.35.dist-info}/METADATA +1 -1
- {clinicedc-2.0.34.dist-info → clinicedc-2.0.35.dist-info}/RECORD +155 -165
- {clinicedc-2.0.34.dist-info → clinicedc-2.0.35.dist-info}/WHEEL +1 -1
- edc_action_item/templatetags/action_item_extras.py +1 -1
- edc_adverse_event/form_validator_mixins/death_report_form_validator.py +1 -1
- edc_adverse_event/form_validator_mixins/requires_death_report_form_validator_mixin.py +5 -3
- edc_adverse_event/form_validators/death_report_tmg.py +2 -2
- edc_adverse_event/modeladmin_mixins/ae_tmg_admin_mixin.py +1 -1
- edc_adverse_event/modeladmin_mixins/utils.py +1 -1
- edc_adverse_event/utils.py +1 -1
- edc_appointment/admin/appointment_admin.py +24 -24
- edc_appointment/admin/list_filters.py +5 -5
- edc_appointment/appointment_reason_updater.py +6 -5
- edc_appointment/appointment_status_updater.py +2 -2
- edc_appointment/context_processors.py +1 -2
- edc_appointment/creators/appointment_creator.py +9 -8
- edc_appointment/creators/unscheduled_appointment_creator.py +31 -24
- edc_appointment/creators/utils.py +35 -37
- edc_appointment/exceptions.py +3 -3
- edc_appointment/form_runners.py +2 -2
- edc_appointment/form_validator_mixins/next_appointment_crf_form_validator_mixin.py +5 -5
- edc_appointment/form_validator_mixins/window_period_form_validator_mixin.py +7 -7
- edc_appointment/form_validators/appointment_form_validator.py +16 -23
- edc_appointment/management/commands/close_appointments.py +3 -3
- edc_appointment/management/commands/reset_visit_code_sequences.py +1 -1
- edc_appointment/management/commands/update_appointment_status.py +2 -2
- edc_appointment/management/commands/update_skipped_appointments.py +7 -7
- edc_appointment/managers.py +6 -7
- edc_appointment/model_mixins/appointment_model_mixin.py +3 -4
- edc_appointment/modeladmin_mixins/next_appointment_crf_modeladmin_mixin.py +2 -2
- edc_appointment/modelform_mixins/next_appointment_crf_modelform_mixins.py +25 -24
- edc_appointment/models/signals.py +1 -1
- edc_appointment/skip_appointments.py +10 -29
- edc_appointment/utils.py +43 -47
- edc_appointment/view_mixins/appointment_view_mixin.py +11 -13
- edc_appointment/views/unscheduled_appointment_view.py +5 -6
- edc_consent/consent_definition.py +5 -13
- edc_consent/consent_definition_extension.py +0 -2
- edc_consent/form_validators/consent_definition_form_validator_mixin.py +26 -30
- edc_consent/form_validators/subject_consent_form_validator.py +3 -3
- edc_consent/modelform_mixins/consent_modelform_mixin/consent_modelform_validation_mixin.py +1 -1
- edc_consent/modelform_mixins/requires_consent_modelform_mixin.py +4 -5
- edc_consent/site_consents.py +13 -14
- edc_crf/crf_form_validator.py +13 -19
- edc_crf/crf_form_validator_mixins.py +2 -17
- edc_data_manager/admin/actions.py +1 -1
- edc_data_manager/admin/data_query_admin.py +1 -1
- edc_dx/form_validators/result_form_validator_mixin.py +1 -1
- edc_dx_review/medical_date.py +1 -1
- edc_egfr/egfr.py +4 -8
- edc_egfr/form_validator_mixins/egfr_form_validator_mixins.py +2 -2
- edc_facility/facility.py +7 -11
- edc_facility/holidays.py +3 -3
- edc_facility/models/holiday.py +1 -1
- edc_form_runners/form_runner.py +15 -16
- edc_form_validators/base_form_validator.py +5 -1
- edc_form_validators/date_range_validator.py +49 -61
- edc_form_validators/date_validator.py +1 -1
- edc_form_validators/extra_mixins/study_day_form_validator.py +1 -1
- edc_lab/form_validators/crf_requisition_form_validator_mixin.py +21 -15
- edc_lab/form_validators/requisition_form_validator_mixin.py +3 -3
- edc_lab_results/form_validator_mixins/blood_results_form_validator_mixin.py +1 -1
- edc_ltfu/modelform_mixins.py +1 -1
- edc_metadata/metadata/metadata.py +20 -7
- edc_metadata/metadata_rules/logic.py +5 -4
- edc_metadata/metadata_rules/predicate.py +22 -24
- edc_model_form/mixins/report_datetime_modelform_mixin.py +1 -5
- edc_offstudy/model_mixins/offstudy_model_mixin.py +1 -1
- edc_offstudy/modelform_mixins/crf/offstudy_crf_modelform_mixin.py +2 -2
- edc_offstudy/utils.py +4 -4
- edc_pdf_reports/crf_pdf_report.py +2 -1
- edc_pdutils/helper.py +3 -3
- edc_pdutils/utils/convert_dates_from_model.py +4 -3
- edc_pharmacy/admin/actions/confirm_stock.py +3 -3
- edc_pharmacy/admin/actions/delete_items_for_stock_request.py +4 -3
- edc_pharmacy/admin/actions/delete_order_items.py +7 -3
- edc_pharmacy/admin/actions/delete_receive_items.py +5 -3
- edc_pharmacy/admin/actions/process_repack_request.py +7 -11
- edc_pharmacy/admin/autocomplete_admin.py +1 -1
- edc_pharmacy/admin/list_filters.py +48 -46
- edc_pharmacy/admin/medication/assignment_admin.py +4 -4
- edc_pharmacy/admin/medication/dosage_guideline_admin.py +3 -3
- edc_pharmacy/admin/medication/formulation_admin.py +5 -5
- edc_pharmacy/admin/medication/medication_admin.py +3 -3
- edc_pharmacy/admin/prescription/rx_admin.py +2 -2
- edc_pharmacy/admin/prescription/rx_refill_admin.py +11 -17
- edc_pharmacy/admin/remove_fields_for_blinded_users.py +1 -1
- edc_pharmacy/admin/reports/stock_availability_admin.py +12 -8
- edc_pharmacy/admin/stock/allocation_admin.py +13 -17
- edc_pharmacy/admin/stock/allocation_proxy_admin.py +1 -2
- edc_pharmacy/admin/stock/confirmation_admin.py +4 -8
- edc_pharmacy/admin/stock/confirmation_at_site_item_admin.py +1 -1
- edc_pharmacy/admin/stock/container_admin.py +3 -3
- edc_pharmacy/admin/stock/location_admin.py +3 -6
- edc_pharmacy/admin/stock/lot_admin.py +9 -12
- edc_pharmacy/admin/stock/order_admin.py +2 -5
- edc_pharmacy/admin/stock/order_item_admin.py +14 -22
- edc_pharmacy/admin/stock/product_admin.py +6 -9
- edc_pharmacy/admin/stock/receive_admin.py +2 -2
- edc_pharmacy/admin/stock/receive_item_admin.py +3 -3
- edc_pharmacy/admin/stock/repack_request_admin.py +7 -10
- edc_pharmacy/admin/stock/stock_adjustment_admin.py +1 -1
- edc_pharmacy/admin/stock/stock_admin.py +17 -28
- edc_pharmacy/admin/stock/stock_proxy_admin.py +3 -4
- edc_pharmacy/admin/stock/stock_request_admin.py +6 -10
- edc_pharmacy/admin/stock/stock_request_item_admin.py +9 -18
- edc_pharmacy/admin/stock/stock_transfer_admin.py +5 -5
- edc_pharmacy/admin/stock/stock_transfer_item_admin.py +3 -3
- edc_pharmacy/admin/stock/storage_bin_admin.py +1 -1
- edc_pharmacy/admin/stock/storage_bin_item_admin.py +2 -2
- edc_pharmacy/form_validators/crf/study_medication_form_validator.py +27 -27
- edc_pharmacy/forms/stock/confirmation_form.py +2 -2
- edc_pharmacy/forms/stock/dispense_form.py +2 -2
- edc_pharmacy/forms/stock/lot_form.py +6 -6
- edc_pharmacy/forms/stock/order_form.py +4 -6
- edc_pharmacy/forms/stock/product_form.py +2 -3
- edc_pharmacy/forms/stock/receive_form.py +4 -4
- edc_pharmacy/forms/stock/receive_item_form.py +7 -5
- edc_pharmacy/forms/stock/repack_request_form.py +10 -8
- edc_pharmacy/forms/stock/stock_form.py +2 -3
- edc_pharmacy/forms/stock/stock_request_form.py +10 -8
- edc_pharmacy/forms/stock/stock_request_item_form.py +6 -5
- edc_pharmacy/forms/stock/stock_transfer_form.py +16 -14
- edc_pharmacy/forms/stock/supplier_form.py +2 -2
- edc_pharmacy/labels/label_data.py +2 -3
- edc_pharmacy/model_mixins/study_medication_crf_model_mixin.py +1 -1
- edc_pharmacy/models/medication/dosage_guideline.py +3 -3
- edc_pharmacy/models/model_mixins.py +12 -10
- edc_pharmacy/models/prescription/rx.py +1 -1
- edc_pharmacy/models/prescription/rx_refill.py +1 -1
- edc_pharmacy/models/signals.py +2 -3
- edc_pharmacy/models/storage/utils.py +4 -4
- edc_pharmacy/pdf_reports/manifest_pdf_report.py +3 -6
- edc_pharmacy/pdf_reports/stock_pdf_report.py +1 -3
- edc_pharmacy/refill/refill_creator.py +3 -4
- edc_protocol/validators.py +10 -16
- edc_reportable/forms/reportables_form_validator_mixin.py +1 -1
- edc_screening/form_validator_mixins.py +2 -1
- edc_transfer/form_validators.py +1 -1
- edc_transfer/model_mixins.py +1 -1
- edc_utils/age.py +17 -15
- edc_utils/date.py +7 -7
- edc_utils/text.py +7 -6
- edc_visit_schedule/exceptions.py +8 -0
- edc_visit_schedule/model_mixins/off_schedule_model_mixin.py +1 -1
- edc_visit_schedule/model_mixins/on_schedule_model_mixin.py +1 -1
- edc_visit_schedule/modelform_mixins/off_schedule_modelform_mixin.py +1 -3
- edc_visit_schedule/schedule/visit_collection.py +1 -1
- edc_visit_schedule/schedule/window.py +0 -2
- edc_visit_schedule/subject_schedule.py +1 -1
- edc_visit_schedule/utils.py +4 -4
- edc_visit_schedule/visit/visit.py +9 -3
- edc_visit_schedule/visit/window_period.py +1 -1
- edc_visit_tracking/form_validators/visit_form_validator.py +1 -1
- edc_metadata/metadata_wrappers/__init__.py +0 -5
- edc_metadata/metadata_wrappers/crf_metadata_wrapper.py +0 -5
- edc_metadata/metadata_wrappers/crf_metadata_wrappers.py +0 -8
- edc_metadata/metadata_wrappers/metadata_wrapper.py +0 -74
- edc_metadata/metadata_wrappers/metadata_wrappers.py +0 -33
- edc_metadata/metadata_wrappers/requisition_metadata_wrapper.py +0 -26
- edc_metadata/metadata_wrappers/requisition_metadata_wrappers.py +0 -10
- edc_pharmacy/management/__init__.py +0 -0
- edc_pharmacy/management/commands/__init__.py +0 -0
- edc_pharmacy/management/commands/update_initial_pharmacy_data.py +0 -10
- {clinicedc-2.0.34.dist-info → clinicedc-2.0.35.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,16 +4,15 @@ from ...models import Stock
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class StockForm(forms.ModelForm):
|
|
7
|
-
|
|
8
7
|
class Meta:
|
|
9
8
|
model = Stock
|
|
10
9
|
fields = "__all__"
|
|
11
|
-
help_text = {
|
|
10
|
+
help_text = { # noqa: RUF012
|
|
12
11
|
"stock_identifier": "(read-only)",
|
|
13
12
|
"receive_item": "(read-only)",
|
|
14
13
|
"container": "(read-only)",
|
|
15
14
|
}
|
|
16
|
-
widgets = {
|
|
15
|
+
widgets = { # noqa: RUF012
|
|
17
16
|
"stock_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
18
17
|
"receive_item": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
19
18
|
"container": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
@@ -62,13 +62,15 @@ class StockRequestForm(forms.ModelForm):
|
|
|
62
62
|
|
|
63
63
|
if not self.instance.id and cleaned_data.get("cancel") == "CANCEL":
|
|
64
64
|
raise forms.ValidationError("Leave this blank")
|
|
65
|
-
if
|
|
66
|
-
|
|
65
|
+
if (
|
|
66
|
+
cleaned_data.get("cancel") == "CANCEL"
|
|
67
|
+
and Allocation.objects.filter(
|
|
67
68
|
stock_request_item__stock_request=self.instance
|
|
68
|
-
).exists()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
).exists()
|
|
70
|
+
):
|
|
71
|
+
raise forms.ValidationError(
|
|
72
|
+
"May not be cancelled. Stock has been allocated for this request"
|
|
73
|
+
)
|
|
72
74
|
return cleaned_data
|
|
73
75
|
|
|
74
76
|
@staticmethod
|
|
@@ -112,7 +114,7 @@ class StockRequestForm(forms.ModelForm):
|
|
|
112
114
|
class Meta:
|
|
113
115
|
model = StockRequest
|
|
114
116
|
fields = "__all__"
|
|
115
|
-
help_text = {"request_identifier": "(read-only)"}
|
|
116
|
-
widgets = {
|
|
117
|
+
help_text = {"request_identifier": "(read-only)"} # noqa: RUF012
|
|
118
|
+
widgets = { # noqa: RUF012
|
|
117
119
|
"request_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
118
120
|
}
|
|
@@ -7,7 +7,6 @@ from ...models import StockRequestItem
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class StockRequestItemForm(forms.ModelForm):
|
|
10
|
-
|
|
11
10
|
def clean(self):
|
|
12
11
|
cleaned_data = super().clean()
|
|
13
12
|
try:
|
|
@@ -16,14 +15,16 @@ class StockRequestItemForm(forms.ModelForm):
|
|
|
16
15
|
consent_datetime__isnull=False,
|
|
17
16
|
randomization_datetime__isnull=False,
|
|
18
17
|
)
|
|
19
|
-
except ObjectDoesNotExist:
|
|
20
|
-
raise forms.ValidationError(
|
|
18
|
+
except ObjectDoesNotExist as e:
|
|
19
|
+
raise forms.ValidationError(
|
|
20
|
+
{"subject_identifier": "Subject does not exist"}
|
|
21
|
+
) from e
|
|
21
22
|
return cleaned_data
|
|
22
23
|
|
|
23
24
|
class Meta:
|
|
24
25
|
model = StockRequestItem
|
|
25
26
|
fields = "__all__"
|
|
26
|
-
help_text = {"request_item_identifier": "(read-only)"}
|
|
27
|
-
widgets = {
|
|
27
|
+
help_text = {"request_item_identifier": "(read-only)"} # noqa: RUF012
|
|
28
|
+
widgets = { # noqa: RUF012
|
|
28
29
|
"request_item_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
29
30
|
}
|
|
@@ -4,29 +4,31 @@ from ...models import StockTransfer, StockTransferItem
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class StockTransferForm(forms.ModelForm):
|
|
7
|
-
|
|
8
7
|
def clean(self):
|
|
9
8
|
cleaned_data = super().clean()
|
|
10
9
|
items_qs = StockTransferItem.objects.filter(stock_transfer__pk=self.instance.pk)
|
|
11
|
-
if
|
|
12
|
-
|
|
10
|
+
if (
|
|
11
|
+
cleaned_data.get("to_location")
|
|
12
|
+
and items_qs.count() > 0
|
|
13
|
+
and (
|
|
13
14
|
items_qs[0].stock.allocation.registered_subject.site
|
|
14
15
|
!= cleaned_data.get("to_location").site
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
)
|
|
17
|
+
):
|
|
18
|
+
raise forms.ValidationError(
|
|
19
|
+
{
|
|
20
|
+
"to_location": (
|
|
21
|
+
"Invalid location. Does not match the intended location of "
|
|
22
|
+
"existing stock items for this transfer."
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
)
|
|
24
26
|
return cleaned_data
|
|
25
27
|
|
|
26
28
|
class Meta:
|
|
27
29
|
model = StockTransfer
|
|
28
30
|
fields = "__all__"
|
|
29
|
-
help_text = {"transfer_identifier": "(read-only)"}
|
|
30
|
-
widgets = {
|
|
31
|
+
help_text = {"transfer_identifier": "(read-only)"} # noqa: RUF012
|
|
32
|
+
widgets = { # noqa: RUF012
|
|
31
33
|
"transfer_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
32
34
|
}
|
|
@@ -7,7 +7,7 @@ class SupplierForm(forms.ModelForm):
|
|
|
7
7
|
class Meta:
|
|
8
8
|
model = Supplier
|
|
9
9
|
fields = "__all__"
|
|
10
|
-
help_text = {"supplier_identifier": "(read-only)"}
|
|
11
|
-
widgets = {
|
|
10
|
+
help_text = {"supplier_identifier": "(read-only)"} # noqa: RUF012
|
|
11
|
+
widgets = { # noqa: RUF012
|
|
12
12
|
"supplier_identifier": forms.TextInput(attrs={"readonly": "readonly"}),
|
|
13
13
|
}
|
|
@@ -3,12 +3,11 @@ import string
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class LabelData:
|
|
6
|
-
|
|
7
6
|
def __init__(self):
|
|
8
|
-
self.gender = random.choice(["M", "F"]) # nosec B311
|
|
7
|
+
self.gender = random.choice(["M", "F"]) # nosec B311 # noqa: S311
|
|
9
8
|
self.subject_identifier = "999-99-9999-9" # nosec B311
|
|
10
9
|
self.reference = "".join(
|
|
11
|
-
random.choices(string.ascii_letters.upper() + "23456789", k=6) # nosec B311
|
|
10
|
+
random.choices(string.ascii_letters.upper() + "23456789", k=6) # nosec B311 # noqa: S311
|
|
12
11
|
)
|
|
13
12
|
self.sid = "12345"
|
|
14
13
|
self.site_name = "AMANA"
|
|
@@ -7,7 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|
|
7
7
|
|
|
8
8
|
from edc_appointment.utils import get_next_appointment
|
|
9
9
|
from edc_constants.constants import YES
|
|
10
|
-
from edc_utils import formatted_datetime
|
|
10
|
+
from edc_utils.text import formatted_datetime
|
|
11
11
|
from edc_visit_tracking.utils import get_next_related_visit
|
|
12
12
|
|
|
13
13
|
from ..exceptions import NextStudyMedicationError, StudyMedicationError
|
|
@@ -83,9 +83,9 @@ class DosageGuideline(BaseUuidModel):
|
|
|
83
83
|
class Meta(BaseUuidModel.Meta):
|
|
84
84
|
verbose_name = "Dosage Guideline"
|
|
85
85
|
verbose_name_plural = "Dosage Guidelines"
|
|
86
|
-
constraints =
|
|
86
|
+
constraints = (
|
|
87
87
|
UniqueConstraint(
|
|
88
88
|
fields=["medication", "dose", "dose_units", "dose_per_kg"],
|
|
89
89
|
name="%(app_label)s_%(class)s_med_dose_uniq",
|
|
90
|
-
)
|
|
91
|
-
|
|
90
|
+
),
|
|
91
|
+
)
|
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
from django.db import models
|
|
2
2
|
|
|
3
|
+
from edc_constants.constants import NULL_STRING
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
class AddressModelMixin(models.Model):
|
|
5
|
-
address_one = models.CharField(max_length=255, default=
|
|
7
|
+
address_one = models.CharField(max_length=255, default=NULL_STRING, blank=True)
|
|
6
8
|
|
|
7
|
-
address_two = models.CharField(max_length=255, default=
|
|
9
|
+
address_two = models.CharField(max_length=255, default=NULL_STRING, blank=True)
|
|
8
10
|
|
|
9
|
-
city = models.CharField(max_length=255, default=
|
|
11
|
+
city = models.CharField(max_length=255, default=NULL_STRING, blank=True)
|
|
10
12
|
|
|
11
|
-
postal_code = models.CharField(max_length=255, default=
|
|
13
|
+
postal_code = models.CharField(max_length=255, default=NULL_STRING, blank=True)
|
|
12
14
|
|
|
13
|
-
state = models.CharField(max_length=255, default=
|
|
15
|
+
state = models.CharField(max_length=255, default=NULL_STRING, blank=True)
|
|
14
16
|
|
|
15
|
-
country = models.CharField(max_length=255, default=
|
|
17
|
+
country = models.CharField(max_length=255, default=NULL_STRING, blank=True)
|
|
16
18
|
|
|
17
19
|
class Meta:
|
|
18
20
|
abstract = True
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
class ContactModelMixin(models.Model):
|
|
22
|
-
email = models.EmailField(default=
|
|
24
|
+
email = models.EmailField(default=NULL_STRING, blank=True)
|
|
23
25
|
|
|
24
|
-
email_alternative = models.EmailField(default=
|
|
26
|
+
email_alternative = models.EmailField(default=NULL_STRING, blank=True)
|
|
25
27
|
|
|
26
|
-
telephone = models.CharField(max_length=15, default=
|
|
28
|
+
telephone = models.CharField(max_length=15, default=NULL_STRING, blank=True)
|
|
27
29
|
|
|
28
|
-
telephone_alternative = models.CharField(max_length=15, default=
|
|
30
|
+
telephone_alternative = models.CharField(max_length=15, default=NULL_STRING, blank=True)
|
|
29
31
|
|
|
30
32
|
class Meta:
|
|
31
33
|
abstract = True
|
|
@@ -13,7 +13,7 @@ from edc_randomization.site_randomizers import site_randomizers
|
|
|
13
13
|
from edc_registration.models import RegisteredSubject
|
|
14
14
|
from edc_sites.managers import CurrentSiteManager
|
|
15
15
|
from edc_sites.model_mixins import SiteModelMixin
|
|
16
|
-
from edc_utils import formatted_age
|
|
16
|
+
from edc_utils.age import formatted_age
|
|
17
17
|
|
|
18
18
|
from ...choices import PRESCRIPTION_STATUS
|
|
19
19
|
from ...constants import PRESCRIPTION_ACTION
|
|
@@ -9,8 +9,8 @@ from django.db.models.deletion import PROTECT
|
|
|
9
9
|
from edc_model.models import BaseUuidModel, HistoricalRecords
|
|
10
10
|
from edc_sites.managers import CurrentSiteManager
|
|
11
11
|
from edc_sites.model_mixins import SiteModelMixin
|
|
12
|
-
from edc_utils import convert_php_dateformat
|
|
13
12
|
from edc_utils.round_up import round_half_away_from_zero
|
|
13
|
+
from edc_utils.text import convert_php_dateformat
|
|
14
14
|
|
|
15
15
|
from ...dosage_calculator import DosageCalculator
|
|
16
16
|
from ...model_mixins import MedicationOrderModelMixin, PreviousNextModelMixin
|
edc_pharmacy/models/signals.py
CHANGED
|
@@ -278,6 +278,5 @@ def create_or_update_refills_on_post_save(
|
|
|
278
278
|
def update_previous_refill_end_datetime_on_post_save(
|
|
279
279
|
sender, instance, raw, created, update_fields, **kwargs
|
|
280
280
|
):
|
|
281
|
-
if not raw and not update_fields:
|
|
282
|
-
|
|
283
|
-
update_previous_refill_end_datetime(instance)
|
|
281
|
+
if not raw and not update_fields and isinstance(instance, (StudyMedicationCrfModelMixin,)):
|
|
282
|
+
update_previous_refill_end_datetime(instance)
|
|
@@ -48,10 +48,10 @@ def repackage(
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def repackage_for_subject(
|
|
51
|
-
rando_sid: str
|
|
52
|
-
subject_identifier: str
|
|
53
|
-
randomizer_name
|
|
54
|
-
source_container
|
|
51
|
+
rando_sid: str,
|
|
52
|
+
subject_identifier: str | None,
|
|
53
|
+
randomizer_name: str,
|
|
54
|
+
source_container,
|
|
55
55
|
**kwargs,
|
|
56
56
|
):
|
|
57
57
|
site_randomizers.get(randomizer_name)
|
|
@@ -22,13 +22,12 @@ class NumberedCanvas(BaseNumberedCanvas):
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class ManifestReport(Report):
|
|
25
|
-
|
|
26
25
|
def __init__(self, stock_transfer: StockTransfer = None, **kwargs):
|
|
27
26
|
self.stock_transfer = stock_transfer
|
|
28
27
|
self.protocol_name = ResearchProtocolConfig().protocol_title
|
|
29
28
|
super().__init__(**kwargs)
|
|
30
29
|
|
|
31
|
-
def draw_header(self, canvas, doc):
|
|
30
|
+
def draw_header(self, canvas, doc): # noqa: ARG002
|
|
32
31
|
width, height = A4
|
|
33
32
|
canvas.setFontSize(6)
|
|
34
33
|
text_width = stringWidth(self.protocol_name, "Helvetica", 6)
|
|
@@ -48,7 +47,7 @@ class ManifestReport(Report):
|
|
|
48
47
|
"stock__allocation__registered_subject__subject_identifier"
|
|
49
48
|
)
|
|
50
49
|
|
|
51
|
-
def get_report_story(self, document_template: SimpleDocTemplate = None, **kwargs):
|
|
50
|
+
def get_report_story(self, document_template: SimpleDocTemplate = None, **kwargs): # noqa: ARG002
|
|
52
51
|
story = []
|
|
53
52
|
|
|
54
53
|
data = [
|
|
@@ -147,7 +146,6 @@ class ManifestReport(Report):
|
|
|
147
146
|
|
|
148
147
|
@property
|
|
149
148
|
def stock_transfer_items_as_table(self) -> Table:
|
|
150
|
-
|
|
151
149
|
style = ParagraphStyle(
|
|
152
150
|
name="line_data_medium",
|
|
153
151
|
alignment=TA_CENTER,
|
|
@@ -267,8 +265,7 @@ class ManifestReport(Report):
|
|
|
267
265
|
Paragraph(_("Received count"), style=style),
|
|
268
266
|
],
|
|
269
267
|
]
|
|
270
|
-
|
|
271
|
-
return table
|
|
268
|
+
return Table(data, colWidths=(None, None, None, None), rowHeights=(10, 10))
|
|
272
269
|
|
|
273
270
|
@property
|
|
274
271
|
def comment_box_as_table(self) -> Table:
|
|
@@ -16,13 +16,12 @@ from ..utils import get_related_or_none
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class StockReport(Report):
|
|
19
|
-
|
|
20
19
|
def __init__(self, queryset: QuerySet[Stock] = None, **kwargs):
|
|
21
20
|
self.queryset = queryset.order_by("from_stock__code", "code")
|
|
22
21
|
self.protocol_name = ResearchProtocolConfig().protocol_title
|
|
23
22
|
super().__init__(**kwargs)
|
|
24
23
|
|
|
25
|
-
def draw_header(self, canvas, doc):
|
|
24
|
+
def draw_header(self, canvas, doc): # noqa: ARG002
|
|
26
25
|
width, height = self.page.get("pagesize")
|
|
27
26
|
canvas.setFontSize(6)
|
|
28
27
|
text_width = stringWidth(self.protocol_name, "Helvetica", 6)
|
|
@@ -72,7 +71,6 @@ class StockReport(Report):
|
|
|
72
71
|
|
|
73
72
|
@property
|
|
74
73
|
def stock_items_as_table(self) -> Table:
|
|
75
|
-
|
|
76
74
|
style = ParagraphStyle(
|
|
77
75
|
name="line_data_medium",
|
|
78
76
|
alignment=TA_CENTER,
|
|
@@ -4,13 +4,12 @@ from datetime import datetime
|
|
|
4
4
|
from decimal import Decimal
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
from uuid import uuid4
|
|
7
|
-
from zoneinfo import ZoneInfo
|
|
8
7
|
|
|
9
8
|
from dateutil.relativedelta import relativedelta
|
|
10
9
|
from django.conf import settings
|
|
11
10
|
from django.core.exceptions import ObjectDoesNotExist
|
|
12
11
|
|
|
13
|
-
from edc_utils import convert_php_dateformat
|
|
12
|
+
from edc_utils.text import convert_php_dateformat
|
|
14
13
|
|
|
15
14
|
from ..exceptions import (
|
|
16
15
|
PrescriptionError,
|
|
@@ -43,9 +42,9 @@ class RefillCreator:
|
|
|
43
42
|
self._next_rx_refill = None
|
|
44
43
|
self._prev_rx_refill = None
|
|
45
44
|
self.refill_identifier = refill_identifier
|
|
46
|
-
self.refill_end_datetime = refill_end_datetime
|
|
45
|
+
self.refill_end_datetime = refill_end_datetime
|
|
47
46
|
self.subject_identifier = subject_identifier
|
|
48
|
-
self.refill_start_datetime = refill_start_datetime
|
|
47
|
+
self.refill_start_datetime = refill_start_datetime
|
|
49
48
|
self.formulation = formulation
|
|
50
49
|
self.dosage_guideline = dosage_guideline
|
|
51
50
|
self.roundup_divisible_by = roundup_divisible_by or 0
|
edc_protocol/validators.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from zoneinfo import ZoneInfo
|
|
3
3
|
|
|
4
|
+
from django.conf import settings
|
|
4
5
|
from django.core.exceptions import ValidationError
|
|
5
|
-
from django.utils import timezone
|
|
6
6
|
|
|
7
|
-
from edc_utils import formatted_datetime
|
|
7
|
+
from edc_utils.text import formatted_datetime
|
|
8
8
|
|
|
9
9
|
from .research_protocol_config import ResearchProtocolConfig
|
|
10
10
|
|
|
@@ -12,14 +12,11 @@ from .research_protocol_config import ResearchProtocolConfig
|
|
|
12
12
|
def date_not_before_study_start(value):
|
|
13
13
|
if value:
|
|
14
14
|
protocol_config = ResearchProtocolConfig()
|
|
15
|
-
|
|
16
|
-
if
|
|
17
|
-
opened = formatted_datetime(
|
|
18
|
-
timezone.localtime(protocol_config.study_open_datetime)
|
|
19
|
-
)
|
|
20
|
-
got = formatted_datetime(timezone.localtime(value_utc))
|
|
15
|
+
dte = datetime(*[*value.timetuple()][0:6], tzinfo=ZoneInfo(settings.TIME_ZONE))
|
|
16
|
+
if dte < protocol_config.study_open_datetime:
|
|
17
|
+
opened = formatted_datetime(protocol_config.study_open_datetime)
|
|
21
18
|
raise ValidationError(
|
|
22
|
-
f"Invalid date. Study opened on {opened}. Got {
|
|
19
|
+
f"Invalid date. Study opened on {opened}. Got {formatted_datetime(dte)}. "
|
|
23
20
|
f"See edc_protocol.AppConfig."
|
|
24
21
|
)
|
|
25
22
|
|
|
@@ -27,13 +24,10 @@ def date_not_before_study_start(value):
|
|
|
27
24
|
def datetime_not_before_study_start(value_datetime):
|
|
28
25
|
if value_datetime:
|
|
29
26
|
protocol_config = ResearchProtocolConfig()
|
|
30
|
-
|
|
31
|
-
if
|
|
32
|
-
opened = formatted_datetime(
|
|
33
|
-
timezone.localtime(protocol_config.study_open_datetime)
|
|
34
|
-
)
|
|
35
|
-
got = formatted_datetime(timezone.localtime(value_utc))
|
|
27
|
+
dte = value_datetime
|
|
28
|
+
if dte < protocol_config.study_open_datetime:
|
|
29
|
+
opened = formatted_datetime(protocol_config.study_open_datetime)
|
|
36
30
|
raise ValidationError(
|
|
37
|
-
f"Invalid date/time. Study opened on {opened}. Got {
|
|
31
|
+
f"Invalid date/time. Study opened on {opened}. Got {formatted_datetime(dte)}."
|
|
38
32
|
f"See edc_protocol.AppConfig."
|
|
39
33
|
)
|
|
@@ -58,7 +58,7 @@ class ReportablesFormValidatorMixin:
|
|
|
58
58
|
try:
|
|
59
59
|
reference_range_evaluator.validate_reportable_fields()
|
|
60
60
|
except NotEvaluated as e:
|
|
61
|
-
self.raise_validation_error({"__all__": str(e)}, INVALID_ERROR)
|
|
61
|
+
self.raise_validation_error({"__all__": str(e)}, INVALID_ERROR, exc=e)
|
|
62
62
|
reference_range_evaluator.validate_results_abnormal_field()
|
|
63
63
|
self.applicable_if(
|
|
64
64
|
YES, field="results_abnormal", field_applicable="results_reportable"
|
|
@@ -43,10 +43,11 @@ class SubjectScreeningFormValidatorMixin:
|
|
|
43
43
|
self._subject_screening = self.subject_screening_model_cls.objects.get(
|
|
44
44
|
screening_identifier=self.screening_identifier
|
|
45
45
|
)
|
|
46
|
-
except ObjectDoesNotExist:
|
|
46
|
+
except ObjectDoesNotExist as e:
|
|
47
47
|
self.raise_validation_error(
|
|
48
48
|
'Complete the "Subject Screening" form before proceeding.',
|
|
49
49
|
error_code="missing_subject_screening",
|
|
50
|
+
exc=e,
|
|
50
51
|
)
|
|
51
52
|
return self._subject_screening
|
|
52
53
|
|
edc_transfer/form_validators.py
CHANGED
|
@@ -6,7 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|
|
6
6
|
from edc_constants.constants import DWTA, OTHER
|
|
7
7
|
from edc_form_validators import FormValidator
|
|
8
8
|
from edc_prn.modelform_mixins import PrnFormValidatorMixin
|
|
9
|
-
from edc_utils import convert_php_dateformat
|
|
9
|
+
from edc_utils.text import convert_php_dateformat
|
|
10
10
|
|
|
11
11
|
from .constants import TRANSFERRED
|
|
12
12
|
|
edc_transfer/model_mixins.py
CHANGED
|
@@ -8,7 +8,7 @@ from edc_constants.choices import YES_NO, YES_NO_UNSURE
|
|
|
8
8
|
from edc_identifier.model_mixins import UniqueSubjectIdentifierFieldMixin
|
|
9
9
|
from edc_model import models as edc_models
|
|
10
10
|
from edc_sites.model_mixins import SiteModelMixin
|
|
11
|
-
from edc_utils import convert_php_dateformat
|
|
11
|
+
from edc_utils.text import convert_php_dateformat
|
|
12
12
|
|
|
13
13
|
from .choices import TRANSFER_INITIATORS
|
|
14
14
|
from .constants import SUBJECT_TRANSFER_ACTION
|
edc_utils/age.py
CHANGED
|
@@ -8,6 +8,8 @@ from dateutil.relativedelta import relativedelta
|
|
|
8
8
|
from django.conf import settings
|
|
9
9
|
from django.utils import timezone
|
|
10
10
|
|
|
11
|
+
from edc_utils.text import formatted_datetime
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class AgeValueError(Exception):
|
|
13
15
|
pass
|
|
@@ -35,23 +37,23 @@ def get_dob(age_in_years: int, now: date | datetime | None = None) -> date:
|
|
|
35
37
|
def age(born: date | datetime, reference_dt: date | datetime) -> relativedelta:
|
|
36
38
|
"""Returns a relative delta.
|
|
37
39
|
|
|
38
|
-
Convert dates
|
|
40
|
+
Convert dates to datetimes in local timezone.
|
|
39
41
|
"""
|
|
40
|
-
if born is None:
|
|
42
|
+
if born is None or reference_dt is None:
|
|
41
43
|
raise AgeValueError("DOB cannot be None")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
rdelta = relativedelta(
|
|
51
|
-
if
|
|
44
|
+
if reference_dt is None:
|
|
45
|
+
raise AgeValueError("Reference cannot be None")
|
|
46
|
+
if not hasattr(born, "date"):
|
|
47
|
+
born = datetime(*[*born.timetuple()][0:6], tzinfo=ZoneInfo(settings.TIME_ZONE))
|
|
48
|
+
if not hasattr(reference_dt, "date"):
|
|
49
|
+
reference_dt = datetime(
|
|
50
|
+
*[*reference_dt.timetuple()][0:6], tzinfo=ZoneInfo(settings.TIME_ZONE)
|
|
51
|
+
)
|
|
52
|
+
rdelta = relativedelta(reference_dt, born)
|
|
53
|
+
if born > reference_dt:
|
|
52
54
|
raise AgeValueError(
|
|
53
|
-
f"Reference date {reference_dt}
|
|
54
|
-
f"
|
|
55
|
+
f"Reference date {formatted_datetime(reference_dt)} precedes DOB "
|
|
56
|
+
f"{formatted_datetime(born)}. Got {rdelta}"
|
|
55
57
|
)
|
|
56
58
|
return rdelta
|
|
57
59
|
|
|
@@ -63,7 +65,7 @@ def formatted_age(
|
|
|
63
65
|
) -> str:
|
|
64
66
|
age_as_str = "?"
|
|
65
67
|
if born:
|
|
66
|
-
tz = tz or
|
|
68
|
+
tz = tz or settings.TIME_ZONE
|
|
67
69
|
born = datetime(*[*born.timetuple()][0:6], tzinfo=ZoneInfo(tz))
|
|
68
70
|
reference_dt = reference_dt or timezone.now()
|
|
69
71
|
age_delta = age(born, reference_dt or timezone.now())
|
edc_utils/date.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from datetime import date, datetime
|
|
4
4
|
from zoneinfo import ZoneInfo
|
|
5
5
|
|
|
6
|
-
from django.
|
|
6
|
+
from django.utils import timezone
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class EdcDatetimeError(Exception):
|
|
@@ -11,21 +11,21 @@ class EdcDatetimeError(Exception):
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_utcnow() -> datetime:
|
|
14
|
-
return
|
|
14
|
+
return timezone.localtime(None, timezone=ZoneInfo("UTC"))
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_utcnow_as_date() -> date:
|
|
18
|
-
return
|
|
18
|
+
return timezone.localtime(None, timezone=ZoneInfo("UTC")).date()
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def to_utc(
|
|
21
|
+
def to_utc(dte: datetime) -> datetime:
|
|
22
22
|
"""Returns UTC datetime from any aware datetime."""
|
|
23
|
-
return
|
|
23
|
+
return timezone.localtime(dte, timezone=ZoneInfo("UTC"))
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def to_local(
|
|
26
|
+
def to_local(dte: datetime) -> datetime:
|
|
27
27
|
"""Returns local datetime from any aware datetime."""
|
|
28
|
-
return
|
|
28
|
+
return timezone.localtime(dte)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def floor_secs(dte) -> datetime:
|
edc_utils/text.py
CHANGED
|
@@ -4,6 +4,7 @@ from datetime import datetime
|
|
|
4
4
|
from zoneinfo import ZoneInfo
|
|
5
5
|
|
|
6
6
|
from django.conf import settings
|
|
7
|
+
from django.utils import timezone
|
|
7
8
|
|
|
8
9
|
safe_allowed_chars = "ABCDEFGHKMNPRTUVWXYZ2346789"
|
|
9
10
|
|
|
@@ -63,9 +64,9 @@ def convert_from_camel(name):
|
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def formatted_datetime(
|
|
66
|
-
|
|
67
|
+
dt: datetime | None,
|
|
67
68
|
php_dateformat: str | None = None,
|
|
68
|
-
tz:
|
|
69
|
+
tz: ZoneInfo | None = None,
|
|
69
70
|
format_as_date: bool | None = None,
|
|
70
71
|
):
|
|
71
72
|
"""Returns a formatted datetime string, localized by default.
|
|
@@ -73,14 +74,14 @@ def formatted_datetime(
|
|
|
73
74
|
format_as_date: does not affect the calculation, just the formatted output.
|
|
74
75
|
"""
|
|
75
76
|
formatted = ""
|
|
76
|
-
if
|
|
77
|
-
|
|
77
|
+
if dt:
|
|
78
|
+
localized_dt = timezone.localtime(dt, timezone=tz)
|
|
78
79
|
if format_as_date:
|
|
79
80
|
php_dateformat = php_dateformat or settings.SHORT_DATE_FORMAT
|
|
80
|
-
formatted =
|
|
81
|
+
formatted = localized_dt.date().strftime(convert_php_dateformat(php_dateformat))
|
|
81
82
|
else:
|
|
82
83
|
php_dateformat = php_dateformat or settings.SHORT_DATETIME_FORMAT
|
|
83
|
-
formatted =
|
|
84
|
+
formatted = localized_dt.strftime(convert_php_dateformat(php_dateformat))
|
|
84
85
|
return formatted
|
|
85
86
|
|
|
86
87
|
|
edc_visit_schedule/exceptions.py
CHANGED
|
@@ -46,10 +46,18 @@ class ScheduledVisitWindowError(Exception):
|
|
|
46
46
|
pass
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
class UnScheduledVisitError(Exception):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
49
53
|
class UnScheduledVisitWindowError(Exception):
|
|
50
54
|
pass
|
|
51
55
|
|
|
52
56
|
|
|
57
|
+
class MissedVisitError(Exception):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
53
61
|
class SiteVisitScheduleError(Exception):
|
|
54
62
|
pass
|
|
55
63
|
|
|
@@ -13,7 +13,7 @@ from edc_identifier.model_mixins import UniqueSubjectIdentifierFieldMixin
|
|
|
13
13
|
from edc_model.validators import datetime_not_future
|
|
14
14
|
from edc_protocol.validators import datetime_not_before_study_start
|
|
15
15
|
from edc_sites.managers import CurrentSiteManager as BaseCurrentSiteManager
|
|
16
|
-
from edc_utils import convert_php_dateformat
|
|
16
|
+
from edc_utils.text import convert_php_dateformat
|
|
17
17
|
|
|
18
18
|
from ..site_visit_schedules import site_visit_schedules
|
|
19
19
|
|
|
@@ -12,7 +12,7 @@ from edc_identifier.model_mixins import UniqueSubjectIdentifierFieldMixin
|
|
|
12
12
|
from edc_model.models import HistoricalRecords
|
|
13
13
|
from edc_model.validators import datetime_not_future
|
|
14
14
|
from edc_protocol.validators import datetime_not_before_study_start
|
|
15
|
-
from edc_utils import convert_php_dateformat
|
|
15
|
+
from edc_utils.text import convert_php_dateformat
|
|
16
16
|
|
|
17
17
|
from ..site_visit_schedules import site_visit_schedules
|
|
18
18
|
|
|
@@ -4,8 +4,6 @@ from datetime import datetime
|
|
|
4
4
|
|
|
5
5
|
from django import forms
|
|
6
6
|
|
|
7
|
-
from edc_utils import to_utc
|
|
8
|
-
|
|
9
7
|
from ..subject_schedule import InvalidOffscheduleDate
|
|
10
8
|
from .visit_schedule_non_crf_modelform_mixin import VisitScheduleNonCrfModelFormMixin
|
|
11
9
|
|
|
@@ -42,7 +40,7 @@ class OffScheduleModelFormMixin(VisitScheduleNonCrfModelFormMixin):
|
|
|
42
40
|
@property
|
|
43
41
|
def offschedule_datetime(self) -> datetime | None:
|
|
44
42
|
if self.offschedule_datetime_field_attr in self.cleaned_data:
|
|
45
|
-
return
|
|
43
|
+
return self.cleaned_data.get(self.offschedule_datetime_field_attr)
|
|
46
44
|
return getattr(self.instance, self.offschedule_datetime_field_attr)
|
|
47
45
|
|
|
48
46
|
@property
|