diff --git a/project/npda/forms/patient_form.py b/project/npda/forms/patient_form.py index 5f9c5ee9..56d7c30e 100644 --- a/project/npda/forms/patient_form.py +++ b/project/npda/forms/patient_form.py @@ -36,18 +36,6 @@ def validate(self, value): class PatientForm(forms.ModelForm): - quarter = forms.ChoiceField( - choices=[ - (1, 1), - (2, 2), - (3, 3), - (4, 4), - ], # Initially empty, will be populated dynamically - required=True, - widget=forms.Select(attrs={"class": SELECT}), - label="Audit Year Quarter", - ) - class Meta: model = Patient fields = [ @@ -61,7 +49,6 @@ class Meta: "death_date", "gp_practice_ods_code", "gp_practice_postcode", - "quarter", ] field_classes = {"nhs_number": NHSNumberField} widgets = { @@ -77,23 +64,8 @@ class Meta: "death_date": DateInput(), "gp_practice_ods_code": forms.TextInput(attrs={"class": TEXT_INPUT}), "gp_practice_postcode": forms.TextInput(attrs={"class": TEXT_INPUT}), - "quarter": forms.Select(), } - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - if self.instance.pk: - # this is a bound form, so we need to get the quarter from the submission related to the instance - Submission = apps.get_model("npda", "Submission") - self.initial["quarter"] = Submission.objects.get( - patients=self.instance - ).quarter - else: - # this is an unbound form, so we need to set the quarter to current quarter - self.initial["quarter"] = retrieve_quarter_for_date( - date_instance=date.today() - ) - def clean_postcode(self): if not self.cleaned_data["postcode"]: raise ValidationError("This field is required") diff --git a/project/npda/forms/visit_form.py b/project/npda/forms/visit_form.py index 08f135a9..cc6da549 100644 --- a/project/npda/forms/visit_form.py +++ b/project/npda/forms/visit_form.py @@ -1,25 +1,19 @@ from django import forms from django.core.exceptions import ValidationError from ...constants.styles import * +from ...constants import * from ..general_functions.validate_dates import validate_date from ..models import Visit + class DateInput(forms.DateInput): - input_type='date' + input_type = "date" + class VisitForm(forms.ModelForm): patient = None - def __init__(self, *args, **kwargs): - self.patient = kwargs['initial'].get('patient') - super(VisitForm, self).__init__(*args, **kwargs) - for field_name, field in self.fields.items(): - model_field = Visit._meta.get_field(field_name) - if hasattr(model_field, 'category'): - field.category = model_field.category - - class Meta: model = Visit fields = [ @@ -92,15 +86,11 @@ class Meta: "coeliac_screen_date": DateInput(), "gluten_free_diet": forms.Select(), "psychological_screening_assessment_date": DateInput(), - "psychological_additional_support_status": forms.Select( - - ), + "psychological_additional_support_status": forms.Select(), "smoking_status": forms.Select(), "smoking_cessation_referral_date": DateInput(), "carbohydrate_counting_level_three_education_date": DateInput(), - "dietician_additional_appointment_offered": forms.Select( - - ), + "dietician_additional_appointment_offered": forms.Select(), "dietician_additional_appointment_date": DateInput(), "flu_immunisation_recommended_date": DateInput(), "ketone_meter_training": forms.Select(), @@ -113,13 +103,13 @@ class Meta: } categories = [ - "Measurements", - "HBA1c", - "Treatment", - "CGM", - "BP", - "Foot Care", - "DECS", + "Measurements", + "HBA1c", + "Treatment", + "CGM", + "BP", + "Foot Care", + "DECS", "ACR", "Cholesterol", "Thyroid", @@ -129,350 +119,614 @@ class Meta: "Dietician", "Sick Day Rules", "Immunisation (flu)", - "Hospital Admission" + "Hospital Admission", ] + def __init__(self, *args, **kwargs): + self.patient = kwargs["initial"].get("patient") + super(VisitForm, self).__init__(*args, **kwargs) + for field_name, field in self.fields.items(): + model_field = Visit._meta.get_field(field_name) + if hasattr(model_field, "category"): + field.category = model_field.category + + """ + Custom clean method for all fields requiring choices + """ + + def clean_smoking_status(self): + data = self.cleaned_data["smoking_status"] + # Convert the list of tuples to a dictionary + smoking_status_dict = dict(SMOKING_STATUS) + + if data is None or data in smoking_status_dict: + return data + else: + options = str(SMOKING_STATUS).strip("[]").replace(")", "").replace("(", "") + raise ValidationError( + f"'{data}' is not a value for 'Smoking Status'. Please select one of {options}." + ) + + def clean_thyroid_treatment_status(self): + data = self.cleaned_data["thyroid_treatment_status"] + # Convert the list of tuples to a dictionary + thyroid_treatment_dict = dict(THYROID_TREATMENT_STATUS) + + if data is None or data in thyroid_treatment_dict: + return data + else: + options = ( + str(THYROID_TREATMENT_STATUS) + .strip("[]") + .replace(")", "") + .replace("(", "") + ) + raise ValidationError( + f"'{data}' is not a value for 'Thyroid Treatment Status'. Please select one of {options}." + ) + + def clean_closed_loop_system(self): + data = self.cleaned_data["closed_loop_system"] + # Convert the list of tuples to a dictionary + closed_loop_system_dict = dict(CLOSED_LOOP_TYPES) + + if data is None or data in closed_loop_system_dict: + return data + else: + options = ( + str(CLOSED_LOOP_TYPES).strip("[]").replace(")", "").replace("(", "") + ) + raise ValidationError( + f"'{data}' is not a value for 'Closed Loop System'. Please select one of {options}." + ) + + def clean_hospital_admission_reason(self): + data = self.cleaned_data["hospital_admission_reason"] + # Convert the list of tuples to a dictionary + hospital_admission_reason_dict = dict(HOSPITAL_ADMISSION_REASONS) + + if data is None or data in hospital_admission_reason_dict: + return data + else: + options = ( + str(HOSPITAL_ADMISSION_REASONS) + .strip("[]") + .replace(")", "") + .replace("(", "") + ) + raise ValidationError( + f"'{data}' is not a value for 'Hospital Admission Reason'. Please select one of {options}." + ) + + def clean_albuminuria_stage(self): + data = self.cleaned_data["albuminuria_stage"] + # Convert the list of tuples to a dictionary + albuminuria_stage_dict = dict(ALBUMINURIA_STAGES) + + if data is None or data in albuminuria_stage_dict: + return data + else: + options = ( + str(ALBUMINURIA_STAGES).strip("[]").replace(")", "").replace("(", "") + ) + raise ValidationError( + f"'{data}' is not a value for 'Albuminuria Stage'. Please select one of {options}." + ) + + def clean_psychological_additional_support_status(self): + data = self.cleaned_data["psychological_additional_support_status"] + # Convert the list of tuples to a dictionary + psychological_additional_support_status_dict = dict(YES_NO_UNKNOWN) + + if data is None or data in psychological_additional_support_status_dict: + return data + else: + options = str(YES_NO_UNKNOWN).strip("[]").replace(")", "").replace("(", "") + raise ValidationError( + f"'{data}' is not a value for 'Psychological Additional Support Status'. Please select one of {options}." + ) + + def clean_dietian_additional_appointment_offered(self): + data = self.cleaned_data["dietician_additional_appointment_offered"] + # Convert the list of tuples to a dictionary + dietitian_additional_appointment_offered_dict = dict(YES_NO_UNKNOWN) + + if data is None or data in dietitian_additional_appointment_offered_dict: + return data + else: + options = str(YES_NO_UNKNOWN).strip("[]").replace(")", "").replace("(", "") + raise ValidationError( + f"'{data}' is not a value for 'Dietician Additional Appointment Offered'. Please select one of {options}." + ) + + def clean_ketone_meter_training(self): + data = self.cleaned_data["ketone_meter_training"] + # Convert the list of tuples to a dictionary + ketone_meter_training_dict = dict(YES_NO_UNKNOWN) + + if data is None or data in ketone_meter_training_dict: + return data + else: + options = str(YES_NO_UNKNOWN).strip("[]").replace(")", "").replace("(", "") + raise ValidationError( + f"'{data}' is not a value for 'Ketone Meter Training'. Please select one of {options}." + ) + + def clean_dka_additional_therapies(self): + data = self.cleaned_data["dka_additional_therapies"] + # Convert the list of tuples to a dictionary + dka_additional_therapies_dict = dict(DKA_ADDITIONAL_THERAPIES) + + if data is None or data in dka_additional_therapies_dict: + return data + else: + options = ( + str(DKA_ADDITIONAL_THERAPIES) + .strip("[]") + .replace(")", "") + .replace("(", "") + ) + raise ValidationError( + f"'{data}' is not a value for 'DKA Additional Therapies'. Please select one of {options}." + ) + + def clean_gluten_free_diet(self): + data = self.cleaned_data["gluten_free_diet"] + # Convert the list of tuples to a dictionary + gluten_free_diet_dict = dict(YES_NO_UNKNOWN) + + if data is None or data in gluten_free_diet_dict: + return data + else: + options = str(YES_NO_UNKNOWN).strip("[]").replace(")", "").replace("(", "") + raise ValidationError( + f"'{data}' is not a value for 'Gluten Free Diet'. Please select one of {options}." + ) + + def clean_hba1c_format(self): + data = self.cleaned_data["hba1c_format"] + # Convert the list of tuples to a dictionary + hba1c_format_dict = dict(HBA1C_FORMATS) + + if data is None or data in hba1c_format_dict: + return data + else: + options = str(HBA1C_FORMATS).strip("[]").replace(")", "").replace("(", "") + raise ValidationError( + f"'{data}' is not a value for 'Hba1c Format'. Please select one of {options}." + ) + + def clean_retinal_screening_result(self): + data = self.cleaned_data["retinal_screening_result"] + # Convert the list of tuples to a dictionary + retinal_screening_result_dict = dict(RETINAL_SCREENING_RESULTS) + + if data is None or data in retinal_screening_result_dict: + return data + else: + options = ( + str(RETINAL_SCREENING_RESULTS) + .strip("[]") + .replace(")", "") + .replace("(", "") + ) + raise ValidationError( + f"'{data}' is not a value for 'Retinal Screening Result'. Please select one of {options}." + ) + + def clean_treatment(self): + data = self.cleaned_data["treatment"] + # Convert the list of tuples to a dictionary + treatment_dict = dict(TREATMENT_TYPES) + + if data is None or data in treatment_dict: + return data + else: + options = str(TREATMENT_TYPES).strip("[]").replace(")", "").replace("(", "") + raise ValidationError( + f"'{data}' is not a value for 'Treatment'. Please select one of {options}." + ) + + def clean_glucose_monitoring(self): + data = self.cleaned_data["glucose_monitoring"] + # Convert the list of tuples to a dictionary + glucose_monitoring_dict = dict(GLUCOSE_MONITORING_TYPES) + + if data is None or data in glucose_monitoring_dict: + return data + else: + options = ( + str(GLUCOSE_MONITORING_TYPES) + .strip("[]") + .replace(")", "") + .replace("(", "") + ) + raise ValidationError( + f"'{data}' is not a value for 'Glucose Monitoring'. Please select one of {options}." + ) + + """ + Custom clean methods for all fields requiring numbers + """ + def clean_height(self): data = self.cleaned_data["height"] if data is not None: if data < 40: - raise ValidationError("Please enter a valid height. Cannot be less than 40cm") + raise ValidationError( + "Please enter a valid height. Cannot be less than 40cm" + ) if data > 240: - raise ValidationError("Please enter a valid height. Cannot be greater than 240cm") + raise ValidationError( + "Please enter a valid height. Cannot be greater than 240cm" + ) return data - + def clean_weight(self): data = self.cleaned_data["height"] if data is not None: if data < 1: - raise ValidationError("Patient Weight (kg)' invalid. Cannot be below 1kg") + raise ValidationError( + "Patient Weight (kg)' invalid. Cannot be below 1kg" + ) if data > 200: - raise ValidationError("Patient Weight (kg)' invalid. Cannot be above 200kg") + raise ValidationError( + "Patient Weight (kg)' invalid. Cannot be above 200kg" + ) return data - + + def clean_systolic_blood_pressure(self): + systolic_blood_pressure = self.cleaned_data["systolic_blood_pressure"] + + if systolic_blood_pressure: + if systolic_blood_pressure < 80: + raise ValidationError( + "Systolic Blood Pressure out of range. Cannot be below 80" + ) + elif systolic_blood_pressure > 240: + raise ValidationError( + "Systolic Blood Pressure out of range. Cannot be above 240" + ) + + def clean_diastolic_blood_pressure(self): + diastolic_blood_pressure = self.cleaned_data["diastolic_blood_pressure"] + + if diastolic_blood_pressure: + if diastolic_blood_pressure < 20: + raise ValidationError( + "Diastolic Blood pressure out of range. Cannot be below 20" + ) + elif diastolic_blood_pressure > 120: + raise ValidationError( + "Diastolic Blood pressure out of range. Cannot be above 120" + ) + + def clean_albumin_creatinine_ratio(self): + albumin_creatinine_ratio = self.cleaned_data["albumin_creatinine_ratio"] + + if albumin_creatinine_ratio: + if albumin_creatinine_ratio < 20: + raise ValidationError( + "Urinary Albumin Level (ACR) out of range. Cannot be below 0" + ) + elif albumin_creatinine_ratio > 50: + raise ValidationError( + "Urinary Albumin Level (ACR) out of range. Cannot be above 50" + ) + + def clean_total_cholesterol(self): + total_cholesterol = self.cleaned_data["total_cholesterol"] + + if total_cholesterol: + if total_cholesterol < 2: + raise ValidationError( + "Total Cholesterol Level (mmol/l) out of range. Cannot be below 2" + ) + elif total_cholesterol > 12: + raise ValidationError( + "Total Cholesterol Level (mmol/l) out of range. Cannot be above 12" + ) + + """ + Custom clean methods for all fields requiring dates + """ + def clean_visit_date(self): - data = self.cleaned_data['visit_date'] + data = self.cleaned_data["visit_date"] valid, error = validate_date( - date_under_examination_field_name='visit_date', - date_under_examination_label_name='Visit/Appointment Date', + date_under_examination_field_name="visit_date", + date_under_examination_label_name="Visit/Appointment Date", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['visit_date'] - + + return self.cleaned_data["visit_date"] def clean_height_weight_observation_date(self): - data = self.cleaned_data['height_weight_observation_date'] + data = self.cleaned_data["height_weight_observation_date"] valid, error = validate_date( - date_under_examination_field_name='height_weight_observation_date', - date_under_examination_label_name='Observation Date (Height and weight)', + date_under_examination_field_name="height_weight_observation_date", + date_under_examination_label_name="Observation Date (Height and weight)", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['height_weight_observation_date'] + + return self.cleaned_data["height_weight_observation_date"] def clean_hba1c_date(self): - data = self.cleaned_data['hba1c_date'] + data = self.cleaned_data["hba1c_date"] valid, error = validate_date( - date_under_examination_field_name='hba1c_date', - date_under_examination_label_name='Observation Date: Hba1c Value', + date_under_examination_field_name="hba1c_date", + date_under_examination_label_name="Observation Date: Hba1c Value", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['hba1c_date'] - - def clean_systolic_blood_pressure(self): - systolic_blood_pressure = self.cleaned_data['systolic_blood_pressure'] - - if systolic_blood_pressure: - if systolic_blood_pressure < 80: - raise ValidationError("Systolic Blood Pressure out of range. Cannot be below 80") - elif systolic_blood_pressure > 240: - raise ValidationError("Systolic Blood Pressure out of range. Cannot be above 240") - - def clean_diastolic_blood_pressure(self): - diastolic_blood_pressure = self.cleaned_data['diastolic_blood_pressure'] - if diastolic_blood_pressure: - if diastolic_blood_pressure < 20: - raise ValidationError("Diastolic Blood pressure out of range. Cannot be below 20") - elif diastolic_blood_pressure > 120: - raise ValidationError("Diastolic Blood pressure out of range. Cannot be above 120") - - def clean_albumin_creatinine_ratio(self): - albumin_creatinine_ratio = self.cleaned_data['albumin_creatinine_ratio'] - - if albumin_creatinine_ratio: - if albumin_creatinine_ratio < 20: - raise ValidationError("Urinary Albumin Level (ACR) out of range. Cannot be below 0") - elif albumin_creatinine_ratio > 50: - raise ValidationError("Urinary Albumin Level (ACR) out of range. Cannot be above 50") - - def clean_total_cholesterol(self): - total_cholesterol = self.cleaned_data['total_cholesterol'] - - if total_cholesterol: - if total_cholesterol < 2: - raise ValidationError("Total Cholesterol Level (mmol/l) out of range. Cannot be below 2") - elif total_cholesterol > 12: - raise ValidationError("Total Cholesterol Level (mmol/l) out of range. Cannot be above 12") + return self.cleaned_data["hba1c_date"] def clean_blood_pressure_observation_date(self): - data = self.cleaned_data['blood_pressure_observation_date'] + data = self.cleaned_data["blood_pressure_observation_date"] valid, error = validate_date( - date_under_examination_field_name='blood_pressure_observation_date', - date_under_examination_label_name='Observation Date (Blood Pressure)', + date_under_examination_field_name="blood_pressure_observation_date", + date_under_examination_label_name="Observation Date (Blood Pressure)", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['blood_pressure_observation_date'] + + return self.cleaned_data["blood_pressure_observation_date"] def clean_foot_examination_observation_date(self): - data = self.cleaned_data['foot_examination_observation_date'] + data = self.cleaned_data["foot_examination_observation_date"] valid, error = validate_date( - date_under_examination_field_name='foot_examination_observation_date', - date_under_examination_label_name='Foot Assessment / Examination Date', + date_under_examination_field_name="foot_examination_observation_date", + date_under_examination_label_name="Foot Assessment / Examination Date", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['foot_examination_observation_date'] + + return self.cleaned_data["foot_examination_observation_date"] def clean_retinal_screening_observation_date(self): - data = self.cleaned_data['retinal_screening_observation_date'] + data = self.cleaned_data["retinal_screening_observation_date"] valid, error = validate_date( - date_under_examination_field_name='retinal_screening_observation_date', + date_under_examination_field_name="retinal_screening_observation_date", date_under_examination=data, - date_under_examination_label_name='Retinal Screening date', + date_under_examination_label_name="Retinal Screening date", date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['retinal_screening_observation_date'] + + return self.cleaned_data["retinal_screening_observation_date"] def clean_albumin_creatinine_ratio_date(self): - data = self.cleaned_data['albumin_creatinine_ratio_date'] + data = self.cleaned_data["albumin_creatinine_ratio_date"] valid, error = validate_date( - date_under_examination_field_name='albumin_creatinine_ratio_date', - date_under_examination_label_name='Observation Date: Urinary Albumin Level', + date_under_examination_field_name="albumin_creatinine_ratio_date", + date_under_examination_label_name="Observation Date: Urinary Albumin Level", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['albumin_creatinine_ratio_date'] + + return self.cleaned_data["albumin_creatinine_ratio_date"] def clean_total_cholesterol_date(self): - data = self.cleaned_data['total_cholesterol_date'] + data = self.cleaned_data["total_cholesterol_date"] valid, error = validate_date( - date_under_examination_field_name='total_cholesterol_date', - date_under_examination_label_name='Observation Date: Total Cholesterol Level', + date_under_examination_field_name="total_cholesterol_date", + date_under_examination_label_name="Observation Date: Total Cholesterol Level", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['total_cholesterol_date'] + + return self.cleaned_data["total_cholesterol_date"] def clean_thyroid_function_date(self): - data = self.cleaned_data['thyroid_function_date'] + data = self.cleaned_data["thyroid_function_date"] valid, error = validate_date( - date_under_examination_field_name='thyroid_function_date', - date_under_examination_label_name='Observation Date: Thyroid Function', + date_under_examination_field_name="thyroid_function_date", + date_under_examination_label_name="Observation Date: Thyroid Function", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['thyroid_function_date'] + + return self.cleaned_data["thyroid_function_date"] def clean_coeliac_screen_date(self): - data = self.cleaned_data['coeliac_screen_date'] + data = self.cleaned_data["coeliac_screen_date"] valid, error = validate_date( - date_under_examination_field_name='coeliac_screen_date', - date_under_examination_label_name='Observation Date: Coeliac Disease Screening', + date_under_examination_field_name="coeliac_screen_date", + date_under_examination_label_name="Observation Date: Coeliac Disease Screening", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['coeliac_screen_date'] + + return self.cleaned_data["coeliac_screen_date"] def clean_psychological_screening_assessment_date(self): - data = self.cleaned_data['psychological_screening_assessment_date'] + data = self.cleaned_data["psychological_screening_assessment_date"] valid, error = validate_date( - date_under_examination_field_name='psychological_screening_assessment_date', - date_under_examination_label_name='Observation Date - Psychological Screening Assessment', + date_under_examination_field_name="psychological_screening_assessment_date", + date_under_examination_label_name="Observation Date - Psychological Screening Assessment", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['psychological_screening_assessment_date'] + + return self.cleaned_data["psychological_screening_assessment_date"] def clean_smoking_cessation_referral_date(self): - data = self.cleaned_data['smoking_cessation_referral_date'] + data = self.cleaned_data["smoking_cessation_referral_date"] valid, error = validate_date( - date_under_examination_field_name='smoking_cessation_referral_date', - date_under_examination_label_name='Date of offer of referral to smoking cessation service (if patient is a current smoker)', + date_under_examination_field_name="smoking_cessation_referral_date", + date_under_examination_label_name="Date of offer of referral to smoking cessation service (if patient is a current smoker)", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['smoking_cessation_referral_date'] + + return self.cleaned_data["smoking_cessation_referral_date"] def clean_carbohydrate_counting_level_three_education_date(self): - data = self.cleaned_data['carbohydrate_counting_level_three_education_date'] + data = self.cleaned_data["carbohydrate_counting_level_three_education_date"] valid, error = validate_date( - date_under_examination_field_name='carbohydrate_counting_level_three_education_date', - date_under_examination_label_name='Date of Level 3 carbohydrate counting education received', + date_under_examination_field_name="carbohydrate_counting_level_three_education_date", + date_under_examination_label_name="Date of Level 3 carbohydrate counting education received", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['carbohydrate_counting_level_three_education_date'] + + return self.cleaned_data["carbohydrate_counting_level_three_education_date"] def clean_dietician_additional_appointment_date(self): - data = self.cleaned_data['dietician_additional_appointment_date'] + data = self.cleaned_data["dietician_additional_appointment_date"] valid, error = validate_date( - date_under_examination_field_name='dietician_additional_appointment_date', - date_under_examination_label_name='Date of additional appointment with dietitian', + date_under_examination_field_name="dietician_additional_appointment_date", + date_under_examination_label_name="Date of additional appointment with dietitian", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['dietician_additional_appointment_date'] + + return self.cleaned_data["dietician_additional_appointment_date"] def clean_flu_immunisation_recommended_date(self): - data = self.cleaned_data['flu_immunisation_recommended_date'] + data = self.cleaned_data["flu_immunisation_recommended_date"] valid, error = validate_date( - date_under_examination_field_name='flu_immunisation_recommended_date', - date_under_examination_label_name='Date that influenza immunisation was recommended', + date_under_examination_field_name="flu_immunisation_recommended_date", + date_under_examination_label_name="Date that influenza immunisation was recommended", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['flu_immunisation_recommended_date'] + + return self.cleaned_data["flu_immunisation_recommended_date"] def clean_sick_day_rules_training_date(self): - data = self.cleaned_data['sick_day_rules_training_date'] + data = self.cleaned_data["sick_day_rules_training_date"] valid, error = validate_date( - date_under_examination_field_name='sick_day_rules_training_date', + date_under_examination_field_name="sick_day_rules_training_date", date_under_examination_label_name="Date of provision of advice ('sick-day rules') about managing diabetes during intercurrent illness or episodes of hyperglycaemia", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['sick_day_rules_training_date'] + + return self.cleaned_data["sick_day_rules_training_date"] def clean_hospital_admission_date(self): - data = self.cleaned_data['hospital_admission_date'] + data = self.cleaned_data["hospital_admission_date"] valid, error = validate_date( - date_under_examination_field_name='hospital_admission_date', - date_under_examination_label_name='Start date (Hospital Provider Spell)', + date_under_examination_field_name="hospital_admission_date", + date_under_examination_label_name="Start date (Hospital Provider Spell)", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['hospital_admission_date'] + + return self.cleaned_data["hospital_admission_date"] def clean_hospital_discharge_date(self): - data = self.cleaned_data['hospital_discharge_date'] + data = self.cleaned_data["hospital_discharge_date"] valid, error = validate_date( - date_under_examination_field_name='hospital_discharge_date', - date_under_examination_label_name='Discharge date (Hospital provider spell', + date_under_examination_field_name="hospital_discharge_date", + date_under_examination_label_name="Discharge date (Hospital provider spell", date_under_examination=data, date_of_birth=self.patient.date_of_birth, date_of_diagnosis=self.patient.diagnosis_date, - date_of_death=self.patient.death_date + date_of_death=self.patient.death_date, ) - if valid==False: + if valid == False: raise ValidationError(error) - - return self.cleaned_data['hospital_discharge_date'] + + return self.cleaned_data["hospital_discharge_date"] def clean(self): cleaned_data = super().clean() - hba1c_value = cleaned_data['hba1c'] - hba1c_format = cleaned_data['hba1c_format'] + hba1c_value = cleaned_data["hba1c"] + hba1c_format = cleaned_data["hba1c_format"] if hba1c_value: if hba1c_format == 1: # mmol/mol if hba1c_value < 20: - raise ValidationError("Hba1c Value out of range (mmol/mol). Cannot be below 20") + raise ValidationError( + "Hba1c Value out of range (mmol/mol). Cannot be below 20" + ) elif hba1c_value > 195: - raise ValidationError("Hba1c Value out of range (mmol/mol). Cannot be above 195") + raise ValidationError( + "Hba1c Value out of range (mmol/mol). Cannot be above 195" + ) elif hba1c_format == 2: # % if hba1c_value < 3: - raise ValidationError("Hba1c Value out of range (%). Cannot be below 3") + raise ValidationError( + "Hba1c Value out of range (%). Cannot be below 3" + ) elif hba1c_value > 20: - raise ValidationError("Hba1c Value out of range (%). Cannot be above 20") \ No newline at end of file + raise ValidationError( + "Hba1c Value out of range (%). Cannot be above 20" + ) + + return cleaned_data diff --git a/project/npda/general_functions/csv_upload.py b/project/npda/general_functions/csv_upload.py index 71cc9ad6..bb29d038 100644 --- a/project/npda/general_functions/csv_upload.py +++ b/project/npda/general_functions/csv_upload.py @@ -17,23 +17,7 @@ # RCPCH imports from ...constants import ( - CSV_HEADINGS, ALL_DATES, - SEX_TYPE, - ETHNICITIES, - DIABETES_TYPES, - LEAVE_PDU_REASONS, - HOSPITAL_ADMISSION_REASONS, - TREATMENT_TYPES, - CLOSED_LOOP_TYPES, - GLUCOSE_MONITORING_TYPES, - HBA1C_FORMATS, - ALBUMINURIA_STAGES, - RETINAL_SCREENING_RESULTS, - THYROID_TREATMENT_STATUS, - SMOKING_STATUS, - YES_NO_UNKNOWN, - DKA_ADDITIONAL_THERAPIES, ) from .validate_postcode import validate_postcode @@ -47,9 +31,7 @@ logger = logging.getLogger(__name__) -def csv_upload( - user, csv_file=None, organisation_ods_code=None, pdu_pz_code=None, quarter=None -): +def csv_upload(user, csv_file=None, organisation_ods_code=None, pdu_pz_code=None): """ Processes standardised NPDA csv file and persists results in NPDA tables @@ -67,30 +49,25 @@ def csv_upload( csv_file, parse_dates=ALL_DATES, dayfirst=True, date_format="%d/%m/%Y" ) - if quarter is None: - # quarter can be passed in as a parameter. if not, retrieve the current quarter - quarter = retrieve_quarter_for_date(date_instance=date.today()) - # get the PDU object pdu, created = PaediatricDiabetesUnit.objects.get_or_create( pz_code=pdu_pz_code, ods_code=organisation_ods_code ) - # Set previous quarter to inactive + # Set previous submission to inactive Submission.objects.filter( paediatric_diabetes_unit__pz_code=pdu.pz_code, audit_year=date.today().year, - quarter=quarter, ).update(submission_active=False) - # Create new submission for the quarter - not it is not possible to have more than one submission per quarter. + # Create new submission for the audit year # It is not possble to create submissions in years other than the current year - new_cohort = Submission.objects.create( + new_submission = Submission.objects.create( paediatric_diabetes_unit=pdu, audit_year=date.today().year, - quarter=quarter, submission_date=timezone.now(), submission_by=user, # user is the user who is logged in. Passed in as a parameter + submission_active=True, ) def csv_value_to_model_value(model_field, value): @@ -105,6 +82,10 @@ def csv_value_to_model_value(model_field, value): if isinstance(value, pd.Timestamp): return value.to_pydatetime().date() + if model_field.choices: + # If the model field has choices, we need to convert the value to the correct type otherwise 1, 2 will be saved as booleans + return model_field.to_python(value) + return value def row_to_dict(row, model, mapping): @@ -142,12 +123,9 @@ def validate_patient_using_form(row): "death_date": "Death Date", }, ) - # TODO MRB: check we Validate gp practice ods code form = PatientForm(fields) - assign_original_row_indices_to_errors(form, row) - return form def validate_visit_using_form(patient, row): @@ -197,7 +175,9 @@ def validate_visit_using_form(patient, row): }, ) - form = VisitForm(fields, initial={"patient": patient}) + fields["patient"] = patient + + form = VisitForm(data=fields, initial=fields) assign_original_row_indices_to_errors(form, row) @@ -215,7 +195,8 @@ def validate_rows(rows): patient_form = validate_patient_using_form(first_row) visits = rows.apply( - lambda row: validate_visit_using_form(patient_form.instance, row), axis=1 + lambda row: validate_visit_using_form(patient_form.instance, row), + axis=1, ) return (patient_form, transfer_fields, visits) @@ -245,9 +226,11 @@ def has_error_that_would_fail_save(errors): def create_instance(model, form): # We want to retain fields even if they're invalid so that we can edit them in the UI # Use the field value from cleaned_data, falling back to data if it's not there - data = form.data | form.cleaned_data + if form.is_valid(): + data = form.cleaned_data + else: + data = form.data instance = model(**data) - instance.is_valid = form.is_valid() instance.errors = ( None if form.is_valid() else form.errors.get_json_data(escape_html=True) @@ -272,21 +255,29 @@ def create_instance(model, form): errors_to_return = errors_to_return | gather_errors(visit_form) if not has_error_that_would_fail_save(errors_to_return): - transfer = Transfer.objects.create(**transfer_fields) - patient = create_instance(Patient, patient_form) - - patient.transfer = transfer patient.save() - new_cohort.patients.add(patient) + # add the patient to a new Transfer instance + transfer_fields["paediatric_diabetes_unit"] = pdu + transfer_fields["patient"] = patient + Transfer.objects.create(**transfer_fields) + + new_submission.patients.add(patient) for visit_form in visits: visit = create_instance(Visit, visit_form) visit.patient = patient visit.save() - new_cohort.save() + new_submission.save() + + # delete the previous submission + Submission.objects.filter( + paediatric_diabetes_unit__pz_code=pdu.pz_code, + audit_year=date.today().year, + submission_active=False, + ).delete() if errors_to_return: raise ValidationError(errors_to_return) @@ -309,11 +300,10 @@ def csv_summarise(csv_file): dataframe["NHS Number"].fillna("").apply(lambda x: x.replace(" ", "")).unique() ) count_of_records_per_nhs_number = dataframe["NHS Number"].value_counts() - matching_patients_in_current_quarter = Patient.objects.filter( + matching_patients_in_current_audit_year = Patient.objects.filter( nhs_number__in=list(unique_nhs_numbers_no_spaces), submissions__submission_active=True, submissions__audit_year=date.today().year, - submissions__quarter=retrieve_quarter_for_date(date_instance=date.today()), ).count() summary = { @@ -322,7 +312,7 @@ def csv_summarise(csv_file): "count_of_records_per_nhs_number": list( count_of_records_per_nhs_number.items() ), - "matching_patients_in_current_quarter": matching_patients_in_current_quarter, + "matching_patients_in_current_audit_year": matching_patients_in_current_audit_year, } return summary diff --git a/project/npda/migrations/0003_alter_submission_options_remove_submission_quarter.py b/project/npda/migrations/0003_alter_submission_options_remove_submission_quarter.py new file mode 100644 index 00000000..5c8b22b5 --- /dev/null +++ b/project/npda/migrations/0003_alter_submission_options_remove_submission_quarter.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1 on 2024-08-10 11:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("npda", "0002_alter_patient_nhs_number_alter_patient_postcode"), + ] + + operations = [ + migrations.AlterModelOptions( + name="submission", + options={ + "ordering": ("audit_year",), + "verbose_name": "Submission", + "verbose_name_plural": "Submissions", + }, + ), + migrations.RemoveField( + model_name="submission", + name="quarter", + ), + ] diff --git a/project/npda/models/submission.py b/project/npda/models/submission.py index e0843e98..cc0f3ac3 100644 --- a/project/npda/models/submission.py +++ b/project/npda/models/submission.py @@ -21,13 +21,6 @@ class Submission(models.Model): help_text="Year the audit is being conducted", ) - quarter = models.IntegerField( - "Quarter", - blank=False, - null=False, - help_text="The quarter in the audit year of the patient", - ) - submission_date = models.DateTimeField( "Submission date", help_text="Date the submission was created", @@ -53,9 +46,9 @@ class Submission(models.Model): ) def __str__(self) -> str: - return f"{self.audit_year} ({self.quarter}), {self.patients.count()} patients" + return f"{self.audit_year}, {self.patients.count()} patients" class Meta: verbose_name = "Submission" verbose_name_plural = "Submissions" - ordering = ("audit_year", "quarter") + ordering = ("audit_year",) diff --git a/project/npda/templates/partials/patient_table.html b/project/npda/templates/partials/patient_table.html index 4df16c36..0b6ce209 100644 --- a/project/npda/templates/partials/patient_table.html +++ b/project/npda/templates/partials/patient_table.html @@ -12,7 +12,7 @@