From 14bcd4fdf77314c7b9b5f80bc15cc5d90845c7fe Mon Sep 17 00:00:00 2001 From: Carlos Vega <carlos.vega@lih.lu> Date: Tue, 24 Oct 2023 15:24:37 +0200 Subject: [PATCH] solved other pylint issues, added ignore pragmas where considered, added two more to the list of globally ignored checks E1131 and E1131 --- .pylintrc | 2 + smash/web/api_views/subject.py | 551 +++++++++++------- smash/web/forms/study_subject_forms.py | 236 ++++---- smash/web/forms/voucher_forms.py | 103 ++-- .../web/importer/csv_subject_import_reader.py | 2 +- smash/web/importer/importer_cron_job.py | 75 ++- smash/web/migrations/0001_version-1-0-0.py | 5 +- .../0032_configurationitem_email_items.py | 4 +- .../migrations/0061_remove_subject_country.py | 16 +- .../web/migrations/0064_auto_20171127_0954.py | 12 +- smash/web/migrations/0066_subject.py | 2 +- .../0082_studysubjectlist_visits.py | 8 +- .../migrations/0086_unfinished_visit_list.py | 2 +- .../0106_approaching_post_mail_list_update.py | 2 +- .../web/migrations/0114_auto_20180611_0950.py | 9 +- .../0158_configurationitem_email_items.py | 4 +- ...onfigurationitem_email_items_for_redcap.py | 4 +- ...onfigurationitem_email_items_for_redcap.py | 4 +- .../0165_configurationitem_email_virus.py | 4 +- .../web/migrations/0166_auto_20200423_1457.py | 4 +- .../migrations/0168_rename_radcap_field.py | 4 +- .../0171_configurationitem_serology.py | 4 +- .../web/migrations/0173_auto_20201105_1142.py | 20 +- .../web/migrations/0174_auto_20201105_1157.py | 12 +- .../web/migrations/0178_auto_20201116_1250.py | 1 + .../web/migrations/0183_auto_20201126_1154.py | 58 +- ...4_migrate_subject_type_to_new_structure.py | 68 ++- .../web/migrations/0212_auto_20231024_1238.py | 27 +- smash/web/officeAvailability.py | 6 +- smash/web/templatetags/filters.py | 24 +- smash/web/tests/models/test_study_subject.py | 137 +++-- smash/web/tests/view/test_privacy_notice.py | 36 +- smash/web/views/subject.py | 2 +- smash/web/views/virus_mail.py | 71 ++- 34 files changed, 871 insertions(+), 648 deletions(-) diff --git a/.pylintrc b/.pylintrc index 501b838b..003bd7d1 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,6 +2,8 @@ load-plugins=pylint.extensions.no_self_use disable= + E1131, # unsupported-binary-operation + R1713, # consider-using-join C0114, # missing-module-docstring C0115, # missing-class-docstring C0116, # missing-function-docstring diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py index 27cfcaa7..808cf020 100644 --- a/smash/web/api_views/subject.py +++ b/smash/web/api_views/subject.py @@ -9,18 +9,53 @@ from django.db.models import Q from django.http import JsonResponse, HttpRequest, HttpResponseNotAllowed, HttpResponse from django.urls import reverse -from web.api_views.serialization_utils import str_to_yes_no_null, bool_to_yes_no, flying_team_to_str, location_to_str, \ - add_column, serialize_date, serialize_datetime, get_filters_for_data_table_request -from web.models import ConfigurationItem, StudySubject, Visit, Appointment, Subject, SubjectColumns, StudyColumns, \ - Study, ContactAttempt, SubjectType -from web.models.constants import GLOBAL_STUDY_ID, VISIT_SHOW_VISIT_NUMBER_FROM_ZERO, \ - CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, \ - CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_FILE +from web.api_views.serialization_utils import ( + str_to_yes_no_null, + bool_to_yes_no, + flying_team_to_str, + location_to_str, + add_column, + serialize_date, + serialize_datetime, + get_filters_for_data_table_request, +) +from web.models import ( + ConfigurationItem, + StudySubject, + Visit, + Appointment, + Subject, + SubjectColumns, + StudyColumns, + Study, + ContactAttempt, + SubjectType, +) +from web.models.constants import ( + GLOBAL_STUDY_ID, + VISIT_SHOW_VISIT_NUMBER_FROM_ZERO, + CUSTOM_FIELD_TYPE_TEXT, + CUSTOM_FIELD_TYPE_BOOLEAN, + CUSTOM_FIELD_TYPE_INTEGER, + CUSTOM_FIELD_TYPE_DOUBLE, + CUSTOM_FIELD_TYPE_DATE, + CUSTOM_FIELD_TYPE_SELECT_LIST, + CUSTOM_FIELD_TYPE_FILE, +) from web.models.custom_data.custom_study_subject_field import get_study_subject_field_id, CustomStudySubjectField -from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \ - StudySubjectList, SUBJECT_LIST_VOUCHER_EXPIRY -from web.views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder, get_today_midnight_date, \ - get_subjects_with_almost_expired_vouchers +from web.models.study_subject_list import ( + SUBJECT_LIST_GENERIC, + SUBJECT_LIST_NO_VISIT, + SUBJECT_LIST_REQUIRE_CONTACT, + StudySubjectList, + SUBJECT_LIST_VOUCHER_EXPIRY, +) +from web.views.notifications import ( + get_subjects_with_no_visit, + get_subjects_with_reminder, + get_today_midnight_date, + get_subjects_with_almost_expired_vouchers, +) from web.views.view_utils import e500_error logger = logging.getLogger(__name__) @@ -28,18 +63,14 @@ logger = logging.getLogger(__name__) # noinspection PyUnusedLocal def cities(request): - result_subjects = Subject.objects.filter(city__isnull=False).values_list('city').distinct() - return JsonResponse({ - "cities": [x[0] for x in result_subjects] - }) + result_subjects = Subject.objects.filter(city__isnull=False).values_list("city").distinct() + return JsonResponse({"cities": [x[0] for x in result_subjects]}) # noinspection PyUnusedLocal def referrals(request): - result_subjects = StudySubject.objects.filter(referral__isnull=False).values_list('referral').distinct() - return JsonResponse({ - "referrals": [x[0] for x in result_subjects] - }) + result_subjects = StudySubject.objects.filter(referral__isnull=False).values_list("referral").distinct() + return JsonResponse({"referrals": [x[0] for x in result_subjects]}) # noinspection PyUnusedLocal @@ -69,15 +100,28 @@ def get_subject_columns(request: HttpRequest, subject_list_type: str) -> JsonRes add_column(result, "Contact on", "datetime_contact_reminder", study_subject_columns, None, study.columns) add_column(result, "Last contact attempt", "last_contact_attempt", study_subject_list, None) add_column(result, "Referred by", "referral", study_subject_columns, "string_filter", study.columns) - add_column(result, "Health partner name", "health_partner_first_name", None, "string_filter", - add_param=study.columns.health_partner, - visible_param=study_subject_columns.health_partner) - add_column(result, "Health partner last name", "health_partner_last_name", None, "string_filter", - add_param=study.columns.health_partner, - visible_param=study_subject_columns.health_partner) + add_column( + result, + "Health partner name", + "health_partner_first_name", + None, + "string_filter", + add_param=study.columns.health_partner, + visible_param=study_subject_columns.health_partner, + ) + add_column( + result, + "Health partner last name", + "health_partner_last_name", + None, + "string_filter", + add_param=study.columns.health_partner, + visible_param=study_subject_columns.health_partner, + ) add_column(result, "Location", "default_location", study_subject_columns, "location_filter", study.columns) - add_column(result, "Flying team location", "flying_team", study_subject_columns, "flying_team_filter", - study.columns) + add_column( + result, "Flying team location", "flying_team", study_subject_columns, "flying_team_filter", study.columns + ) add_column(result, "Deceased", "dead", subject_columns, "yes_no_filter") add_column(result, "Resigned", "resigned", study_subject_columns, "yes_no_filter", study.columns) add_column(result, "Endpoint Reached", "endpoint_reached", study_subject_columns, "yes_no_filter", study.columns) @@ -87,8 +131,13 @@ def get_subject_columns(request: HttpRequest, subject_list_type: str) -> JsonRes add_column(result, "Next of kin address", "next_of_kin_address", subject_columns, "string_filter") add_column(result, "Excluded", "excluded", study_subject_columns, "yes_no_filter", study.columns) add_column(result, "Info sent", "information_sent", study_subject_columns, "yes_no_filter", study.columns) - add_column(result, "Default Written Communication Language", - "default_written_communication_language", subject_columns, "language_filter") + add_column( + result, + "Default Written Communication Language", + "default_written_communication_language", + subject_columns, + "language_filter", + ) visit_from_zero = ConfigurationItem.objects.get(type=VISIT_SHOW_VISIT_NUMBER_FROM_ZERO).value # True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. @@ -101,56 +150,70 @@ def get_subject_columns(request: HttpRequest, subject_list_type: str) -> JsonRes for custom_study_subject_field in study.customstudysubjectfield_set.all(): visible = study_subject_columns.is_custom_field_visible(custom_study_subject_field) if custom_study_subject_field.type == CUSTOM_FIELD_TYPE_TEXT: - add_column(result, - custom_study_subject_field.name, - get_study_subject_field_id(custom_study_subject_field), - study_subject_columns, - "string_filter", - visible_param=visible) + add_column( + result, + custom_study_subject_field.name, + get_study_subject_field_id(custom_study_subject_field), + study_subject_columns, + "string_filter", + visible_param=visible, + ) elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_BOOLEAN: - add_column(result, - custom_study_subject_field.name, - get_study_subject_field_id(custom_study_subject_field), - study_subject_columns, - "yes_no_filter", - visible_param=visible) + add_column( + result, + custom_study_subject_field.name, + get_study_subject_field_id(custom_study_subject_field), + study_subject_columns, + "yes_no_filter", + visible_param=visible, + ) elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_INTEGER: - add_column(result, - custom_study_subject_field.name, - get_study_subject_field_id(custom_study_subject_field), - study_subject_columns, - None, - sortable=False, - visible_param=visible) + add_column( + result, + custom_study_subject_field.name, + get_study_subject_field_id(custom_study_subject_field), + study_subject_columns, + None, + sortable=False, + visible_param=visible, + ) elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_DOUBLE: - add_column(result, - custom_study_subject_field.name, - get_study_subject_field_id(custom_study_subject_field), - study_subject_columns, - None, - sortable=False, - visible_param=visible) + add_column( + result, + custom_study_subject_field.name, + get_study_subject_field_id(custom_study_subject_field), + study_subject_columns, + None, + sortable=False, + visible_param=visible, + ) elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_DATE: - add_column(result, - custom_study_subject_field.name, - get_study_subject_field_id(custom_study_subject_field), - study_subject_columns, - None, - visible_param=visible) + add_column( + result, + custom_study_subject_field.name, + get_study_subject_field_id(custom_study_subject_field), + study_subject_columns, + None, + visible_param=visible, + ) elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_SELECT_LIST: - add_column(result, - custom_study_subject_field.name, - get_study_subject_field_id(custom_study_subject_field), - study_subject_columns, - 'select_filter:' + custom_study_subject_field.possible_values, - visible_param=visible) + add_column( + result, + custom_study_subject_field.name, + get_study_subject_field_id(custom_study_subject_field), + study_subject_columns, + "select_filter:" + custom_study_subject_field.possible_values, + visible_param=visible, + ) elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_FILE: - add_column(result, - custom_study_subject_field.name, - get_study_subject_field_id(custom_study_subject_field), - study_subject_columns, - 'select_filter:N/A;Available', - visible_param=visible) + add_column( + result, + custom_study_subject_field.name, + get_study_subject_field_id(custom_study_subject_field), + study_subject_columns, + "select_filter:N/A;Available", + visible_param=visible, + ) else: raise NotImplementedError @@ -158,8 +221,9 @@ def get_subject_columns(request: HttpRequest, subject_list_type: str) -> JsonRes for one_based_idx, visit_number in enumerate(visit_numbers, 1): visit_key = f"visit_{one_based_idx}" # always starts in 1 - add_column(result, f"Visit {visit_number}", visit_key, None, - "visit_filter", visible_param=study_subject_list.visits) + add_column( + result, f"Visit {visit_number}", visit_key, None, "visit_filter", visible_param=study_subject_list.visits + ) return JsonResponse({"columns": result}) @@ -179,8 +243,8 @@ def get_subjects(request, list_type): def order_by_visit(subjects_to_be_ordered, order_f, visit_number): return subjects_to_be_ordered.annotate( - sort_visit_date=Min(Case(When(visit__visit_number=visit_number, - then='visit__datetime_begin')))).order_by(order_f('sort_visit_date')) + sort_visit_date=Min(Case(When(visit__visit_number=visit_number, then="visit__datetime_begin"))) + ).order_by(order_f("sort_visit_date")) def get_subjects_order(subjects_to_be_ordered: QuerySet, order_column, order_direction, column_filters=None): @@ -188,88 +252,101 @@ def get_subjects_order(subjects_to_be_ordered: QuerySet, order_column, order_dir column_filters = {} result = subjects_to_be_ordered if order_direction == "asc": + def order_f(x): return F(x).asc(nulls_first=True) + else: + def order_f(x): return F(x).desc(nulls_last=True) + if order_column is None: logger.warning("Column cannot be null") elif order_column == "first_name": - result = subjects_to_be_ordered.order_by(order_f('subject__first_name')) + result = subjects_to_be_ordered.order_by(order_f("subject__first_name")) elif order_column == "last_name": - result = subjects_to_be_ordered.order_by(order_f('subject__last_name')) + result = subjects_to_be_ordered.order_by(order_f("subject__last_name")) elif order_column == "address": - result = subjects_to_be_ordered.order_by(order_f('subject__address')) + result = subjects_to_be_ordered.order_by(order_f("subject__address")) elif order_column == "next_of_kin_name": - result = subjects_to_be_ordered.order_by(order_f('subject__next_of_kin_name')) + result = subjects_to_be_ordered.order_by(order_f("subject__next_of_kin_name")) elif order_column == "next_of_kin_phone": - result = subjects_to_be_ordered.order_by(order_f('subject__next_of_kin_phone')) + result = subjects_to_be_ordered.order_by(order_f("subject__next_of_kin_phone")) elif order_column == "next_of_kin_address": - result = subjects_to_be_ordered.order_by(order_f('subject__next_of_kin_address')) + result = subjects_to_be_ordered.order_by(order_f("subject__next_of_kin_address")) elif order_column == "nd_number": - result = subjects_to_be_ordered.order_by(order_f('nd_number')) + result = subjects_to_be_ordered.order_by(order_f("nd_number")) elif order_column == "referral": - result = subjects_to_be_ordered.order_by(order_f('referral')) + result = subjects_to_be_ordered.order_by(order_f("referral")) elif order_column == "default_written_communication_language": - result = subjects_to_be_ordered.order_by(order_f('subject__default_written_communication_language__name')) + result = subjects_to_be_ordered.order_by(order_f("subject__default_written_communication_language__name")) elif order_column == "phone_number": - result = subjects_to_be_ordered.order_by(order_f('subject__phone_number')) + result = subjects_to_be_ordered.order_by(order_f("subject__phone_number")) elif order_column == "phone_number_2": - result = subjects_to_be_ordered.order_by(order_f('subject__phone_number_2')) + result = subjects_to_be_ordered.order_by(order_f("subject__phone_number_2")) elif order_column == "phone_number_3": - result = subjects_to_be_ordered.order_by(order_f('subject__phone_number_3')) + result = subjects_to_be_ordered.order_by(order_f("subject__phone_number_3")) elif order_column == "screening_number": - if 'screening_number' not in column_filters: + if "screening_number" not in column_filters: pattern = None else: - pattern = column_filters['screening_number'] + pattern = column_filters["screening_number"] result = subjects_to_be_ordered.all() - result = sorted(result, key=lambda t: t.sort_matched_screening_first(pattern, reverse=order_direction != 'asc'), - reverse=order_direction != 'asc') + result = sorted( + result, + key=lambda t: t.sort_matched_screening_first(pattern, reverse=order_direction != "asc"), + reverse=order_direction != "asc", + ) elif order_column == "default_location": - result = subjects_to_be_ordered.order_by(order_f('default_location__name')) + result = subjects_to_be_ordered.order_by(order_f("default_location__name")) elif order_column == "flying_team": - result = subjects_to_be_ordered.order_by(order_f('flying_team')) + result = subjects_to_be_ordered.order_by(order_f("flying_team")) elif order_column == "dead": - result = subjects_to_be_ordered.order_by(order_f('subject__dead')) + result = subjects_to_be_ordered.order_by(order_f("subject__dead")) elif order_column == "resigned": - result = subjects_to_be_ordered.order_by(order_f('resigned')) + result = subjects_to_be_ordered.order_by(order_f("resigned")) elif order_column == "endpoint_reached": - result = subjects_to_be_ordered.order_by(order_f('endpoint_reached')) + result = subjects_to_be_ordered.order_by(order_f("endpoint_reached")) elif order_column == "information_sent": - result = subjects_to_be_ordered.order_by(order_f('information_sent')) + result = subjects_to_be_ordered.order_by(order_f("information_sent")) elif order_column == "health_partner_first_name": - result = subjects_to_be_ordered.order_by(order_f('health_partner__first_name')) + result = subjects_to_be_ordered.order_by(order_f("health_partner__first_name")) elif order_column == "health_partner_last_name": - result = subjects_to_be_ordered.order_by(order_f('health_partner__last_name')) + result = subjects_to_be_ordered.order_by(order_f("health_partner__last_name")) elif order_column == "social_security_number": - result = subjects_to_be_ordered.order_by(order_f('subject__social_security_number')) + result = subjects_to_be_ordered.order_by(order_f("subject__social_security_number")) elif order_column == "postponed": - result = subjects_to_be_ordered.order_by(order_f('postponed')) + result = subjects_to_be_ordered.order_by(order_f("postponed")) elif order_column == "excluded": - result = subjects_to_be_ordered.order_by(order_f('excluded')) + result = subjects_to_be_ordered.order_by(order_f("excluded")) elif order_column == "type": - result = subjects_to_be_ordered.order_by(order_f('type__name')) + result = subjects_to_be_ordered.order_by(order_f("type__name")) elif order_column == "id": - result = subjects_to_be_ordered.order_by(order_f('id')) + result = subjects_to_be_ordered.order_by(order_f("id")) elif order_column == "date_born": - result = subjects_to_be_ordered.order_by(order_f('subject__date_born')) + result = subjects_to_be_ordered.order_by(order_f("subject__date_born")) elif order_column == "datetime_contact_reminder": - result = subjects_to_be_ordered.order_by(order_f('datetime_contact_reminder')) + result = subjects_to_be_ordered.order_by(order_f("datetime_contact_reminder")) elif order_column == "last_contact_attempt": # noinspection SpellCheckingInspection - result = subjects_to_be_ordered.annotate(sort_contact_attempt=Max( - "contactattempt__datetime_when")).order_by(order_f('sort_contact_attempt')) + result = subjects_to_be_ordered.annotate(sort_contact_attempt=Max("contactattempt__datetime_when")).order_by( + order_f("sort_contact_attempt") + ) elif str(order_column).startswith("visit_"): visit_number = get_visit_number_from_visit_x_string(order_column) result = order_by_visit(subjects_to_be_ordered, order_f, visit_number) - elif re.search(r'^custom_field-[0-9]+$', order_column): + elif re.search(r"^custom_field-[0-9]+$", order_column): field_id = int(order_column.replace("custom_field-", "")) result = subjects_to_be_ordered.annotate( - custom_field_value=Min(Case(When(customstudysubjectvalue__study_subject_field__id=field_id, - then='customstudysubjectvalue__value')))). \ - order_by(order_f('custom_field_value')) + custom_field_value=Min( + Case( + When( + customstudysubjectvalue__study_subject_field__id=field_id, then="customstudysubjectvalue__value" + ) + ) + ) + ).order_by(order_f("custom_field_value")) else: logger.warning("Unknown sort column: %s", str(order_column)) return result @@ -282,58 +359,94 @@ def get_visit_number_from_visit_x_string(order_column): def filter_by_visit(result, visit_number, visit_type): # we need to give custom names for filtering params that contain visit_number in it # because we might want to filter by few visits and they shouldn't collide - datetime_begin_filter = 'visit_' + str(visit_number) + '_datetime_begin' - datetime_end_filter = 'visit_' + str(visit_number) + '_datetime_end' - is_finished_filter = 'visit_' + str(visit_number) + '_is_finished' - finished_appointments_filter = 'visit_' + str(visit_number) + '_finished_appointments' - scheduled_appointments_filter = 'visit_' + str(visit_number) + '_scheduled_appointments' + datetime_begin_filter = "visit_" + str(visit_number) + "_datetime_begin" + datetime_end_filter = "visit_" + str(visit_number) + "_datetime_end" + is_finished_filter = "visit_" + str(visit_number) + "_is_finished" + finished_appointments_filter = "visit_" + str(visit_number) + "_finished_appointments" + scheduled_appointments_filter = "visit_" + str(visit_number) + "_scheduled_appointments" # this is hack... instead of providing True/False value this field contain 1/0 value, the problem is that we need # to provide aggregate function for the interacting parameter # If we try to assign it with pure Case(When...) (without Count/Min/Max/Avg) we obtain duplicates # of the results which are later on messing up with the subject list - result = result.annotate(**{ - is_finished_filter: Count(Case(When(Q(visit__is_finished=True) & Q(visit__visit_number=visit_number), then=1))) - }) + result = result.annotate( + **{ + is_finished_filter: Count( + Case(When(Q(visit__is_finished=True) & Q(visit__visit_number=visit_number), then=1)) + ) + } + ) # number of finished appointments - result = result.annotate(**{finished_appointments_filter: Count(Case(When( - Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_FINISHED) & Q(visit__visit_number=visit_number), - then=1)))}) + result = result.annotate( + **{ + finished_appointments_filter: Count( + Case( + When( + Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_FINISHED) + & Q(visit__visit_number=visit_number), + then=1, + ) + ) + ) + } + ) # number of scheduled appointments - result = result.annotate(**{scheduled_appointments_filter: Count(Case(When( - Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED) & Q(visit__visit_number=visit_number), - then=1)))}) + result = result.annotate( + **{ + scheduled_appointments_filter: Count( + Case( + When( + Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED) + & Q(visit__visit_number=visit_number), + then=1, + ) + ) + ) + } + ) # when visit starts result = result.annotate( - **{datetime_begin_filter: Min(Case(When(visit__visit_number=visit_number, then='visit__datetime_begin')))}) + **{datetime_begin_filter: Min(Case(When(visit__visit_number=visit_number, then="visit__datetime_begin")))} + ) # when visit finish result = result.annotate( - **{datetime_end_filter: Min(Case(When(visit__visit_number=visit_number, then='visit__datetime_end')))}) + **{datetime_end_filter: Min(Case(When(visit__visit_number=visit_number, then="visit__datetime_end")))} + ) if visit_type == "DONE": - result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ - filter(**{is_finished_filter + "__gt": 0}). \ - filter(**{finished_appointments_filter + "__gt": 0}) + result = ( + result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}) + .filter(**{is_finished_filter + "__gt": 0}) + .filter(**{finished_appointments_filter + "__gt": 0}) + ) elif visit_type == "MISSED": - result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ - filter(**{is_finished_filter + "__gt": 0}). \ - filter(**{finished_appointments_filter: 0}) + result = ( + result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}) + .filter(**{is_finished_filter + "__gt": 0}) + .filter(**{finished_appointments_filter: 0}) + ) elif visit_type == "EXCEED": - result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ - filter(**{is_finished_filter: 0}). \ - filter(**{scheduled_appointments_filter: 0}). \ - filter(**{datetime_end_filter + "__lt": get_today_midnight_date()}) + result = ( + result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}) + .filter(**{is_finished_filter: 0}) + .filter(**{scheduled_appointments_filter: 0}) + .filter(**{datetime_end_filter + "__lt": get_today_midnight_date()}) + ) elif visit_type == "IN_PROGRESS": - result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ - filter(**{is_finished_filter: 0}). \ - filter(**{scheduled_appointments_filter + "__gt": 0}) + result = ( + result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}) + .filter(**{is_finished_filter: 0}) + .filter(**{scheduled_appointments_filter + "__gt": 0}) + ) elif visit_type == "SHOULD_BE_IN_PROGRESS": - result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ - filter(**{is_finished_filter: 0}). \ - filter(**{datetime_end_filter + "__gt": get_today_midnight_date()}). \ - filter(**{scheduled_appointments_filter: 0}) + result = ( + result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}) + .filter(**{is_finished_filter: 0}) + .filter(**{datetime_end_filter + "__gt": get_today_midnight_date()}) + .filter(**{scheduled_appointments_filter: 0}) + ) elif visit_type == "UPCOMING": result = result.filter(**{datetime_begin_filter + "__gt": get_today_midnight_date()}) @@ -352,8 +465,12 @@ def get_subjects_filtered(subjects_to_be_filtered: QuerySet, filters) -> QuerySe elif column == "last_name": result = result.filter(subject__last_name__icontains=value) elif column == "address": - result = result.filter(Q(subject__address__icontains=value) | Q(subject__city__icontains=value) | Q( - subject__country__name__icontains=value)) + # pylint: disable-next=E1131 + result = result.filter( + Q(subject__address__icontains=value) + | Q(subject__city__icontains=value) + | Q(subject__country__name__icontains=value) + ) elif column == "next_of_kin_name": result = result.filter(subject__next_of_kin_name__icontains=value) elif column == "next_of_kin_phone": @@ -401,31 +518,41 @@ def get_subjects_filtered(subjects_to_be_filtered: QuerySet, filters) -> QuerySe elif str(column).startswith("visit_"): visit_number = get_visit_number_from_visit_x_string(column) result = filter_by_visit(result, visit_number, value) - elif re.search(r'^custom_field-[0-9]+$', column): + elif re.search(r"^custom_field-[0-9]+$", column): field_id = int(column.replace("custom_field-", "")) field = CustomStudySubjectField.objects.get(pk=field_id) if field.type == CUSTOM_FIELD_TYPE_TEXT: - result = result.filter(customstudysubjectvalue__study_subject_field__id=field_id, - customstudysubjectvalue__value__icontains=value) + result = result.filter( + customstudysubjectvalue__study_subject_field__id=field_id, + customstudysubjectvalue__value__icontains=value, + ) elif field.type == CUSTOM_FIELD_TYPE_BOOLEAN: - if value.lower() == 'true' or value.lower() == 'false': - result = result.filter(customstudysubjectvalue__study_subject_field__id=field_id, - customstudysubjectvalue__value__icontains=value) + if value.lower() == "true" or value.lower() == "false": + result = result.filter( + customstudysubjectvalue__study_subject_field__id=field_id, + customstudysubjectvalue__value__icontains=value, + ) else: - result = result.filter(customstudysubjectvalue__study_subject_field__id=field_id, - customstudysubjectvalue__value='') - elif field.type == CUSTOM_FIELD_TYPE_INTEGER or field.type == CUSTOM_FIELD_TYPE_DOUBLE: - result = result.filter(customstudysubjectvalue__study_subject_field__id=field_id, - customstudysubjectvalue__value=value) + result = result.filter( + customstudysubjectvalue__study_subject_field__id=field_id, customstudysubjectvalue__value="" + ) + elif field.type in (CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE): + result = result.filter( + customstudysubjectvalue__study_subject_field__id=field_id, customstudysubjectvalue__value=value + ) elif field.type == CUSTOM_FIELD_TYPE_DATE: - result = result.filter(customstudysubjectvalue__study_subject_field__id=field_id, - customstudysubjectvalue__value=value) - elif field.type == CUSTOM_FIELD_TYPE_INTEGER or field.type == CUSTOM_FIELD_TYPE_SELECT_LIST: - result = result.filter(customstudysubjectvalue__study_subject_field__id=field_id, - customstudysubjectvalue__value=value) + result = result.filter( + customstudysubjectvalue__study_subject_field__id=field_id, customstudysubjectvalue__value=value + ) + elif field.type in (CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_SELECT_LIST): + result = result.filter( + customstudysubjectvalue__study_subject_field__id=field_id, customstudysubjectvalue__value=value + ) elif field.type == CUSTOM_FIELD_TYPE_FILE: - result = result.filter(customstudysubjectvalue__study_subject_field__id=field_id, - customstudysubjectvalue__value__isnull=(value == 'N/A')) + result = result.filter( + customstudysubjectvalue__study_subject_field__id=field_id, + customstudysubjectvalue__value__isnull=(value == "N/A"), + ) else: raise NotImplementedError elif column == "": @@ -447,7 +574,7 @@ def subjects(request: WSGIRequest, subject_list_type: str) -> HttpResponse: elif request.method == "POST": request_data = request.POST else: - return HttpResponseNotAllowed(['GET', 'POST']) + return HttpResponseNotAllowed(["GET", "POST"]) # id of the query from dataTable: https://datatables.net/manual/server-side draw = int(request_data.get("draw", "-1")) @@ -469,7 +596,7 @@ def subjects(request: WSGIRequest, subject_list_type: str) -> HttpResponse: if length == -1: sliced_subjects = ordered_subjects else: - sliced_subjects = ordered_subjects[start:(start + length)] + sliced_subjects = ordered_subjects[start: (start + length)] result_subjects = sliced_subjects @@ -479,12 +606,14 @@ def subjects(request: WSGIRequest, subject_list_type: str) -> HttpResponse: for subject in result_subjects: data.append(serialize_subject(subject)) - return JsonResponse({ - "draw": draw, - "recordsTotal": count, - "recordsFiltered": count_filtered, - "data": data, - }) + return JsonResponse( + { + "draw": draw, + "recordsTotal": count, + "recordsFiltered": count_filtered, + "data": data, + } + ) except Exception as e: logger.error(e, exc_info=True) return e500_error(request) @@ -495,35 +624,36 @@ def types(request): data = [] for subject_type in SubjectType.objects.all(): data.append({"id": subject_type.id, "name": subject_type.name}) - return JsonResponse({ - "types": data - }) + return JsonResponse({"types": data}) def serialize_subject(study_subject: StudySubject): location = location_to_str(study_subject.default_location) flying_team = flying_team_to_str(study_subject.flying_team) - visits = Visit.objects.filter(subject=study_subject).order_by('visit_number') + visits = Visit.objects.filter(subject=study_subject).order_by("visit_number") serialized_visits = [] for visit in visits: if visit.datetime_begin < get_today_midnight_date(): if visit.is_finished: finished_appointments_count = visit.appointment_set.filter( - status=Appointment.APPOINTMENT_STATUS_FINISHED).count() + status=Appointment.APPOINTMENT_STATUS_FINISHED + ).count() if finished_appointments_count > 0: status = "DONE" else: status = "MISSED" elif visit.datetime_end < get_today_midnight_date(): scheduled_appointments_count = visit.appointment_set.filter( - status=Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + status=Appointment.APPOINTMENT_STATUS_SCHEDULED + ).count() if scheduled_appointments_count > 0: status = "IN_PROGRESS" else: status = "EXCEEDED" else: scheduled_appointments_count = visit.appointment_set.filter( - status=Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + status=Appointment.APPOINTMENT_STATUS_SCHEDULED + ).count() if scheduled_appointments_count > 0: status = "IN_PROGRESS" else: @@ -531,26 +661,34 @@ def serialize_subject(study_subject: StudySubject): else: status = "UPCOMING" - appointment_types = [f'{at.code} ({at.description})' for at in visit.appointment_types.all()] + appointment_types = [f"{at.code} ({at.description})" for at in visit.appointment_types.all()] if len(appointment_types) == 0: - appointment_types = ['No appointment types set.'] - - serialized_visits.append({ - "status": status, - "appointment_types": appointment_types, - "edit_visit_url": reverse('web.views.visit_details', args=(visit.id,)), - "add_appointment_url": reverse('web.views.appointment_add', args=(visit.id,)), - "datetime_start": serialize_date(visit.datetime_begin), - "datetime_end": serialize_date(visit.datetime_end), - "is_finished": visit.is_finished - }) + appointment_types = ["No appointment types set."] + + serialized_visits.append( + { + "status": status, + "appointment_types": appointment_types, + "edit_visit_url": reverse("web.views.visit_details", args=(visit.id,)), + "add_appointment_url": reverse("web.views.appointment_add", args=(visit.id,)), + "datetime_start": serialize_date(visit.datetime_begin), + "datetime_end": serialize_date(visit.datetime_end), + "is_finished": visit.is_finished, + } + ) contact_reminder = serialize_datetime(study_subject.datetime_contact_reminder) contact_attempts = ContactAttempt.objects.filter(subject=study_subject).order_by("-datetime_when") if len(contact_attempts) > 0: last_contact_attempt = contact_attempts[0] - last_contact_attempt_string = serialize_datetime(last_contact_attempt.datetime_when) + "<br/>" + str( - last_contact_attempt.worker) + "<br/> Success: " + bool_to_yes_no( - last_contact_attempt.success) + "<br/>" + last_contact_attempt.comment + last_contact_attempt_string = ( + serialize_datetime(last_contact_attempt.datetime_when) + + "<br/>" + + str(last_contact_attempt.worker) + + "<br/> Success: " + + bool_to_yes_no(last_contact_attempt.success) + + "<br/>" + + last_contact_attempt.comment + ) else: last_contact_attempt_string = "" @@ -594,16 +732,18 @@ def serialize_subject(study_subject: StudySubject): "type": study_subject.type.name, "id": study_subject.id, "visits": serialized_visits, - "default_written_communication_language": default_written_communication_language + "default_written_communication_language": default_written_communication_language, } for field_value in study_subject.custom_data_values: - if field_value.study_subject_field.type == CUSTOM_FIELD_TYPE_TEXT \ - or field_value.study_subject_field.type == CUSTOM_FIELD_TYPE_INTEGER \ - or field_value.study_subject_field.type == CUSTOM_FIELD_TYPE_DOUBLE: + if field_value.study_subject_field.type in ( + CUSTOM_FIELD_TYPE_TEXT, + CUSTOM_FIELD_TYPE_INTEGER, + CUSTOM_FIELD_TYPE_DOUBLE, + ): val = field_value.value if val is None: - val = '' + val = "" result[get_study_subject_field_id(field_value.study_subject_field)] = val elif field_value.study_subject_field.type == CUSTOM_FIELD_TYPE_BOOLEAN: result[get_study_subject_field_id(field_value.study_subject_field)] = str_to_yes_no_null(field_value.value) @@ -613,10 +753,11 @@ def serialize_subject(study_subject: StudySubject): result[get_study_subject_field_id(field_value.study_subject_field)] = field_value.value elif field_value.study_subject_field.type == CUSTOM_FIELD_TYPE_FILE: if field_value.value is None: - result[get_study_subject_field_id(field_value.study_subject_field)] = '' + result[get_study_subject_field_id(field_value.study_subject_field)] = "" else: - result[get_study_subject_field_id(field_value.study_subject_field)] = reverse( - 'web.views.uploaded_files') + '?file=' + str(field_value.value) + result[get_study_subject_field_id(field_value.study_subject_field)] = ( + reverse("web.views.uploaded_files") + "?file=" + str(field_value.value) + ) else: raise NotImplementedError diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py index 8027773c..8a848792 100644 --- a/smash/web/forms/study_subject_forms.py +++ b/smash/web/forms/study_subject_forms.py @@ -7,9 +7,15 @@ from django.forms import ModelForm from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, get_worker_from_args, DATEPICKER_DATE_ATTRS from web.models import StudySubject, Study, StudyColumns, VoucherType, Worker, Visit -from web.models.constants import CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, \ - CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, \ - CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_FILE +from web.models.constants import ( + CUSTOM_FIELD_TYPE_TEXT, + CUSTOM_FIELD_TYPE_BOOLEAN, + CUSTOM_FIELD_TYPE_INTEGER, + CUSTOM_FIELD_TYPE_DOUBLE, + CUSTOM_FIELD_TYPE_DATE, + CUSTOM_FIELD_TYPE_SELECT_LIST, + CUSTOM_FIELD_TYPE_FILE, +) from web.models.custom_data import CustomStudySubjectField, CustomStudySubjectValue from web.models.custom_data.custom_study_subject_field import get_study_subject_field_id from web.models.worker_study_role import WORKER_HEALTH_PARTNER @@ -19,61 +25,87 @@ logger = logging.getLogger(__name__) def get_custom_select_choices(possible_values): - result = list() + result = [] index = 1 - result.append(('0', '---')) + result.append(("0", "---")) for value in possible_values.split(";"): result.append((str(index), value)) index += 1 return result -def create_field_for_custom_study_subject_field(study_subject_field: CustomStudySubjectField, - field_value: CustomStudySubjectValue = None) -> forms.Field: +def create_field_for_custom_study_subject_field( + study_subject_field: CustomStudySubjectField, field_value: CustomStudySubjectValue = None +) -> forms.Field: val = study_subject_field.default_value if field_value is not None: val = field_value.value if study_subject_field.type == CUSTOM_FIELD_TYPE_TEXT: - field = forms.CharField(label=study_subject_field.name, initial=val, - required=study_subject_field.required, disabled=study_subject_field.readonly) + field = forms.CharField( + label=study_subject_field.name, + initial=val, + required=study_subject_field.required, + disabled=study_subject_field.readonly, + ) elif study_subject_field.type == CUSTOM_FIELD_TYPE_BOOLEAN: initial = False - if val is not None and val.lower() == 'true': + if val is not None and val.lower() == "true": initial = True - field = forms.BooleanField(label=study_subject_field.name, initial=initial, - required=study_subject_field.required, disabled=study_subject_field.readonly) + field = forms.BooleanField( + label=study_subject_field.name, + initial=initial, + required=study_subject_field.required, + disabled=study_subject_field.readonly, + ) elif study_subject_field.type == CUSTOM_FIELD_TYPE_INTEGER: initial = None if val is not None and re.match(r"[-+]?\d+$", val) is not None: initial = int(val) - field = forms.IntegerField(label=study_subject_field.name, initial=initial, - required=study_subject_field.required, disabled=study_subject_field.readonly) + field = forms.IntegerField( + label=study_subject_field.name, + initial=initial, + required=study_subject_field.required, + disabled=study_subject_field.readonly, + ) elif study_subject_field.type == CUSTOM_FIELD_TYPE_DOUBLE: initial = None if val is not None and re.match(r"[-+]?\d+?\.\d+?$", val) is not None: initial = float(val) - field = forms.FloatField(label=study_subject_field.name, initial=initial, - required=study_subject_field.required, disabled=study_subject_field.readonly) + field = forms.FloatField( + label=study_subject_field.name, + initial=initial, + required=study_subject_field.required, + disabled=study_subject_field.readonly, + ) elif study_subject_field.type == CUSTOM_FIELD_TYPE_DATE: initial = None - if val is not None and val != '': - initial = datetime.datetime.strptime(val, '%Y-%m-%d') - field = forms.DateTimeField(label=study_subject_field.name, initial=initial, - required=study_subject_field.required, disabled=study_subject_field.readonly, - widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d")) + if val is not None and val != "": + initial = datetime.datetime.strptime(val, "%Y-%m-%d") + field = forms.DateTimeField( + label=study_subject_field.name, + initial=initial, + required=study_subject_field.required, + disabled=study_subject_field.readonly, + widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), + ) elif study_subject_field.type == CUSTOM_FIELD_TYPE_SELECT_LIST: - initial = '0' + initial = "0" for v, k in get_custom_select_choices(study_subject_field.possible_values): if k == val: initial = v - field = forms.ChoiceField(label=study_subject_field.name, initial=initial, - required=study_subject_field.required, disabled=study_subject_field.readonly, - choices=get_custom_select_choices(study_subject_field.possible_values)) + field = forms.ChoiceField( + label=study_subject_field.name, + initial=initial, + required=study_subject_field.required, + disabled=study_subject_field.readonly, + choices=get_custom_select_choices(study_subject_field.possible_values), + ) elif study_subject_field.type == CUSTOM_FIELD_TYPE_FILE: initial = None if val is not None: + class CustomFileField: - url = '' + url = "" def __str__(self): return f"{self.url}" @@ -89,7 +121,7 @@ def create_field_for_custom_study_subject_field(study_subject_field: CustomStudy required=study_subject_field.required, disabled=study_subject_field.readonly, initial=initial, - widget=SecuredFileWidget() # TODO: how to allow multiple files? + widget=SecuredFileWidget(), ) else: raise NotImplementedError @@ -97,18 +129,19 @@ def create_field_for_custom_study_subject_field(study_subject_field: CustomStudy class StudySubjectForm(ModelForm): - datetime_contact_reminder = forms.DateTimeField(label="Contact on", - widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), - required=False) - referral_letter = forms.FileField(label='Referral letter', widget=SecuredFileWidget(), required=False) - voucher_types = forms.ModelMultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple, - queryset=VoucherType.objects.all()) + datetime_contact_reminder = forms.DateTimeField( + label="Contact on", widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), required=False + ) + referral_letter = forms.FileField(label="Referral letter", widget=SecuredFileWidget(), required=False) + voucher_types = forms.ModelMultipleChoiceField( + required=False, widget=forms.CheckboxSelectMultiple, queryset=VoucherType.objects.all() + ) study: Study def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - instance = kwargs.get('instance') + instance = kwargs.get("instance") if instance: for value in instance.custom_data_values: field_id = get_study_subject_field_id(value.study_subject_field) @@ -118,24 +151,27 @@ class StudySubjectForm(ModelForm): field_id = get_study_subject_field_id(field_type) self.fields[field_id] = create_field_for_custom_study_subject_field(field_type) - self.fields['health_partner'].queryset = Worker.get_workers_by_worker_type( - WORKER_HEALTH_PARTNER) + self.fields["health_partner"].queryset = Worker.get_workers_by_worker_type(WORKER_HEALTH_PARTNER) def clean(self): cleaned_data = super().clean() subject_id = -1 - if getattr(self, 'instance', None) is not None: - subject_id = getattr(self, 'instance', None).id + if getattr(self, "instance", None) is not None: + subject_id = getattr(self, "instance", None).id for field_type in CustomStudySubjectField.objects.filter(study=self.study): if field_type.unique: field_id = get_study_subject_field_id(field_type) value = cleaned_data[field_id] if value is not None and value != "": - count = StudySubject.objects.filter(customstudysubjectvalue__study_subject_field=field_type, - customstudysubjectvalue__value=value, - study=self.study) \ - .exclude(id=subject_id) \ + count = ( + StudySubject.objects.filter( + customstudysubjectvalue__study_subject_field=field_type, + customstudysubjectvalue__value=value, + study=self.study, + ) + .exclude(id=subject_id) .count() + ) if count > 0: self.add_error(field_id, "Value must be unique within the study") return cleaned_data @@ -144,8 +180,8 @@ class StudySubjectForm(ModelForm): class StudySubjectAddForm(StudySubjectForm): class Meta: model = StudySubject - fields = '__all__' - exclude = ['resigned', 'resign_reason', 'endpoint_reached', 'endpoint_reached_reason'] + fields = "__all__" + exclude = ["resigned", "resign_reason", "endpoint_reached", "endpoint_reached_reason"] def __init__(self, *args, **kwargs): self.user = get_worker_from_args(kwargs) @@ -161,12 +197,13 @@ class StudySubjectAddForm(StudySubjectForm): # we can add custom values only after object exists in the database for field_type in CustomStudySubjectField.objects.filter(study=self.study): if not field_type.readonly: - self.instance.set_custom_data_value(field_type, get_study_subject_field_value(field_type, self[ - get_study_subject_field_id(field_type)])) + self.instance.set_custom_data_value( + field_type, get_study_subject_field_value(field_type, self[get_study_subject_field_id(field_type)]) + ) return instance def build_screening_number(self, cleaned_data): - screening_number = cleaned_data.get('screening_number', None) + screening_number = cleaned_data.get("screening_number", None) if not screening_number: prefix_screening_number = self.get_prefix_screening_number() if prefix_screening_number is not None: @@ -177,18 +214,18 @@ class StudySubjectAddForm(StudySubjectForm): cleaned_data = super().clean() screening_number = self.build_screening_number(cleaned_data) if screening_number is not None and self.study.columns.screening_number: - cleaned_data['screening_number'] = screening_number + cleaned_data["screening_number"] = screening_number validate_subject_screening_number(self, cleaned_data) validate_subject_nd_number(self, cleaned_data) return cleaned_data def get_prefix_screening_number(self): - default_location = self.cleaned_data.get('default_location', None) + default_location = self.cleaned_data.get("default_location", None) screening_number_prefix = None if default_location is not None and default_location.prefix: screening_number_prefix = default_location.prefix else: - subject_type = self.cleaned_data.get('type', None) + subject_type = self.cleaned_data.get("type", None) if subject_type is not None: screening_number_prefix = subject_type.screening_number_prefix if screening_number_prefix is None: @@ -217,11 +254,11 @@ def get_new_screening_number(screening_number_prefix): class StudySubjectDetailForm(StudySubjectForm): class Meta: model = StudySubject - fields = '__all__' + fields = "__all__" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - instance = getattr(self, 'instance', None) + instance = getattr(self, "instance", None) self.study = get_study_from_study_subject_instance(instance) @@ -253,8 +290,8 @@ def get_study_subject_field_value(field_type: CustomStudySubjectField, field: fo return None return field.value() elif field_type.type == CUSTOM_FIELD_TYPE_SELECT_LIST: - if field.value() == '0': - return '' + if field.value() == "0": + return "" if field.value() is None: return None for v, k in get_custom_select_choices(field_type.possible_values): @@ -269,19 +306,18 @@ def get_study_subject_field_value(field_type: CustomStudySubjectField, field: fo class StudySubjectEditForm(StudySubjectForm): - def __init__(self, *args, **kwargs): - was_resigned = kwargs.pop('was_resigned', False) - endpoint_was_reached = kwargs.pop('endpoint_was_reached', False) + was_resigned = kwargs.pop("was_resigned", False) + endpoint_was_reached = kwargs.pop("endpoint_was_reached", False) super().__init__(*args, **kwargs) - instance: StudySubject = getattr(self, 'instance', None) + instance: StudySubject = getattr(self, "instance", None) if instance and instance.id: - self.fields['screening_number'].widget.attrs['readonly'] = True + self.fields["screening_number"].widget.attrs["readonly"] = True self.study = get_study_from_study_subject_instance(instance) self.original_type = instance.type - self.fields['resigned'].disabled = was_resigned - self.fields['endpoint_reached'].disabled = endpoint_was_reached + self.fields["resigned"].disabled = was_resigned + self.fields["endpoint_reached"].disabled = endpoint_was_reached prepare_study_subject_fields(fields=self.fields, study=self.study) @@ -294,22 +330,24 @@ class StudySubjectEditForm(StudySubjectForm): def save(self, commit=True) -> StudySubject: for field_type in CustomStudySubjectField.objects.filter(study=self.study): if not field_type.readonly: - self.instance.set_custom_data_value(field_type, get_study_subject_field_value(field_type, self[ - get_study_subject_field_id(field_type)])) + self.instance.set_custom_data_value( + field_type, get_study_subject_field_value(field_type, self[get_study_subject_field_id(field_type)]) + ) if self.original_type != self.instance.type: - self.instance.visit_used_to_compute_followup_date = Visit.objects.filter(subject=self.instance).order_by( - '-visit_number').first() + self.instance.visit_used_to_compute_followup_date = ( + Visit.objects.filter(subject=self.instance).order_by("-visit_number").first() + ) return super().save(commit) class Meta: model = StudySubject - fields = '__all__' + fields = "__all__" def get_study_from_args(kwargs): - study = kwargs.pop('study', None) + study = kwargs.pop("study", None) if study is None: raise TypeError("Study not defined") return study @@ -323,55 +361,55 @@ def prepare_field(fields, visible_columns: StudyColumns, field_name: str, requir def prepare_study_subject_fields(fields, study: Study): - prepare_field(fields, study.columns, 'default_location', required=True) - prepare_field(fields, study.columns, 'type', required=True) - prepare_field(fields, study.columns, 'screening_number') - prepare_field(fields, study.columns, 'nd_number') - prepare_field(fields, study.columns, 'datetime_contact_reminder') - prepare_field(fields, study.columns, 'postponed') - prepare_field(fields, study.columns, 'flying_team') - prepare_field(fields, study.columns, 'comments') - prepare_field(fields, study.columns, 'referral') - prepare_field(fields, study.columns, 'information_sent') - prepare_field(fields, study.columns, 'endpoint_reached') - prepare_field(fields, study.columns, 'endpoint_reached_reason') - prepare_field(fields, study.columns, 'excluded') - prepare_field(fields, study.columns, 'exclude_reason') - prepare_field(fields, study.columns, 'resigned') - prepare_field(fields, study.columns, 'resign_reason') - prepare_field(fields, study.columns, 'referral_letter') - prepare_field(fields, study.columns, 'health_partner') - prepare_field(fields, study.columns, 'health_partner_feedback_agreement') + prepare_field(fields, study.columns, "default_location", required=True) + prepare_field(fields, study.columns, "type", required=True) + prepare_field(fields, study.columns, "screening_number") + prepare_field(fields, study.columns, "nd_number") + prepare_field(fields, study.columns, "datetime_contact_reminder") + prepare_field(fields, study.columns, "postponed") + prepare_field(fields, study.columns, "flying_team") + prepare_field(fields, study.columns, "comments") + prepare_field(fields, study.columns, "referral") + prepare_field(fields, study.columns, "information_sent") + prepare_field(fields, study.columns, "endpoint_reached") + prepare_field(fields, study.columns, "endpoint_reached_reason") + prepare_field(fields, study.columns, "excluded") + prepare_field(fields, study.columns, "exclude_reason") + prepare_field(fields, study.columns, "resigned") + prepare_field(fields, study.columns, "resign_reason") + prepare_field(fields, study.columns, "referral_letter") + prepare_field(fields, study.columns, "health_partner") + prepare_field(fields, study.columns, "health_partner_feedback_agreement") if not study.columns.vouchers: - del fields['voucher_types'] + del fields["voucher_types"] def validate_subject_screening_number(self, cleaned_data): if self.study.columns.resign_reason and self.study.columns.screening_number: - subjects_from_db = StudySubject.objects.filter(screening_number=cleaned_data["screening_number"], - study=self.study) + subjects_from_db = StudySubject.objects.filter( + screening_number=cleaned_data["screening_number"], study=self.study + ) if len(subjects_from_db) > 0: - self.add_error('screening_number', "Screening number already in use") + self.add_error("screening_number", "Screening number already in use") def validate_subject_nd_number(self, cleaned_data): if self.study.columns.nd_number: - nd_number = cleaned_data['nd_number'] + nd_number = cleaned_data["nd_number"] if nd_number is None: - self.add_error('nd_number', "None ND number. ND number can be blank but not None.") + self.add_error("nd_number", "None ND number. ND number can be blank but not None.") elif nd_number: if not self.study.check_nd_number(nd_number): - self.add_error('nd_number', "Invalid ND number") + self.add_error("nd_number", "Invalid ND number") else: - subjects_from_db = StudySubject.objects.filter( - nd_number=nd_number, study=self.study) + subjects_from_db = StudySubject.objects.filter(nd_number=nd_number, study=self.study) if subjects_from_db: - if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''): - self.add_error('nd_number', "Subject number already in use") + if subjects_from_db[0].screening_number != cleaned_data.get("screening_number", ""): + self.add_error("nd_number", "Subject number already in use") # else: #empty nd_number is valid def validate_subject_resign_reason(self, cleaned_data): if self.study.columns.resigned and self.study.columns.resign_reason: - if cleaned_data['resigned'] and cleaned_data['resign_reason'] == '': - self.add_error('resign_reason', "Resign reason cannot be empty") + if cleaned_data["resigned"] and cleaned_data["resign_reason"] == "": + self.add_error("resign_reason", "Resign reason cannot be empty") diff --git a/smash/web/forms/voucher_forms.py b/smash/web/forms/voucher_forms.py index 94ee92fc..bfd2de1b 100644 --- a/smash/web/forms/voucher_forms.py +++ b/smash/web/forms/voucher_forms.py @@ -18,100 +18,101 @@ logger = logging.getLogger(__name__) class VoucherTypeForm(ModelForm): class Meta: model = VoucherType - exclude = ['study'] + exclude = ["study"] class VoucherTypePriceForm(ModelForm): - start_date = forms.DateField(label="Start date", - widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d") - ) - end_date = forms.DateField(label="End date", - widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d") - ) + start_date = forms.DateField(label="Start date", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d")) + end_date = forms.DateField(label="End date", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d")) class Meta: model = VoucherTypePrice - exclude = ['voucher_type'] + exclude = ["voucher_type"] class VoucherForm(ModelForm): class Meta: model = Voucher - fields = '__all__' + fields = "__all__" @staticmethod def _voucher_type_optgroup(subject_voucher_types, all_voucher_types): subject_voucher_types = {(vt.id, str(vt)) for vt in subject_voucher_types} all_voucher_types = {(vt.id, str(vt)) for vt in all_voucher_types} if subject_voucher_types == all_voucher_types: - return [(None, 'Please select an option')] + list(all_voucher_types) + return [(None, "Please select an option")] + list(all_voucher_types) elif len(subject_voucher_types) == 0: - return [(None, 'Please select an option')] + list(all_voucher_types) + return [(None, "Please select an option")] + list(all_voucher_types) rest_voucher_types = all_voucher_types - subject_voucher_types return ( - (None, 'Please select an option'), - ('Subject Voucher Types', tuple(subject_voucher_types)), - ('Rest of Voucher Types', tuple(rest_voucher_types)) + (None, "Please select an option"), + ("Subject Voucher Types", tuple(subject_voucher_types)), + ("Rest of Voucher Types", tuple(rest_voucher_types)), ) def __init__(self, *args, **kwargs): - voucher_types = kwargs.pop('voucher_types', VoucherType.objects.all()) + voucher_types = kwargs.pop("voucher_types", VoucherType.objects.all()) super().__init__(*args, **kwargs) - self.fields['voucher_type'].choices = VoucherForm._voucher_type_optgroup(voucher_types, - VoucherType.objects.all()) - self.fields['voucher_type'].default = None - self.fields['usage_partner'].queryset = Worker.get_workers_by_worker_type(WORKER_VOUCHER_PARTNER) + self.fields["voucher_type"].choices = VoucherForm._voucher_type_optgroup( + voucher_types, VoucherType.objects.all() + ) + self.fields["voucher_type"].default = None + self.fields["usage_partner"].queryset = Worker.get_workers_by_worker_type(WORKER_VOUCHER_PARTNER) - self.fields['number'].widget.attrs['readonly'] = True - self.fields['number'].required = False - self.fields['issue_worker'].widget.attrs['readonly'] = True + self.fields["number"].widget.attrs["readonly"] = True + self.fields["number"].required = False + self.fields["issue_worker"].widget.attrs["readonly"] = True # issue date - self.fields['issue_date'].input_formats = ('%Y-%m-%d',) - self.fields['issue_date'].required = False - self.fields['issue_date'].widget.attrs.update({'class': 'datepicker'}) - self.fields['issue_date'].widget.attrs.update({'readonly': False}) - self.fields['issue_date'].widget.attrs.update({'placeholder': 'yyyy-mm-dd (optional)'}) - self.fields['issue_date'].widget.attrs.update({'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2}'}) - self.fields['issue_date'].widget.attrs.update({'required': False}) - self.fields['issue_date'].initial = timezone.now() + self.fields["issue_date"].input_formats = ("%Y-%m-%d",) + self.fields["issue_date"].required = False + self.fields["issue_date"].widget.attrs.update({"class": "datepicker"}) + self.fields["issue_date"].widget.attrs.update({"readonly": False}) + self.fields["issue_date"].widget.attrs.update({"placeholder": "yyyy-mm-dd (optional)"}) + self.fields["issue_date"].widget.attrs.update({"pattern": "[0-9]{4}-[0-9]{2}-[0-9]{2}"}) + self.fields["issue_date"].widget.attrs.update({"required": False}) + self.fields["issue_date"].initial = timezone.now() # hours - self.fields['hours'].widget.attrs.update({'min': 0}) + self.fields["hours"].widget.attrs.update({"min": 0}) # expiry date - self.fields['expiry_date'].widget.attrs['readonly'] = True - self.fields['expiry_date'].required = False - instance = getattr(self, 'instance', None) + self.fields["expiry_date"].widget.attrs["readonly"] = True + self.fields["expiry_date"].required = False + instance = getattr(self, "instance", None) if instance and instance.pk: - self.fields['issue_date'].widget.attrs['readonly'] = True - self.fields['voucher_type'].widget.attrs['readonly'] = True - self.fields['hours'].widget.attrs['readonly'] = True - self.fields['activity_type'].widget.attrs['readonly'] = True - self.fields['usage_partner'].widget.attrs['readonly'] = True + self.fields["issue_date"].widget.attrs["readonly"] = True + self.fields["voucher_type"].widget.attrs["readonly"] = True + self.fields["hours"].widget.attrs["readonly"] = True + self.fields["activity_type"].widget.attrs["readonly"] = True + self.fields["usage_partner"].widget.attrs["readonly"] = True if instance.status in [VOUCHER_STATUS_USED, VOUCHER_STATUS_EXPIRED, VOUCHER_STATUS_REMOVED]: - self.fields['status'].widget.attrs['readonly'] = True - self.fields['feedback'].widget.attrs['readonly'] = True + self.fields["status"].widget.attrs["readonly"] = True + self.fields["feedback"].widget.attrs["readonly"] = True def clean(self): # if empty, add a datetime - if self.cleaned_data['issue_date'] is None: - self.cleaned_data['issue_date'] = timezone.now() + if self.cleaned_data["issue_date"] is None: + self.cleaned_data["issue_date"] = timezone.now() def save(self, commit=True): instance = super().save(commit=False) if not instance.id: instance.expiry_date = instance.issue_date + relativedelta( - months=+instance.study_subject.study.default_voucher_expiration_in_months) + months=+instance.study_subject.study.default_voucher_expiration_in_months + ) max_id = str(1).zfill(5) if Voucher.objects.all().count() > 0: - max_id = str(Voucher.objects.latest('id').id + 1).zfill(4) + max_id = str(Voucher.objects.latest("id").id + 1).zfill(4) # number in format {ND_NUMBER}-{DATE}-{VOUCHER_PARTNER_CODE}-{VOUCHER_TYPE}-{SEQ_NUMBER}{CHECKSUM} - instance.number = "{}-{}-{}-{}-{}{}".format(instance.study_subject.nd_number, - datetime.datetime.now().strftime("%Y%m%d"), - instance.usage_partner.voucher_partner_code, - instance.voucher_type.code, - max_id, - VerhoeffAlgorithm.calculate_verhoeff_check_sum(max_id)) + # pylint: disable-next=consider-using-f-string + instance.number = "{}-{}-{}-{}-{}{}".format( + instance.study_subject.nd_number, + datetime.datetime.now().strftime("%Y%m%d"), + instance.usage_partner.voucher_partner_code, + instance.voucher_type.code, + max_id, + VerhoeffAlgorithm.calculate_verhoeff_check_sum(max_id), + ) if commit: instance.save() diff --git a/smash/web/importer/csv_subject_import_reader.py b/smash/web/importer/csv_subject_import_reader.py index 68c3276f..df587e77 100644 --- a/smash/web/importer/csv_subject_import_reader.py +++ b/smash/web/importer/csv_subject_import_reader.py @@ -45,7 +45,7 @@ class CsvSubjectImportReader(SubjectImportReader): for header, value in zip(headers, row): field = self.get_table_and_field(header)[1] - if field is not None and (field.name == "nd_number" or field.name == "screening_number"): + if field is not None and field.name in ("nd_number", "screening_number"): self.add_data(study_subject, header, value) if study_subject.nd_number is None or study_subject.nd_number == "": study_subject.nd_number = study_subject.screening_number diff --git a/smash/web/importer/importer_cron_job.py b/smash/web/importer/importer_cron_job.py index 195152ba..19785538 100644 --- a/smash/web/importer/importer_cron_job.py +++ b/smash/web/importer/importer_cron_job.py @@ -24,11 +24,11 @@ class SubjectImporterCronJob(CronJobBase): try: item = SubjectImportData.objects.filter(study_id=GLOBAL_STUDY_ID).first() if item is not None: - RUN_AT_TIMES = item.run_at_times.split(';') + RUN_AT_TIMES = item.run_at_times.split(";") except (OperationalError, ProgrammingError): # sqlite and postgres throw different errors here - logger.debug('Looks like db is not initialized') + logger.debug("Looks like db is not initialized") schedule = Schedule(run_at_times=RUN_AT_TIMES) - code = 'web.import_subjects_daily_job' # a unique code + code = "web.import_subjects_daily_job" # a unique code def __init__(self, study_id=GLOBAL_STUDY_ID): super().__init__() @@ -40,36 +40,41 @@ class SubjectImporterCronJob(CronJobBase): email_recipients = ConfigurationItem.objects.get(type=DEFAULT_FROM_EMAIL).value for import_data in SubjectImportData.objects.filter(study=self.study).all(): - if import_data.filename is None or import_data.filename == '': + if import_data.filename is None or import_data.filename == "": logger.info("Importing subjects skipped. File not defined ") return "import file not defined" filename = import_data.get_absolute_file_path() logger.info("Importing subjects from file: %s", filename) if not os.path.isfile(filename): - content = "<h3><font color='red'>File with imported data is not available in the system: " + \ - filename + "</font></h3>" - EmailSender.send_email(email_title, - content, - email_recipients) + content = ( + "<h3><font color='red'>File with imported data is not available in the system: " + + filename + + "</font></h3>" + ) + EmailSender.send_email(email_title, content, email_recipients) return "import file not found" # noinspection PyBroadException try: importer = Importer(CsvSubjectImportReader(import_data)) importer.execute() email_body = importer.get_summary() - EmailSender.send_email(email_title, - f"<h3>Data was successfully imported from file: {filename}</h3>{email_body}", - email_recipients) + EmailSender.send_email( + email_title, + f"<h3>Data was successfully imported from file: {filename}</h3>{email_body}", + email_recipients, + ) backup_file(filename) return "import is successful" except BaseException: tb = traceback.format_exc() - EmailSender.send_email(email_title, - f"<h3><font color='red'>There was a problem with importing data from file: " - "{filename}</font></h3><pre>{tb}</pre>", - email_recipients) - logger.exception(f'There was a problem with importing data from file: {filename}') + EmailSender.send_email( + email_title, + f"<h3><font color='red'>There was a problem with importing data from file: " + f"{filename}</font></h3><pre>{tb}</pre>", + email_recipients, + ) + logger.exception("There was a problem with importing data from file: %s", filename) return "import crashed" @@ -78,11 +83,11 @@ class VisitImporterCronJob(CronJobBase): try: item = VisitImportData.objects.filter(study_id=GLOBAL_STUDY_ID).first() if item is not None: - RUN_AT_TIMES = item.run_at_times.split(';') + RUN_AT_TIMES = item.run_at_times.split(";") except (OperationalError, ProgrammingError): # sqlite and postgres throw different errors here - logger.debug('Looks like db is not initialized') + logger.debug("Looks like db is not initialized") schedule = Schedule(run_at_times=RUN_AT_TIMES) - code = 'web.import_visits_daily_job' # a unique code + code = "web.import_visits_daily_job" # a unique code def __init__(self, study_id=GLOBAL_STUDY_ID): super().__init__() @@ -94,15 +99,17 @@ class VisitImporterCronJob(CronJobBase): email_recipients = ConfigurationItem.objects.get(type=DEFAULT_FROM_EMAIL).value for import_data in VisitImportData.objects.filter(study=self.study).all(): - - if import_data.filename is None or import_data.filename == '': + if import_data.filename is None or import_data.filename == "": logger.info("Importing visits skipped. File not defined ") return "import file not defined" filename = import_data.get_absolute_file_path() logger.info("Importing visits from file: %s", filename) if not import_data.file_available(): - content = "<h3><font color='red'>File with imported data is not available in the system: " + \ - import_data.filename + "</font></h3>" + content = ( + "<h3><font color='red'>File with imported data is not available in the system: " + + import_data.filename + + "</font></h3>" + ) EmailSender.send_email(email_title, content, email_recipients) return "import file not found" @@ -111,19 +118,23 @@ class VisitImporterCronJob(CronJobBase): importer = CsvVisitImportReader(import_data) importer.load_data() email_body = importer.get_summary() - EmailSender.send_email(email_title, - f"<h3>Data was successfully imported from file: {filename}</h3>{email_body}", - email_recipients) + EmailSender.send_email( + email_title, + f"<h3>Data was successfully imported from file: {filename}</h3>{email_body}", + email_recipients, + ) backup_file(filename) return "import is successful" except BaseException: tb = traceback.format_exc() - EmailSender.send_email(email_title, - "<h3><font color='red'>There was a problem with importing data from file: " - f"{filename}</font></h3><pre>{tb}</pre>", - email_recipients) - logger.exception(f"There was a problem with importing data from file: {filename}") + EmailSender.send_email( + email_title, + "<h3><font color='red'>There was a problem with importing data from file: " + f"{filename}</font></h3><pre>{tb}</pre>", + email_recipients, + ) + logger.exception("There was a problem with importing data from file: %s", filename) return "import crashed" diff --git a/smash/web/migrations/0001_version-1-0-0.py b/smash/web/migrations/0001_version-1-0-0.py index f72b974c..251793ee 100644 --- a/smash/web/migrations/0001_version-1-0-0.py +++ b/smash/web/migrations/0001_version-1-0-0.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines # Generated by Django 3.1.4 on 2021-10-01 11:26 import django.core.files.storage @@ -70,12 +71,12 @@ def configuration_item_color_fields__0025(apps, schema_editor): # pylint: disab cancelled_appointment_color.save() -def create_item(apps, type, value, name, value_type: str = VALUE_TYPE_TEXT): +def create_item(apps, _type, value, name, value_type: str = VALUE_TYPE_TEXT): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.value_type = value_type diff --git a/smash/web/migrations/0032_configurationitem_email_items.py b/smash/web/migrations/0032_configurationitem_email_items.py index 78b4deff..2145b322 100644 --- a/smash/web/migrations/0032_configurationitem_email_items.py +++ b/smash/web/migrations/0032_configurationitem_email_items.py @@ -11,12 +11,12 @@ from web.models.constants import ( ) -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0061_remove_subject_country.py b/smash/web/migrations/0061_remove_subject_country.py index 23befcb7..e7657e16 100644 --- a/smash/web/migrations/0061_remove_subject_country.py +++ b/smash/web/migrations/0061_remove_subject_country.py @@ -2,23 +2,23 @@ # Generated by Django 1.10.7 on 2017-10-31 09:15 -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('web', '0060_remove_subject_country'), + ("web", "0060_remove_subject_country"), ] operations = [ migrations.RemoveField( - model_name='subject', - name='country', + model_name="subject", + name="country", ), migrations.RenameField( - model_name='subject', - old_name='country_2', - new_name='country', + model_name="subject", + old_name="country_2", + new_name="country", ), - migrations.RunSQL('update web_country set name=\'---\', "order"=0 where id=1;'), + migrations.RunSQL("update web_country set name='---', \"order\"=0 where id=1;"), ] diff --git a/smash/web/migrations/0064_auto_20171127_0954.py b/smash/web/migrations/0064_auto_20171127_0954.py index 55136985..712cded6 100644 --- a/smash/web/migrations/0064_auto_20171127_0954.py +++ b/smash/web/migrations/0064_auto_20171127_0954.py @@ -2,18 +2,14 @@ # Generated by Django 1.10.7 on 2017-11-27 09:54 -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion +from django.db import migrations class Migration(migrations.Migration): atomic = False - + dependencies = [ - ('web', '0063_auto_20171120_1429'), + ("web", "0063_auto_20171120_1429"), ] - operations = [ - migrations.RenameModel('Subject', 'StudySubject') - ] + operations = [migrations.RenameModel("Subject", "StudySubject")] diff --git a/smash/web/migrations/0066_subject.py b/smash/web/migrations/0066_subject.py index 4f92c9aa..7778b0b3 100644 --- a/smash/web/migrations/0066_subject.py +++ b/smash/web/migrations/0066_subject.py @@ -39,7 +39,7 @@ class Migration(migrations.Migration): "web_studyubject_default_location_id_da83b714_fk_web_location_id;" ), migrations.RunSQL( - "alter table web_studysubject rename constraint" "web_subject_pkey to web_studysubject_pkey;" + "alter table web_studysubject rename constraint" + "web_subject_pkey to web_studysubject_pkey;" ), migrations.RunSQL( "alter table web_studysubject_languages rename constraint" diff --git a/smash/web/migrations/0082_studysubjectlist_visits.py b/smash/web/migrations/0082_studysubjectlist_visits.py index 8cd76414..fc7ca077 100644 --- a/smash/web/migrations/0082_studysubjectlist_visits.py +++ b/smash/web/migrations/0082_studysubjectlist_visits.py @@ -2,14 +2,12 @@ # Generated by Django 1.10.7 on 2017-12-04 16:47 -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('web', '0081_studysubjectlist_last_contact_attempt'), + ("web", "0081_studysubjectlist_last_contact_attempt"), ] - operations = [ - ] + operations = [] diff --git a/smash/web/migrations/0086_unfinished_visit_list.py b/smash/web/migrations/0086_unfinished_visit_list.py index e6221b90..9f35dfae 100644 --- a/smash/web/migrations/0086_unfinished_visit_list.py +++ b/smash/web/migrations/0086_unfinished_visit_list.py @@ -2,7 +2,7 @@ # Generated by Django 1.10.7 on 2017-12-05 16:50 -from django.db import migrations, models +from django.db import migrations from ..migration_functions import is_sqlite_db diff --git a/smash/web/migrations/0106_approaching_post_mail_list_update.py b/smash/web/migrations/0106_approaching_post_mail_list_update.py index 2b45f359..f6d57a75 100644 --- a/smash/web/migrations/0106_approaching_post_mail_list_update.py +++ b/smash/web/migrations/0106_approaching_post_mail_list_update.py @@ -2,7 +2,7 @@ # Generated by Django 1.10.7 on 2018-02-14 10:26 -from django.db import migrations, models +from django.db import migrations from ..migration_functions import is_sqlite_db diff --git a/smash/web/migrations/0114_auto_20180611_0950.py b/smash/web/migrations/0114_auto_20180611_0950.py index 7bf9361e..b42ec0cf 100644 --- a/smash/web/migrations/0114_auto_20180611_0950.py +++ b/smash/web/migrations/0114_auto_20180611_0950.py @@ -2,16 +2,15 @@ # Generated by Django 1.10.7 on 2018-06-11 09:50 -from django.db import migrations, models -import django.db.models.deletion +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('web', '0113_auto_20180608_1258'), + ("web", "0113_auto_20180608_1258"), ] operations = [ - migrations.RunSQL('delete from web_voucherpartnersession;'), - migrations.RunSQL('delete from web_voucher;'), + migrations.RunSQL("delete from web_voucherpartnersession;"), + migrations.RunSQL("delete from web_voucher;"), ] diff --git a/smash/web/migrations/0158_configurationitem_email_items.py b/smash/web/migrations/0158_configurationitem_email_items.py index 9dd9143c..5bdd886e 100644 --- a/smash/web/migrations/0158_configurationitem_email_items.py +++ b/smash/web/migrations/0158_configurationitem_email_items.py @@ -7,12 +7,12 @@ from django.db import migrations from web.models.constants import KIT_DAILY_EMAIL_DAYS_PERIOD_TYPE, KIT_DAILY_EMAIL_TIME_FORMAT_TYPE -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0159_configurationitem_email_items_for_redcap.py b/smash/web/migrations/0159_configurationitem_email_items_for_redcap.py index bde0d0ae..970ab382 100644 --- a/smash/web/migrations/0159_configurationitem_email_items_for_redcap.py +++ b/smash/web/migrations/0159_configurationitem_email_items_for_redcap.py @@ -18,12 +18,12 @@ from web.models.constants import ( ) -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0164_configurationitem_email_items_for_redcap.py b/smash/web/migrations/0164_configurationitem_email_items_for_redcap.py index 40539225..b9a75fd4 100644 --- a/smash/web/migrations/0164_configurationitem_email_items_for_redcap.py +++ b/smash/web/migrations/0164_configurationitem_email_items_for_redcap.py @@ -7,12 +7,12 @@ from django.db import migrations from web.models.constants import RED_CAP_SAMPLE_DATE_FIELD_TYPE -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0165_configurationitem_email_virus.py b/smash/web/migrations/0165_configurationitem_email_virus.py index 1e5c201c..5942ac67 100644 --- a/smash/web/migrations/0165_configurationitem_email_virus.py +++ b/smash/web/migrations/0165_configurationitem_email_virus.py @@ -7,12 +7,12 @@ from django.db import migrations from web.models.constants import VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0166_auto_20200423_1457.py b/smash/web/migrations/0166_auto_20200423_1457.py index f2a541e1..31488847 100644 --- a/smash/web/migrations/0166_auto_20200423_1457.py +++ b/smash/web/migrations/0166_auto_20200423_1457.py @@ -6,12 +6,12 @@ from web.models.constants import VISIT_SHOW_VISIT_NUMBER_FROM_ZERO from django.db import migrations -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0168_rename_radcap_field.py b/smash/web/migrations/0168_rename_radcap_field.py index ab57c072..37be8307 100644 --- a/smash/web/migrations/0168_rename_radcap_field.py +++ b/smash/web/migrations/0168_rename_radcap_field.py @@ -6,12 +6,12 @@ from django.db import migrations from web.models.constants import RED_CAP_SAMPLE_DATE_FIELD_TYPE, RED_CAP_KIT_ID_FIELD_TYPE -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0171_configurationitem_serology.py b/smash/web/migrations/0171_configurationitem_serology.py index f5abbea1..e9ea2ed4 100644 --- a/smash/web/migrations/0171_configurationitem_serology.py +++ b/smash/web/migrations/0171_configurationitem_serology.py @@ -7,12 +7,12 @@ from django.db import migrations from web.models.constants import RED_CAP_IGA_STATUS_FIELD_TYPE, RED_CAP_IGG_STATUS_FIELD_TYPE -def create_item(apps, type, value, name): +def create_item(apps, _type, value, name): # We can't import the ConfigurationItem model directly as it may be a newer # version than this migration expects. We use the historical version. ConfigurationItem = apps.get_model("web", "ConfigurationItem") item = ConfigurationItem.objects.create() - item.type = type + item.type = _type item.value = value item.name = name item.save() diff --git a/smash/web/migrations/0173_auto_20201105_1142.py b/smash/web/migrations/0173_auto_20201105_1142.py index 9332a188..74e66773 100644 --- a/smash/web/migrations/0173_auto_20201105_1142.py +++ b/smash/web/migrations/0173_auto_20201105_1142.py @@ -1,26 +1,22 @@ # Generated by Django 2.0.13 on 2020-11-05 11:42 -import django.core.files.storage -import django.core.validators from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('web', '0172_auto_20200525_1246'), + ("web", "0172_auto_20200525_1246"), ] operations = [ migrations.AlterField( - model_name='provenance', - name='modified_table', - field=models.CharField(max_length=1024, null=True, verbose_name='Modified table'), + model_name="provenance", + name="modified_table", + field=models.CharField(max_length=1024, null=True, verbose_name="Modified table"), ), migrations.AlterField( - model_name='provenance', - name='modified_table_id', - field=models.IntegerField(default=0, null=True, verbose_name='Modified table row'), - ) + model_name="provenance", + name="modified_table_id", + field=models.IntegerField(default=0, null=True, verbose_name="Modified table row"), + ), ] diff --git a/smash/web/migrations/0174_auto_20201105_1157.py b/smash/web/migrations/0174_auto_20201105_1157.py index b6f2abbb..fdb995fe 100644 --- a/smash/web/migrations/0174_auto_20201105_1157.py +++ b/smash/web/migrations/0174_auto_20201105_1157.py @@ -1,21 +1,17 @@ # Generated by Django 2.0.13 on 2020-11-05 11:57 -import django.core.files.storage -import django.core.validators from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('web', '0173_auto_20201105_1142'), + ("web", "0173_auto_20201105_1142"), ] operations = [ migrations.AddField( - model_name='provenance', - name='request_path', - field=models.CharField(blank=True, max_length=20480, null=True, verbose_name='Request Path'), + model_name="provenance", + name="request_path", + field=models.CharField(blank=True, max_length=20480, null=True, verbose_name="Request Path"), ) ] diff --git a/smash/web/migrations/0178_auto_20201116_1250.py b/smash/web/migrations/0178_auto_20201116_1250.py index 1a8e45fe..91febe10 100644 --- a/smash/web/migrations/0178_auto_20201116_1250.py +++ b/smash/web/migrations/0178_auto_20201116_1250.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines # Generated by Django 2.0.13 on 2020-11-16 12:50 import django.core.files.storage diff --git a/smash/web/migrations/0183_auto_20201126_1154.py b/smash/web/migrations/0183_auto_20201126_1154.py index 2e2670d0..1aeea749 100644 --- a/smash/web/migrations/0183_auto_20201126_1154.py +++ b/smash/web/migrations/0183_auto_20201126_1154.py @@ -1,59 +1,57 @@ # Generated by Django 2.0.13 on 2020-11-26 11:54 -import django.core.files.storage from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('web', '0182_auto_20201126_1042'), + ("web", "0182_auto_20201126_1042"), ] operations = [ migrations.RenameField( - model_name='subjectcolumns', - old_name='next_of_keen_address', - new_name='next_of_kin_address', + model_name="subjectcolumns", + old_name="next_of_keen_address", + new_name="next_of_kin_address", ), migrations.RenameField( - model_name='subjectcolumns', - old_name='next_of_keen_name', - new_name='next_of_kin_name', + model_name="subjectcolumns", + old_name="next_of_keen_name", + new_name="next_of_kin_name", ), migrations.RenameField( - model_name='subjectcolumns', - old_name='next_of_keen_phone', - new_name='next_of_kin_phone', + model_name="subjectcolumns", + old_name="next_of_keen_phone", + new_name="next_of_kin_phone", ), migrations.RenameField( - model_name='subject', - old_name='next_of_keen_address', - new_name='next_of_kin_address', + model_name="subject", + old_name="next_of_keen_address", + new_name="next_of_kin_address", ), migrations.RenameField( - model_name='subject', - old_name='next_of_keen_name', - new_name='next_of_kin_name', + model_name="subject", + old_name="next_of_keen_name", + new_name="next_of_kin_name", ), migrations.RenameField( - model_name='subject', - old_name='next_of_keen_phone', - new_name='next_of_kin_phone', + model_name="subject", + old_name="next_of_keen_phone", + new_name="next_of_kin_phone", ), migrations.AlterField( - model_name='subject', - name='next_of_kin_address', - field=models.TextField(blank=True, max_length=2000, verbose_name='Next of kin address'), + model_name="subject", + name="next_of_kin_address", + field=models.TextField(blank=True, max_length=2000, verbose_name="Next of kin address"), ), migrations.AlterField( - model_name='subject', - name='next_of_kin_name', - field=models.CharField(blank=True, max_length=255, verbose_name='Next of kin'), + model_name="subject", + name="next_of_kin_name", + field=models.CharField(blank=True, max_length=255, verbose_name="Next of kin"), ), migrations.AlterField( - model_name='subject', - name='next_of_kin_phone', - field=models.CharField(blank=True, max_length=50, verbose_name='Next of kin phone'), + model_name="subject", + name="next_of_kin_phone", + field=models.CharField(blank=True, max_length=50, verbose_name="Next of kin phone"), ), ] diff --git a/smash/web/migrations/0194_migrate_subject_type_to_new_structure.py b/smash/web/migrations/0194_migrate_subject_type_to_new_structure.py index d1da504c..231d01ed 100644 --- a/smash/web/migrations/0194_migrate_subject_type_to_new_structure.py +++ b/smash/web/migrations/0194_migrate_subject_type_to_new_structure.py @@ -2,43 +2,49 @@ from django.db import migrations -from web.models.constants import GLOBAL_STUDY_ID -from web.models.study import FOLLOW_UP_INCREMENT_IN_YEARS -from ..migration_functions import is_sqlite_db +patient_prefix = "P" -patient_prefix = 'P' - -control_prefix = 'L' +control_prefix = "L" class Migration(migrations.Migration): dependencies = [ - ('web', '0193_subjecttype'), + ("web", "0193_subjecttype"), ] operations = [ - migrations.RunSQL("insert into web_subjecttype " + - "(name, screening_number_prefix, follow_up_delta_time, follow_up_delta_units, " - "auto_create_follow_up, study_id) " + - "select 'PATIENT' , '" + - patient_prefix + "', " + - "default_delta_time_for_patient_follow_up," + - "default_delta_time_for_follow_up_units," + - "auto_create_follow_up," + - "id from web_study"), - migrations.RunSQL("update web_studysubject " + - " set new_type_id = (select max(id) from web_subjecttype) where type = 'P'"), - migrations.RunSQL("insert into web_subjecttype " + - "(name, screening_number_prefix, follow_up_delta_time, follow_up_delta_units, " - "auto_create_follow_up, study_id) " + - "select 'CONTROL' , '" + - control_prefix + "', " + - "default_delta_time_for_control_follow_up," + - "default_delta_time_for_follow_up_units," + - "auto_create_follow_up," + - "id from web_study"), - migrations.RunSQL("update web_studysubject " + - " set new_type_id = (select max(id) from web_subjecttype) where type = 'C'"), - migrations.RunSQL("update web_studysubject " + - " set new_type_id = (select max(id) from web_subjecttype) where new_type_id is null"), + migrations.RunSQL( + "insert into web_subjecttype " + + "(name, screening_number_prefix, follow_up_delta_time, follow_up_delta_units, " + "auto_create_follow_up, study_id) " + + "select 'PATIENT' , '" + + patient_prefix + + "', " + + "default_delta_time_for_patient_follow_up," + + "default_delta_time_for_follow_up_units," + + "auto_create_follow_up," + + "id from web_study" + ), + migrations.RunSQL( + "update web_studysubject " + " set new_type_id = (select max(id) from web_subjecttype) where type = 'P'" + ), + migrations.RunSQL( + "insert into web_subjecttype " + + "(name, screening_number_prefix, follow_up_delta_time, follow_up_delta_units, " + "auto_create_follow_up, study_id) " + + "select 'CONTROL' , '" + + control_prefix + + "', " + + "default_delta_time_for_control_follow_up," + + "default_delta_time_for_follow_up_units," + + "auto_create_follow_up," + + "id from web_study" + ), + migrations.RunSQL( + "update web_studysubject " + " set new_type_id = (select max(id) from web_subjecttype) where type = 'C'" + ), + migrations.RunSQL( + "update web_studysubject " + + " set new_type_id = (select max(id) from web_subjecttype) where new_type_id is null" + ), ] diff --git a/smash/web/migrations/0212_auto_20231024_1238.py b/smash/web/migrations/0212_auto_20231024_1238.py index b161ab2e..1fc38ff2 100644 --- a/smash/web/migrations/0212_auto_20231024_1238.py +++ b/smash/web/migrations/0212_auto_20231024_1238.py @@ -5,20 +5,31 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('web', '0211_subjectexport_data'), + ("web", "0211_subjectexport_data"), ] operations = [ migrations.AlterField( - model_name='study', - name='nd_number_study_subject_regex', - field=models.CharField(default='^ND\\d{4}$', help_text='Defines the regex to check the ID used for each study subject. Keep in mind that this regex should be valid for all previous study subjects in the database.', max_length=255, verbose_name='Study Subject ND Number Regex'), + model_name="study", + name="nd_number_study_subject_regex", + field=models.CharField( + default="^ND\\d{4}$", + help_text="Defines the regex to check the ID used for each study subject." + + " Keep in mind that this regex should be valid for all previous study subjects in the database.", + max_length=255, + verbose_name="Study Subject ND Number Regex", + ), ), migrations.AlterField( - model_name='studysubject', - name='referral_letter', - field=models.FileField(blank=True, null=True, storage=django.core.files.storage.FileSystemStorage(location='/tmp/upload'), upload_to='referral_letters', verbose_name='Referral letter'), + model_name="studysubject", + name="referral_letter", + field=models.FileField( + blank=True, + null=True, + storage=django.core.files.storage.FileSystemStorage(location="/tmp/upload"), + upload_to="referral_letters", + verbose_name="Referral letter", + ), ), ] diff --git a/smash/web/officeAvailability.py b/smash/web/officeAvailability.py index 51ed62c5..f7a15419 100644 --- a/smash/web/officeAvailability.py +++ b/smash/web/officeAvailability.py @@ -259,8 +259,10 @@ class OfficeAvailability: else: xticks = self.availability.asfreq(f"{n_ticks}H").index - title = f'Availability for {self.name} from {self.start.strftime("%Y/%m/%d %H:%M")}' - ' to {self.end.strftime("%Y/%m/%d %H:%M")}' + title = ( + f'Availability for {self.name} from {self.start.strftime("%Y/%m/%d %H:%M")}' + + ' to {self.end.strftime("%Y/%m/%d %H:%M")}' + ) axes = self.availability.plot( figsize=(16, 8), diff --git a/smash/web/templatetags/filters.py b/smash/web/templatetags/filters.py index 718e488e..339bd0e3 100644 --- a/smash/web/templatetags/filters.py +++ b/smash/web/templatetags/filters.py @@ -14,30 +14,30 @@ from web.models.constants import VISIT_SHOW_VISIT_NUMBER_FROM_ZERO register = template.Library() -@register.filter(name='add_class') +@register.filter(name="add_class") def add_class(value, arg): # Get all the field's initial css-classes if isinstance(value.field.widget, CheckboxSelectMultiple): return value if isinstance(value.field.widget, CheckboxInput): - arg = 'checkbox' - css_classes = value.field.widget.attrs.get('class', ' ').split(' ') + arg = "checkbox" + css_classes = value.field.widget.attrs.get("class", " ").split(" ") # Filter out zero-length class names ('') css_classes = [x for x in css_classes if len(x) > 0] # Convert list to string css_classes = reduce(lambda a, x: f"{a} {x}", css_classes, "") - css_classes = f'{css_classes} {arg}' + css_classes = f"{css_classes} {arg}" # Return the widget with freshly crafted classes - return value.as_widget(attrs={'class': css_classes}) + return value.as_widget(attrs={"class": css_classes}) -@register.filter(name='disable') +@register.filter(name="disable") def disable(value): - value.field.widget.attrs['disabled'] = 'disabled' + value.field.widget.attrs["disabled"] = "disabled" return value -@register.filter(name='is_checkbox') +@register.filter(name="is_checkbox") def is_checkbox(value): return isinstance(value.field.widget, CheckboxSelectMultiple) @@ -46,7 +46,7 @@ def is_checkbox(value): def render_appointments(statistics, appointment_type): html = "" for status_count in statistics.get(appointment_type, []): - html += f'<td>{status_count}</td>' + html += f"<td>{status_count}</td>" return mark_safe(html) @@ -56,16 +56,16 @@ def timestamp(value): return (value.replace(tzinfo=None) - epoch).total_seconds() -@register.filter(name='display_visit_number') +@register.filter(name="display_visit_number") def display_visit_number(visit_number): visit_from_zero = ConfigurationItem.objects.get(type=VISIT_SHOW_VISIT_NUMBER_FROM_ZERO).value # True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. if strtobool(visit_from_zero): - return (visit_number - 1) + return visit_number - 1 else: return visit_number -@register.filter(name='basename') +@register.filter(name="basename") def basename(path): return os.path.basename(path) diff --git a/smash/web/tests/models/test_study_subject.py b/smash/web/tests/models/test_study_subject.py index 5bbd8a71..727366a4 100644 --- a/smash/web/tests/models/test_study_subject.py +++ b/smash/web/tests/models/test_study_subject.py @@ -4,12 +4,15 @@ from web.models import Appointment, Visit, StudySubject, Study from web.models.constants import CUSTOM_FIELD_TYPE_TEXT from web.models.custom_data import CustomStudySubjectField from web.tests.functions import create_visit, create_study -from web.tests.functions import get_test_study, create_study_subject, create_appointment, \ - create_study_subject_with_multiple_screening_numbers +from web.tests.functions import ( + get_test_study, + create_study_subject, + create_appointment, + create_study_subject_with_multiple_screening_numbers, +) class SubjectModelTests(TestCase): - def test_signal_mark_as_endpoint_reached(self): subject = create_study_subject() visit = create_visit(subject) @@ -17,14 +20,12 @@ class SubjectModelTests(TestCase): subject.endpoint_reached = True subject.save() - appointment_status = Appointment.objects.filter(id=appointment.id)[ - 0].status + appointment_status = Appointment.objects.filter(id=appointment.id)[0].status visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished self.assertTrue(subject.endpoint_reached) self.assertTrue(visit_finished) - self.assertEqual( - Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEqual(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) def test_mark_as_endpoint_reached(self): subject = create_study_subject() @@ -32,14 +33,12 @@ class SubjectModelTests(TestCase): appointment = create_appointment(visit) subject.mark_as_endpoint_reached() - appointment_status = Appointment.objects.filter(id=appointment.id)[ - 0].status + appointment_status = Appointment.objects.filter(id=appointment.id)[0].status visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished self.assertTrue(subject.endpoint_reached) self.assertTrue(visit_finished) - self.assertEqual( - Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEqual(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) def test_signal_mark_as_resigned(self): subject = create_study_subject() @@ -48,14 +47,12 @@ class SubjectModelTests(TestCase): subject.resigned = True subject.save() - appointment_status = Appointment.objects.filter(id=appointment.id)[ - 0].status + appointment_status = Appointment.objects.filter(id=appointment.id)[0].status visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished self.assertTrue(subject.resigned) self.assertTrue(visit_finished) - self.assertEqual( - Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEqual(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) def test_mark_as_resigned(self): subject = create_study_subject() @@ -63,14 +60,12 @@ class SubjectModelTests(TestCase): appointment = create_appointment(visit) subject.mark_as_resigned() - appointment_status = Appointment.objects.filter(id=appointment.id)[ - 0].status + appointment_status = Appointment.objects.filter(id=appointment.id)[0].status visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished self.assertTrue(subject.resigned) self.assertTrue(visit_finished) - self.assertEqual( - Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEqual(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) def test_signal_mark_as_excluded(self): subject = create_study_subject() @@ -79,14 +74,12 @@ class SubjectModelTests(TestCase): subject.excluded = True subject.save() - appointment_status = Appointment.objects.filter(id=appointment.id)[ - 0].status + appointment_status = Appointment.objects.filter(id=appointment.id)[0].status visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished self.assertTrue(subject.excluded) self.assertTrue(visit_finished) - self.assertEqual( - Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEqual(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) def test_mark_as_excluded(self): subject = create_study_subject() @@ -94,14 +87,12 @@ class SubjectModelTests(TestCase): appointment = create_appointment(visit) subject.mark_as_excluded() - appointment_status = Appointment.objects.filter(id=appointment.id)[ - 0].status + appointment_status = Appointment.objects.filter(id=appointment.id)[0].status visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished self.assertTrue(subject.excluded) self.assertTrue(visit_finished) - self.assertEqual( - Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEqual(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) def test_check_nd_number_regex(self): study = get_test_study() @@ -109,70 +100,83 @@ class SubjectModelTests(TestCase): # delete everything StudySubject.objects.all().delete() # get default regex - nd_number_study_subject_regex_default = Study._meta.get_field( - 'nd_number_study_subject_regex').get_default() + nd_number_study_subject_regex_default = Study._meta.get_field("nd_number_study_subject_regex").get_default() self.assertTrue(StudySubject.check_nd_number_regex(nd_number_study_subject_regex_default, study)) # this will add a studysubject with a NDnumber - create_study_subject(nd_number='ND0001') + create_study_subject(nd_number="ND0001") self.assertTrue(StudySubject.check_nd_number_regex(nd_number_study_subject_regex_default, study)) # delete everything StudySubject.objects.all().delete() # this will add a studysubject with a NDnumber - create_study_subject(nd_number='ND00001') + create_study_subject(nd_number="ND00001") self.assertFalse(StudySubject.check_nd_number_regex(nd_number_study_subject_regex_default, study)) def test_sort_matched_screening_first(self): def create_result(phrase, subject_id=1): phrase = phrase.format(subject_id=subject_id) - phrase = phrase.split(';') + phrase = phrase.split(";") for i, _ in enumerate(phrase): - letter, num = phrase[i].strip().split('-') + letter, num = _.strip().split("-") phrase[i] = (letter, int(num)) return phrase - subject = create_study_subject_with_multiple_screening_numbers( - subject_id=1) - self.assertEqual(subject.sort_matched_screening_first('L'), create_result( - 'L-00{subject_id}; E-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first( - 'L-00'), create_result('L-00{subject_id}; E-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('E'), create_result( - 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first( - '-'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first(''), create_result( - 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('001'), create_result( - 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('00'), create_result( - 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) - self.assertEqual(subject.sort_matched_screening_first('potato'), create_result( - 'E-00{subject_id}; L-00{subject_id}', subject_id=1)) + subject = create_study_subject_with_multiple_screening_numbers(subject_id=1) + self.assertEqual( + subject.sort_matched_screening_first("L"), create_result("L-00{subject_id}; E-00{subject_id}", subject_id=1) + ) + self.assertEqual( + subject.sort_matched_screening_first("L-00"), + create_result("L-00{subject_id}; E-00{subject_id}", subject_id=1), + ) + self.assertEqual( + subject.sort_matched_screening_first("E"), create_result("E-00{subject_id}; L-00{subject_id}", subject_id=1) + ) + self.assertEqual( + subject.sort_matched_screening_first("-"), create_result("E-00{subject_id}; L-00{subject_id}", subject_id=1) + ) + self.assertEqual( + subject.sort_matched_screening_first(""), create_result("E-00{subject_id}; L-00{subject_id}", subject_id=1) + ) + self.assertEqual( + subject.sort_matched_screening_first("001"), + create_result("E-00{subject_id}; L-00{subject_id}", subject_id=1), + ) + self.assertEqual( + subject.sort_matched_screening_first("00"), + create_result("E-00{subject_id}; L-00{subject_id}", subject_id=1), + ) + self.assertEqual( + subject.sort_matched_screening_first("potato"), + create_result("E-00{subject_id}; L-00{subject_id}", subject_id=1), + ) def test_sort_matched_screening_first_bad_number(self): subject_id = 2 - screening_number = f'L-0/0{subject_id}; E-00{subject_id}; F-0/1{subject_id}' + screening_number = f"L-0/0{subject_id}; E-00{subject_id}; F-0/1{subject_id}" subject = create_study_subject_with_multiple_screening_numbers( - subject_id=subject_id, screening_number=screening_number) + subject_id=subject_id, screening_number=screening_number + ) - self.assertEqual(subject.sort_matched_screening_first('E-'), [('E', subject_id), ('F', '0/12'), ('L', '0/02')]) + self.assertEqual(subject.sort_matched_screening_first("E-"), [("E", subject_id), ("F", "0/12"), ("L", "0/02")]) - self.assertEqual(subject.sort_matched_screening_first('L-'), [('L', '0/02'), ('E', subject_id), ('F', '0/12')]) + self.assertEqual(subject.sort_matched_screening_first("L-"), [("L", "0/02"), ("E", subject_id), ("F", "0/12")]) def test_sort_matched_screening_first_bad_format(self): - screening_number = 'potato; tomato' + screening_number = "potato; tomato" subject_id = 3 subject = create_study_subject_with_multiple_screening_numbers( - subject_id=subject_id, screening_number=screening_number) + subject_id=subject_id, screening_number=screening_number + ) - self.assertEqual(subject.sort_matched_screening_first('L-'), [('potato', None), ('tomato', None)]) + self.assertEqual(subject.sort_matched_screening_first("L-"), [("potato", None), ("tomato", None)]) def test_subject_with_custom_field(self): - field = CustomStudySubjectField.objects.create(study=get_test_study(), default_value="xyz", - type=CUSTOM_FIELD_TYPE_TEXT) + field = CustomStudySubjectField.objects.create( + study=get_test_study(), default_value="xyz", type=CUSTOM_FIELD_TYPE_TEXT + ) subject = create_study_subject() @@ -183,8 +187,9 @@ class SubjectModelTests(TestCase): self.assertEqual(field, value.study_subject_field) def test_subject_with_removed_custom_field(self): - field = CustomStudySubjectField.objects.create(study=get_test_study(), default_value="xyz", - type=CUSTOM_FIELD_TYPE_TEXT) + field = CustomStudySubjectField.objects.create( + study=get_test_study(), default_value="xyz", type=CUSTOM_FIELD_TYPE_TEXT + ) subject = create_study_subject() self.assertEqual(1, len(subject.custom_data_values)) @@ -193,7 +198,7 @@ class SubjectModelTests(TestCase): self.assertEqual(0, len(subject.custom_data_values)) def test_subject_with_field_from_different_study(self): - field = CustomStudySubjectField.objects.create(study=create_study('bla'), type=CUSTOM_FIELD_TYPE_TEXT) + field = CustomStudySubjectField.objects.create(study=create_study("bla"), type=CUSTOM_FIELD_TYPE_TEXT) subject = create_study_subject() self.assertIsNone(subject.get_custom_data_value(field)) @@ -201,7 +206,11 @@ class SubjectModelTests(TestCase): def test_status(self): subject = create_study_subject() # noinspection PySetFunctionToLiteral - statuses = set([subject.status, ]) + statuses = set( + [ + subject.status, + ] + ) subject.endpoint_reached = True statuses.add(subject.status) subject.excluded = True diff --git a/smash/web/tests/view/test_privacy_notice.py b/smash/web/tests/view/test_privacy_notice.py index abd22b6f..54ec848a 100644 --- a/smash/web/tests/view/test_privacy_notice.py +++ b/smash/web/tests/view/test_privacy_notice.py @@ -16,19 +16,14 @@ class PrivacyNoticeTests(LoggedInTestCase): self.assertEqual(0, PrivacyNotice.objects.count()) self.login_as_admin() - form_data = dict( - name='example', - summary='example summary' - ) + form_data = {"name": "example", "summary": "example summary"} - file_data = dict( - document=SimpleUploadedFile('file.txt', b"file_content") - ) + file_data = {"document": SimpleUploadedFile("file.txt", b"file_content")} form = PrivacyNoticeForm(form_data, file_data) self.assertTrue(form.is_valid()) - page = reverse('web.views.privacy_notice_add') + page = reverse("web.views.privacy_notice_add") response = self.client.post(page, data={**form_data, **file_data}) self.assertEqual(response.status_code, 302) self.assertEqual(1, PrivacyNotice.objects.count()) @@ -42,28 +37,23 @@ class PrivacyNoticeTests(LoggedInTestCase): def test_edit_privacy_notice(self): self.assertEqual(1, PrivacyNotice.objects.count()) pn = PrivacyNotice.objects.all()[0] - form_data = dict( - name='example2', - summary=pn.summary - ) + form_data = {"name": "example2", "summary": pn.summary} - file_data = dict( - document=SimpleUploadedFile('file.txt', b"file_content") - ) + file_data = {"document": SimpleUploadedFile("file.txt", b"file_content")} form = PrivacyNoticeForm(form_data, file_data, instance=pn) self.assertTrue(form.is_valid()) - page = reverse('web.views.privacy_notice_edit', kwargs={'pk': pn.id}) + page = reverse("web.views.privacy_notice_edit", kwargs={"pk": pn.id}) response = self.client.post(page, data={**form_data, **file_data}) self.assertEqual(response.status_code, 302) pn = PrivacyNotice.objects.all()[0] - self.assertEqual(pn.name, 'example2') + self.assertEqual(pn.name, "example2") def test_delete_privacy_notice(self): self.assertEqual(1, PrivacyNotice.objects.count()) pn = PrivacyNotice.objects.all()[0] - page = reverse('web.views.privacy_notice_delete', kwargs={'pk': pn.id}) + page = reverse("web.views.privacy_notice_delete", kwargs={"pk": pn.id}) response = self.client.post(page) self.assertEqual(response.status_code, 302) self.assertEqual(0, PrivacyNotice.objects.count()) @@ -83,7 +73,7 @@ class PrivacyNoticeTests(LoggedInTestCase): self.login_as_super() self.assertEqual(self.staff_worker.privacy_notice_accepted, False) - page = reverse('web.views.appointments') + page = reverse("web.views.appointments") response = self.client.get(page) self.assertEqual(response.status_code, 200) messages = list(get_messages(response.wsgi_request)) @@ -104,17 +94,17 @@ class PrivacyNoticeTests(LoggedInTestCase): self.login_as_staff() self.assertEqual(self.staff_worker.privacy_notice_accepted, False) - page = reverse('web.views.appointments') + page = reverse("web.views.appointments") response = self.client.get(page) self.assertEqual(response.status_code, 302) messages = list(get_messages(response.wsgi_request)) self.assertEqual(len(messages), 1) self.assertEqual(str(messages[0]), "You can't use the system until you accept the privacy notice.") # accept privacy notice - form_data = dict(privacy_notice_accepted=True) + form_data = {"privacy_notice_accepted": True} form = WorkerAcceptPrivacyNoticeForm(form_data) self.assertTrue(form.is_valid()) - page = reverse('web.views.accept_privacy_notice', kwargs={'pk': pn.id}) + page = reverse("web.views.accept_privacy_notice", kwargs={"pk": pn.id}) response = self.client.post(page, data={**form_data}) self.assertEqual(response.status_code, 302) messages = [m.message for m in get_messages(response.wsgi_request)] @@ -122,7 +112,7 @@ class PrivacyNoticeTests(LoggedInTestCase): # check acceptance worker = Worker.objects.filter(id=self.staff_worker.id).first() self.assertEqual(worker.privacy_notice_accepted, True) - page = reverse('web.views.appointments') + page = reverse("web.views.appointments") response = self.client.get(page) self.assertEqual(response.status_code, 200) messages = list(get_messages(response.wsgi_request)) diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py index aca4e134..84106125 100644 --- a/smash/web/views/subject.py +++ b/smash/web/views/subject.py @@ -159,7 +159,7 @@ def subject_edit(request, subject_id): if "type" in study_subject_form.changed_data and old_type != study_subject_form.cleaned_data["type"]: worker = Worker.get_by_user(request.user) - old_value = old_type.name + # old_value = old_type.name new_value = None if study_subject_form.cleaned_data["type"] is not None: new_value = study_subject_form.cleaned_data["type"].name diff --git a/smash/web/views/virus_mail.py b/smash/web/views/virus_mail.py index 98064be6..a9e8e3b5 100644 --- a/smash/web/views/virus_mail.py +++ b/smash/web/views/virus_mail.py @@ -7,8 +7,7 @@ from django.db.models import When, Case, Min from django_cron import CronJobBase, Schedule from web.models import ConfigurationItem -from web.models.constants import VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE, \ - CRON_JOB_TIMEOUT, GLOBAL_STUDY_ID +from web.models.constants import VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT, GLOBAL_STUDY_ID from ..models import StudySubject, Worker from ..models.custom_data import CustomStudySubjectField from ..smash_email import EmailSender @@ -20,24 +19,44 @@ def count_subjects(date_when: datetime, status: str) -> int: result = 0 str_date = date_when.strftime("%Y-%m-%d") for i in range(0, 10): - fields = CustomStudySubjectField.objects.filter(study_id=GLOBAL_STUDY_ID, - name=f'Visit {i} RT-PCR collection date').all() + fields = CustomStudySubjectField.objects.filter( + study_id=GLOBAL_STUDY_ID, name=f"Visit {i} RT-PCR collection date" + ).all() collect_field = None if len(fields) > 0: collect_field = fields[0] - fields = CustomStudySubjectField.objects.filter(study_id=GLOBAL_STUDY_ID, - name__contains=f'Virus {i} RT-PCR').all() + fields = CustomStudySubjectField.objects.filter( + study_id=GLOBAL_STUDY_ID, name__contains=f"Virus {i} RT-PCR" + ).all() status_field = None if len(fields) > 0: status_field = fields[0] if collect_field is not None and status_field is not None: - print('exist') - result += StudySubject.objects.annotate( - custom_field_update_value=Min(Case(When(customstudysubjectvalue__study_subject_field=collect_field, - then='customstudysubjectvalue__value')))).annotate( - custom_field_status_value=Min(Case(When(customstudysubjectvalue__study_subject_field=status_field, - then='customstudysubjectvalue__value')))).filter( - custom_field_update_value=str_date, custom_field_status_value=status).count() + print("exist") + result += ( + StudySubject.objects.annotate( + custom_field_update_value=Min( + Case( + When( + customstudysubjectvalue__study_subject_field=collect_field, + then="customstudysubjectvalue__value", + ) + ) + ) + ) + .annotate( + custom_field_status_value=Min( + Case( + When( + customstudysubjectvalue__study_subject_field=status_field, + then="customstudysubjectvalue__value", + ) + ) + ) + ) + .filter(custom_field_update_value=str_date, custom_field_status_value=status) + .count() + ) return result @@ -48,9 +67,12 @@ def get_subject_statistics(): subjects_negative_count = count_subjects(date_from, "Negative") subjects_inconclusive_count = count_subjects(date_from, "Inconclusive") - return {"Positive": subjects_positive_count, "Negative": subjects_negative_count, - "Inconclusive": subjects_inconclusive_count, - "total": subjects_positive_count + subjects_negative_count + subjects_inconclusive_count} + return { + "Positive": subjects_positive_count, + "Negative": subjects_negative_count, + "Inconclusive": subjects_inconclusive_count, + "total": subjects_positive_count + subjects_negative_count + subjects_inconclusive_count, + } def create_statistic_email_content(data, title): @@ -70,19 +92,24 @@ def create_statistic_email_content(data, title): for status in data: if status != "total": + # pylint: disable-next=consider-using-f-string email_body += """ <tr style="border: 1px solid black;"> <td style="border: 1px solid black;">{}</td> <td style="border: 1px solid black; text-align: right;">{}</td> </tr> - """.format(status, data[status]) - + """.format( + status, data[status] + ) + # pylint: disable-next=consider-using-f-string email_body += """ <tr style="border: 1px solid black;"> <td style="border: 1px solid black;">{}</td> <td style="border: 1px solid black; text-align: right;">{}</td> </tr> - """.format('Total', data["total"]) + """.format( + "Total", data["total"] + ) email_body += "</table>" @@ -107,7 +134,7 @@ def send_mail(data): if worker.email is not None and worker.email != "": recipients.append(worker.email) cc_recipients = [] - logger.warning('Calling to send email') + logger.warning("Calling to send email") EmailSender.send_email(title, email_body, ";".join(recipients), cc_recipients) @@ -118,14 +145,14 @@ class KitRequestEmailSendJob(CronJobBase): try: times = ConfigurationItem.objects.get(type=VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(";") for entry in times: - if entry != '': + if entry != "": # it's a hack assuming that we are in CEST text = str((int(entry.split(":")[0]) + 22) % 24) + ":" + entry.split(":")[1] RUN_AT.append(text) except BaseException as e: logger.debug("Cannot fetch data about email hour") schedule = Schedule(run_at_times=RUN_AT) - code = 'web.virus_daily_email' # a unique code + code = "web.virus_daily_email" # a unique code # pylint: disable=no-self-use @timeout_decorator.timeout(CRON_JOB_TIMEOUT) -- GitLab