diff --git a/smash/web/admin.py b/smash/web/admin.py index 303aa56d09cf241230750c164e8abb6424dedea9..91037bb878f4658e2f3142ee72a6b1878ddff938 100644 --- a/smash/web/admin.py +++ b/smash/web/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin -from models import StudySubject, Item, Room, AppointmentType, Language, Location, Worker, FlyingTeam, Availability, Holiday, \ - Visit, Appointment +from models import StudySubject, Item, Room, AppointmentType, Language, Location, Worker, FlyingTeam, Availability, \ + Holiday, Visit, Appointment class LanguageAdmin(admin.ModelAdmin): diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py index 93f32c2839d8ea0448c8cc88324cb2630ccb2aec..b6eb174006ab53e076b1e057e6deee497419c46c 100644 --- a/smash/web/api_urls.py +++ b/smash/web/api_urls.py @@ -34,6 +34,8 @@ urlpatterns = [ url(r'^cities$', subject.cities, name='web.api.cities'), url(r'^referrals$', subject.referrals, name='web.api.referrals'), url(r'^subjects/(?P<type>[A-z]+)$', subject.subjects, name='web.api.subjects'), + url(r'^subjects:columns/(?P<subject_list_type>[A-z]+)$', subject.get_subject_columns, + name='web.api.subjects.columns'), url(r'^subject_types', subject.types, name='web.api.subject_types'), # locations diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py index 14a96d62d4423f0343255cc896301db479169dc1..331c331c50b6cecc4f17070235448222c07d7dda 100644 --- a/smash/web/api_views/appointment.py +++ b/smash/web/api_views/appointment.py @@ -91,7 +91,9 @@ def serialize_appointment(appointment): subject_string = study_subject.subject.last_name + " " + study_subject.subject.first_name nd_number = study_subject.nd_number screening_number = study_subject.screening_number - phone_numbers = ", ".join(filter(None, [study_subject.subject.phone_number, study_subject.subject.phone_number_2, study_subject.subject.phone_number_3])) + phone_numbers = ", ".join(filter(None, + [study_subject.subject.phone_number, study_subject.subject.phone_number_2, + study_subject.subject.phone_number_3])) appointment_types = ", ".join([unicode(type) for type in appointment.appointment_types.all()]) else: title = appointment.comment diff --git a/smash/web/api_views/daily_planning.py b/smash/web/api_views/daily_planning.py index bd739c5c5569aabffc3ed9b05c9164e3cd9d5c07..cca2057d53c58f1e41114d90968e1026ac759a51 100644 --- a/smash/web/api_views/daily_planning.py +++ b/smash/web/api_views/daily_planning.py @@ -73,15 +73,13 @@ def remove_holidays(availability, worker, date): holidays_ending_today = Holiday.objects.filter(person=worker, datetime_end__lte=today_end, datetime_end__gte=today_start) result = [] - timestamps = [] - timestamps.append({ + timestamps = [{ "time": availability.available_from, "type": "start" - }) - timestamps.append({ + }, { "time": availability.available_till, "type": "stop" - }) + }] direction = -holidays_starting_before.count() diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py index c47bd91d42c9c2c5513db6ea31f31b6523072786..170367b0a5999c0cd91a42329efd0b83f08cf6ab 100644 --- a/smash/web/api_views/subject.py +++ b/smash/web/api_views/subject.py @@ -5,11 +5,12 @@ from django.db.models import Count, Case, When, Min from django.db.models import Q from django.http import JsonResponse -from web.models import StudySubject, Visit, Appointment, Subject -from web.models.constants import SUBJECT_TYPE_CHOICES +from web.models import StudySubject, Visit, Appointment, Subject, SubjectColumns, StudyColumns, Study +from web.models.constants import SUBJECT_TYPE_CHOICES, GLOBAL_STUDY_ID +from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \ + StudySubjectList from web.views import e500_error from web.views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder, get_today_midnight_date -from web.views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT logger = logging.getLogger(__name__) @@ -30,6 +31,59 @@ def referrals(request): }) +def add_column(result, name, field_name, column_list, param, columns_used_in_study=None): + add = True + if columns_used_in_study: + add = getattr(columns_used_in_study, field_name) + if add: + if column_list is None: + visible = True + else: + visible = getattr(column_list, field_name) + result.append({ + "type": field_name, + "name": name, + "filter": param, + "visible": visible + }) + + +@login_required +def get_subject_columns(request, subject_list_type): + study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] + study_subject_list = StudySubjectList.objects.filter(study=study, type=subject_list_type) + if len(study_subject_list) > 0: + subject_columns = study_subject_list[0].visible_subject_columns + study_subject_columns = study_subject_list[0].visible_subject_study_columns + else: + subject_columns = SubjectColumns() + study_subject_columns = StudyColumns() + + result = [] + add_column(result, "ND", "nd_number", study_subject_columns, "string_filter", study.columns) + add_column(result, "Screening", "screening_number", study_subject_columns, "string_filter", study.columns) + add_column(result, "First name", "first_name", subject_columns, "string_filter") + add_column(result, "Last name", "last_name", subject_columns, "string_filter") + add_column(result, "Date of birth", "date_born", subject_columns, None) + add_column(result, "Location", "default_location", study_subject_columns, "location_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, "Postponed", "postponed", 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, "Type", "type", study_subject_columns, "type_filter", study.columns) + add_column(result, "Edit", "edit", None, None) + add_column(result, "Visit 1", "visit_1", None, "visit_filter") + add_column(result, "Visit 2", "visit_2", None, "visit_filter") + add_column(result, "Visit 3", "visit_3", None, "visit_filter") + add_column(result, "Visit 4", "visit_4", None, "visit_filter") + add_column(result, "Visit 5", "visit_5", None, "visit_filter") + add_column(result, "Visit 6", "visit_6", None, "visit_filter") + add_column(result, "Visit 7", "visit_7", None, "visit_filter") + add_column(result, "Visit 8", "visit_8", None, "visit_filter") + + return JsonResponse({"columns": result}) + + @login_required def get_subjects(request, type): if type == SUBJECT_LIST_GENERIC: diff --git a/smash/web/docx_helper.py b/smash/web/docx_helper.py index fb78711c12cf62d7129610e3417785a2039ed796..49c2fc87105b7722a9ab341dd37f4b90fe67242b 100644 --- a/smash/web/docx_helper.py +++ b/smash/web/docx_helper.py @@ -1,5 +1,9 @@ +import logging + from docx import Document +logger = logging.getLogger(__name__) + def process_file(path_to_docx, path_to_new_docx, changes_to_apply): """ diff --git a/smash/web/forms/__init__.py b/smash/web/forms/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..41432b74e56c6ac250f4f182d9654a39f6f7f772 --- /dev/null +++ b/smash/web/forms/__init__.py @@ -0,0 +1,11 @@ +from forms import WorkerAddForm, \ + WorkerEditForm, AppointmentDetailForm, AppointmentEditForm, AppointmentAddForm, VisitDetailForm, VisitAddForm, \ + ContactAttemptForm, ContactAttemptEditForm, KitRequestForm, StatisticsForm, AvailabilityAddForm, \ + AvailabilityEditForm, HolidayAddForm +from study_subject_forms import StudySubjectAddForm, StudySubjectDetailForm, StudySubjectEditForm +from subject_forms import SubjectAddForm, SubjectEditForm, SubjectDetailForm + +__all__ = [StudySubjectAddForm, StudySubjectDetailForm, StudySubjectEditForm, WorkerAddForm, WorkerEditForm, + AppointmentDetailForm, AppointmentEditForm, AppointmentAddForm, VisitDetailForm, VisitAddForm, + ContactAttemptForm, ContactAttemptEditForm, KitRequestForm, StatisticsForm, AvailabilityAddForm, + AvailabilityEditForm, HolidayAddForm, SubjectAddForm, SubjectEditForm, SubjectDetailForm] diff --git a/smash/web/forms.py b/smash/web/forms/forms.py similarity index 67% rename from smash/web/forms.py rename to smash/web/forms/forms.py index ade762d5d0f7bb3bfdfd76840d90fb337bf1877c..b33eb52d2f19cde653eb450aa2732a97e241aba6 100644 --- a/smash/web/forms.py +++ b/smash/web/forms/forms.py @@ -6,10 +6,9 @@ from django import forms from django.forms import ModelForm, Form from django.utils.dates import MONTHS -from web.models import Subject, StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, \ - AppointmentTypeLink, \ +from web.models import StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, AppointmentTypeLink, \ Availability, Holiday -from web.models.constants import SUBJECT_TYPE_CHOICES, SCREENING_NUMBER_PREFIXES_FOR_TYPE, COUNTRY_OTHER_ID +from web.models.constants import SUBJECT_TYPE_CHOICES from web.views.notifications import get_filter_locations """ @@ -40,138 +39,14 @@ APPOINTMENT_TYPES_FIELD_POSITION = 1 logger = logging.getLogger(__name__) -def validate_subject_nd_number(self, cleaned_data): - if cleaned_data['nd_number'] != "": - subjects_from_db = StudySubject.objects.filter(nd_number=cleaned_data['nd_number']) - if subjects_from_db: - if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''): - self.add_error('nd_number', "ND number already in use") - - -def validate_subject_country(self, cleaned_data): - if cleaned_data['country'].id == COUNTRY_OTHER_ID: - self.add_error('country', "Select valid country") - - -def validate_subject_resign_reason(self, cleaned_data): - if cleaned_data['resigned'] and cleaned_data['resign_reason'] == '': - self.add_error('resign_reason', "Resign reason cannot be empty") - - -def validate_subject_mpower_number(self, cleaned_data): - if cleaned_data['mpower_id'] != "": - subjects_from_db = StudySubject.objects.filter(mpower_id=cleaned_data['mpower_id']) - if subjects_from_db: - if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''): - self.add_error('mpower_id', "mPower number already in use") - - -class StudySubjectAddForm(ModelForm): - datetime_contact_reminder = forms.DateTimeField(label="Contact on", - widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), - required=False - ) - - class Meta: - model = StudySubject - fields = '__all__' - exclude = ['resigned', 'resign_reason'] - - def __init__(self, *args, **kwargs): - user = kwargs.pop('user', None) - if user is None: - raise TypeError("User not defined") - self.user = Worker.get_by_user(user) - if self.user is None: - raise TypeError("Worker not defined for: " + user.username) - - super(ModelForm, self).__init__(*args, **kwargs) - self.fields['screening_number'].required = False - - def build_screening_number(self, cleaned_data): - 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: - screening_number = get_new_screening_number(prefix_screening_number) - return screening_number - - def clean(self): - cleaned_data = super(StudySubjectAddForm, self).clean() - screening_number = self.build_screening_number(cleaned_data) - if screening_number is not None: - cleaned_data['screening_number'] = screening_number - subjects_from_db = StudySubject.objects.filter(screening_number=screening_number) - - if len(subjects_from_db) > 0: - self.add_error('screening_number', "Screening number already in use") - validate_subject_nd_number(self, cleaned_data) - validate_subject_mpower_number(self, cleaned_data) - return cleaned_data - - def get_prefix_screening_number(self): - 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) - if subject_type is not None: - screening_number_prefix = SCREENING_NUMBER_PREFIXES_FOR_TYPE[subject_type] - if screening_number_prefix is None: - return None - prefix_screening_number = screening_number_prefix + "-" - return prefix_screening_number - - -def get_new_screening_number(screening_number_prefix): - result_number = 0 - subjects = StudySubject.objects.filter(screening_number__contains=screening_number_prefix) - for subject in subjects: - screening_numbers = subject.screening_number.split(";") - for screening_number in screening_numbers: - screening_number = screening_number.strip() - if screening_number.startswith(screening_number_prefix): - number = screening_number[len(screening_number_prefix):] - try: - result_number = max(result_number, int(number)) - except ValueError: - pass - - return screening_number_prefix + str(result_number + 1).zfill(3) - - -class StudySubjectDetailForm(ModelForm): - class Meta: - model = StudySubject - fields = '__all__' - - -class StudySubjectEditForm(ModelForm): - datetime_contact_reminder = forms.DateTimeField(label="Contact on", - widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), - required=False - ) - - def __init__(self, *args, **kwargs): - was_resigned = kwargs.get('was_resigned', False) - if 'was_resigned' in kwargs: - kwargs.pop('was_resigned') - super(StudySubjectEditForm, self).__init__(*args, **kwargs) - instance = getattr(self, 'instance', None) - if instance and instance.id: - self.fields['screening_number'].widget.attrs['readonly'] = True - if was_resigned: - self.fields['resigned'].disabled = True - - def clean(self): - validate_subject_nd_number(self, self.cleaned_data) - validate_subject_mpower_number(self, self.cleaned_data) - validate_subject_resign_reason(self, self.cleaned_data) - - class Meta: - model = StudySubject - fields = '__all__' +def get_worker_from_args(kwargs): + user = kwargs.pop('user', None) + if user is None: + raise TypeError("User not defined") + result = Worker.get_by_user(user) + if result is None: + raise TypeError("Worker not defined for: " + user.username) + return result class WorkerAddForm(ModelForm): @@ -474,7 +349,7 @@ class AvailabilityEditForm(ModelForm): worker = Worker.objects.get(id=self.cleaned_data["person"].id) availabilities = worker.availability_set.all() for availability in availabilities: - if availability.id <> self.availability_id: + if availability.id != self.availability_id: validate_availability_conflict(self, self.cleaned_data, availability) @@ -483,9 +358,9 @@ def validate_availability_conflict(self, cleaned_data, availability): end_hour = self.cleaned_data.get("available_till", None) if availability.day_number == self.cleaned_data.get("day_number", None) and \ ((start_hour <= availability.available_from < end_hour) or - (start_hour < availability.available_till <= end_hour) or - (availability.available_from <= start_hour < availability.available_till) or - (availability.available_from < end_hour <= availability.available_till)): + (start_hour < availability.available_till <= end_hour) or + (availability.available_from <= start_hour < availability.available_till) or + (availability.available_from < end_hour <= availability.available_till)): error = "User has defined availability for this day that overlaps: " + availability.available_from.strftime( DATE_FORMAT_TIME) + ", " + availability.available_till.strftime(DATE_FORMAT_TIME) self.add_error('day_number', error) @@ -510,48 +385,3 @@ class HolidayAddForm(ModelForm): availabilities = worker.availability_set.all() for availability in availabilities: validate_availability_conflict(self, self.cleaned_data, availability) - - -class SubjectAddForm(ModelForm): - date_born = forms.DateField(label="Date of birth", - widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), - required=False - ) - - class Meta: - model = Subject - fields = '__all__' - exclude = ['dead'] - - def clean(self): - cleaned_data = super(SubjectAddForm, self).clean() - validate_subject_country(self, cleaned_data) - return cleaned_data - - -class SubjectEditForm(ModelForm): - date_born = forms.DateField(label="Date of birth", - widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), - required=False - ) - - def __init__(self, *args, **kwargs): - was_dead = kwargs.get('was_dead', False) - if 'was_dead' in kwargs: - kwargs.pop('was_dead') - super(SubjectEditForm, self).__init__(*args, **kwargs) - if was_dead: - self.fields['dead'].disabled = True - - def clean(self): - validate_subject_country(self, self.cleaned_data) - - class Meta: - model = Subject - fields = '__all__' - - -class SubjectDetailForm(ModelForm): - class Meta: - model = Subject - fields = '__all__' diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py new file mode 100644 index 0000000000000000000000000000000000000000..aacd999186c4d76bd08a7545369d488530456558 --- /dev/null +++ b/smash/web/forms/study_subject_forms.py @@ -0,0 +1,204 @@ +import logging +import re + +from django import forms +from django.forms import ModelForm + +from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, get_worker_from_args +from web.models import StudySubject, Study, StudyColumns +from web.models.constants import SCREENING_NUMBER_PREFIXES_FOR_TYPE + +logger = logging.getLogger(__name__) + + +class StudySubjectAddForm(ModelForm): + datetime_contact_reminder = forms.DateTimeField(label="Contact on", + widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), + required=False + ) + + class Meta: + model = StudySubject + fields = '__all__' + exclude = ['resigned', 'resign_reason'] + + def __init__(self, *args, **kwargs): + self.user = get_worker_from_args(kwargs) + self.study = get_study_from_args(kwargs) + + super(ModelForm, self).__init__(*args, **kwargs) + prepare_study_subject_fields(fields=self.fields, study=self.study) + + def save(self, commit=True): + self.instance.study_id = self.study.id + return super(ModelForm, self).save(commit) + + def build_screening_number(self, cleaned_data): + 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: + screening_number = get_new_screening_number(prefix_screening_number) + return screening_number + + def clean(self): + cleaned_data = super(StudySubjectAddForm, self).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 + validate_subject_screening_number(self, cleaned_data) + validate_subject_nd_number(self, cleaned_data) + validate_subject_mpower_number(self, cleaned_data) + return cleaned_data + + def get_prefix_screening_number(self): + 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) + if subject_type is not None: + screening_number_prefix = SCREENING_NUMBER_PREFIXES_FOR_TYPE[subject_type] + if screening_number_prefix is None: + return None + prefix_screening_number = screening_number_prefix + "-" + return prefix_screening_number + + +def get_new_screening_number(screening_number_prefix): + result_number = 0 + subjects = StudySubject.objects.filter(screening_number__contains=screening_number_prefix) + for subject in subjects: + screening_numbers = subject.screening_number.split(";") + for screening_number in screening_numbers: + screening_number = screening_number.strip() + if screening_number.startswith(screening_number_prefix): + number = screening_number[len(screening_number_prefix):] + try: + result_number = max(result_number, int(number)) + except ValueError: + pass + + return screening_number_prefix + str(result_number + 1).zfill(3) + + +class StudySubjectDetailForm(ModelForm): + class Meta: + model = StudySubject + fields = '__all__' + + def __init__(self, *args, **kwargs): + super(StudySubjectDetailForm, self).__init__(*args, **kwargs) + instance = getattr(self, 'instance', None) + + self.study = get_study_from_study_subject_instance(instance) + + prepare_study_subject_fields(fields=self.fields, study=self.study) + + +def get_study_from_study_subject_instance(study_subject): + if study_subject and study_subject.study_id: + return Study.objects.filter(id=study_subject.study_id)[0] + else: + return Study(columns=StudyColumns()) + + +class StudySubjectEditForm(ModelForm): + datetime_contact_reminder = forms.DateTimeField(label="Contact on", + widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), + required=False + ) + + def __init__(self, *args, **kwargs): + was_resigned = kwargs.get('was_resigned', False) + if 'was_resigned' in kwargs: + kwargs.pop('was_resigned') + super(StudySubjectEditForm, self).__init__(*args, **kwargs) + instance = getattr(self, 'instance', None) + if instance and instance.id: + self.fields['screening_number'].widget.attrs['readonly'] = True + self.study = get_study_from_study_subject_instance(instance) + + if was_resigned: + self.fields['resigned'].disabled = True + + prepare_study_subject_fields(fields=self.fields, study=self.study) + + def clean(self): + validate_subject_nd_number(self, self.cleaned_data) + validate_subject_mpower_number(self, self.cleaned_data) + validate_subject_resign_reason(self, self.cleaned_data) + + class Meta: + model = StudySubject + fields = '__all__' + + +def get_study_from_args(kwargs): + study = kwargs.pop('study', None) + if study is None: + raise TypeError("Study not defined") + return study + + +def prepare_field(fields, visible_columns, field_name, required=False): + if not getattr(visible_columns, field_name) and field_name in fields: + del fields[field_name] + elif required: + fields[field_name].required = True + + +def prepare_study_subject_fields(fields, 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, 'mpower_id') + prepare_field(fields, study.columns, 'comments') + prepare_field(fields, study.columns, 'referral') + prepare_field(fields, study.columns, 'diagnosis') + prepare_field(fields, study.columns, 'year_of_diagnosis') + prepare_field(fields, study.columns, 'information_sent') + prepare_field(fields, study.columns, 'pd_in_family') + prepare_field(fields, study.columns, 'resigned') + prepare_field(fields, study.columns, 'resign_reason') + + +def validate_subject_screening_number(self, cleaned_data): + if self.study.columns.resign_reason: + 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") + + +def validate_subject_nd_number(self, cleaned_data): + if self.study.columns.nd_number: + nd_number = cleaned_data['nd_number'] + if nd_number != "": + if re.match('ND[0-9][0-9][0-9][0-9]', nd_number) is None: + self.add_error('nd_number', "Invalid ND number") + else: + 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', "ND number already in use") + + +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") + + +def validate_subject_mpower_number(self, cleaned_data): + if self.study.columns.mpower_id: + if cleaned_data['mpower_id'] != "": + subjects_from_db = StudySubject.objects.filter(mpower_id=cleaned_data['mpower_id']) + if subjects_from_db: + if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''): + self.add_error('mpower_id', "mPower number already in use") diff --git a/smash/web/forms/subject_forms.py b/smash/web/forms/subject_forms.py new file mode 100644 index 0000000000000000000000000000000000000000..008ab88238188f249a20755f215103846a57f11c --- /dev/null +++ b/smash/web/forms/subject_forms.py @@ -0,0 +1,56 @@ +from django import forms +from django.forms import ModelForm + +from web.models import Subject +from web.models.constants import COUNTRY_OTHER_ID +from web.forms.forms import DATEPICKER_DATE_ATTRS + + +def validate_subject_country(self, cleaned_data): + if cleaned_data['country'].id == COUNTRY_OTHER_ID: + self.add_error('country', "Select valid country") + + +class SubjectAddForm(ModelForm): + date_born = forms.DateField(label="Date of birth", + widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), + required=False + ) + + class Meta: + model = Subject + fields = '__all__' + exclude = ['dead'] + + def clean(self): + cleaned_data = super(SubjectAddForm, self).clean() + validate_subject_country(self, cleaned_data) + return cleaned_data + + +class SubjectEditForm(ModelForm): + date_born = forms.DateField(label="Date of birth", + widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), + required=False + ) + + def __init__(self, *args, **kwargs): + was_dead = kwargs.get('was_dead', False) + if 'was_dead' in kwargs: + kwargs.pop('was_dead') + super(SubjectEditForm, self).__init__(*args, **kwargs) + if was_dead: + self.fields['dead'].disabled = True + + def clean(self): + validate_subject_country(self, self.cleaned_data) + + class Meta: + model = Subject + fields = '__all__' + + +class SubjectDetailForm(ModelForm): + class Meta: + model = Subject + fields = '__all__' diff --git a/smash/web/migrations/0071_auto_20171130_1607.py b/smash/web/migrations/0071_auto_20171130_1607.py new file mode 100644 index 0000000000000000000000000000000000000000..c757fdc14f19e05e1dc95a1518757140e59179db --- /dev/null +++ b/smash/web/migrations/0071_auto_20171130_1607.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-30 16:07 +from __future__ import unicode_literals + +import django.db.models.deletion +from django.db import migrations, models + + +# noinspection PyUnusedLocal +# noinspection PyPep8Naming +def create_default_study(apps, schema_editor): + # We can't import the Study model directly as it may be a newer + # version than this migration expects. We use the historical version. + Study = apps.get_model("web", "Study") + study = Study.objects.create() + study.name = "New study" + study.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('web', '0070_auto_20171128_1124'), + ] + + operations = [ + migrations.CreateModel( + name='Study', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name=b'Name')), + ], + ), + migrations.AlterField( + model_name='studysubject', + name='subject', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.Subject', + verbose_name=b'Subject'), + ), + migrations.AddField( + model_name='studysubject', + name='study', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, + to='web.Study', verbose_name=b'Study'), + ), + migrations.RunPython(create_default_study), + migrations.AlterField( + model_name='studysubject', + name='study', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.Study', verbose_name=b'Study'), + preserve_default=False, + ), + + ] diff --git a/smash/web/migrations/0072_auto_20171201_1013.py b/smash/web/migrations/0072_auto_20171201_1013.py new file mode 100644 index 0000000000000000000000000000000000000000..c90da294dd1ceb61ac58b09bbdc8b1eff9588641 --- /dev/null +++ b/smash/web/migrations/0072_auto_20171201_1013.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-01 10:13 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +# noinspection PyUnusedLocal +# noinspection PyPep8Naming +def create_default_study_columns(apps, schema_editor): + # We can't import the Study model directly as it may be a newer + # version than this migration expects. We use the historical version. + StudyColumns = apps.get_model("web", "StudyColumns") + study_columns = StudyColumns.objects.create() + study_columns.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0071_auto_20171130_1607'), + ] + + operations = [ + migrations.CreateModel( + name='StudyColumns', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('postponed', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Postponed')), + ('datetime_contact_reminder', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Please make a contact on')), + ('type', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Type')), + ('default_location', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Default appointment location')), + ('flying_team', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Default flying team location (if applicable)')), + ('screening_number', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Screening number')), + ('nd_number', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'ND number')), + ('mpower_id', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'MPower ID')), + ('comments', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Comments')), + ('referral', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Referred by')), + ('diagnosis', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Diagnosis')), + ('year_of_diagnosis', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Year of diagnosis (YYYY)')), + ('information_sent', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Information sent')), + ('pd_in_family', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'PD in family')), + ('resigned', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Resigned')), + ('resign_reason', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Resign reason')), + ], + ), + migrations.RunPython(create_default_study_columns), + migrations.AddField( + model_name='study', + name='columns', + field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, to='web.StudyColumns'), + preserve_default=False, + ), + ] diff --git a/smash/web/migrations/0073_auto_20171201_1034.py b/smash/web/migrations/0073_auto_20171201_1034.py new file mode 100644 index 0000000000000000000000000000000000000000..d23196db8c602c9824645a587468ef700dc7587d --- /dev/null +++ b/smash/web/migrations/0073_auto_20171201_1034.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-01 10:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0072_auto_20171201_1013'), + ] + + operations = [ + migrations.AlterField( + model_name='studysubject', + name='default_location', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Location', verbose_name=b'Default appointment location'), + ), + ] diff --git a/smash/web/migrations/0074_auto_20171201_1038.py b/smash/web/migrations/0074_auto_20171201_1038.py new file mode 100644 index 0000000000000000000000000000000000000000..55297a5b043aec9a5285464223c44444586d3edc --- /dev/null +++ b/smash/web/migrations/0074_auto_20171201_1038.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-01 10:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0073_auto_20171201_1034'), + ] + + operations = [ + migrations.AlterField( + model_name='studysubject', + name='default_location', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Location', verbose_name=b'Default appointment location'), + ), + ] diff --git a/smash/web/migrations/0075_auto_20171201_1252.py b/smash/web/migrations/0075_auto_20171201_1252.py new file mode 100644 index 0000000000000000000000000000000000000000..5d11beaa9167f3e0cd36d8688c1c2243e4a95d6c --- /dev/null +++ b/smash/web/migrations/0075_auto_20171201_1252.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-01 12:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0074_auto_20171201_1038'), + ] + + operations = [ + migrations.AlterField( + model_name='studysubject', + name='nd_number', + field=models.CharField(blank=True, max_length=25, verbose_name=b'ND number'), + ), + migrations.AlterField( + model_name='studysubject', + name='screening_number', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name=b'Screening number'), + ), + migrations.AlterField( + model_name='studysubject', + name='type', + field=models.CharField(blank=True, choices=[(b'P', b'PATIENT'), (b'C', b'CONTROL')], max_length=1, null=True, verbose_name=b'Type'), + ), + ] diff --git a/smash/web/migrations/0076_studysubjectlist.py b/smash/web/migrations/0076_studysubjectlist.py new file mode 100644 index 0000000000000000000000000000000000000000..1da548038649494fb446aa0104f29a5bea51de9f --- /dev/null +++ b/smash/web/migrations/0076_studysubjectlist.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-01 14:51 +from __future__ import unicode_literals + +import django.db.models.deletion +from django.db import migrations, models + + +# noinspection PyUnusedLocal +# noinspection PyPep8Naming +def create_default_study_columns(apps, schema_editor): + # We can't import the Study model directly as it may be a newer + # version than this migration expects. We use the historical version. + StudyColumns = apps.get_model("web", "StudyColumns") + study_columns = StudyColumns.objects.create() + study_columns.postponed = False + study_columns.datetime_contact_reminder = False + study_columns.type = True + study_columns.default_location = True + study_columns.flying_team = False + study_columns.screening_number = True + study_columns.nd_number = True + study_columns.mpower_id = False + study_columns.comments = False + study_columns.referral = False + study_columns.diagnosis = False + study_columns.year_of_diagnosis = False + study_columns.information_sent = True + study_columns.pd_in_family = False + study_columns.resigned = True + study_columns.resign_reason = False + study_columns.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('web', '0075_auto_20171201_1252'), + ] + + operations = [ + migrations.CreateModel( + name='StudySubjectList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(blank=True, + choices=[(b'GENERIC', b'Generic'), (b'NO_VISIT', b'Subjects without visit'), + (b'REQUIRE_CONTACT', b'Subjects required contact')], max_length=50, + null=True, verbose_name=b'Type o list')), + ('study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Study')), + ('visible_columns', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.StudyColumns')), + ], + ), + migrations.RunPython(create_default_study_columns), + migrations.RunSQL('insert into web_studysubjectlist (study_id, visible_columns_id, type) ' + + "select 1, max(id), 'GENERIC' from web_studycolumns;"), + ] diff --git a/smash/web/migrations/0077_subjectcolumns.py b/smash/web/migrations/0077_subjectcolumns.py new file mode 100644 index 0000000000000000000000000000000000000000..1f2757b04f8c8c6779b880e96fbacf37d94ec5af --- /dev/null +++ b/smash/web/migrations/0077_subjectcolumns.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-01 15:21 +from __future__ import unicode_literals + +import django +from django.db import migrations, models + + +# noinspection PyUnusedLocal +# noinspection PyPep8Naming +def create_default_subject_columns(apps, schema_editor): + # We can't import the Study model directly as it may be a newer + # version than this migration expects. We use the historical version. + SubjectColumns = apps.get_model("web", "SubjectColumns") + subject_columns = SubjectColumns.objects.create() + subject_columns.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0076_studysubjectlist'), + ] + + operations = [ + migrations.CreateModel( + name='SubjectColumns', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sex', models.BooleanField(default=False, max_length=1, verbose_name=b'Sex')), + ('first_name', models.BooleanField(default=True, max_length=1, verbose_name=b'First name')), + ('last_name', models.BooleanField(default=True, max_length=1, verbose_name=b'Last name')), + ('languages', models.BooleanField(default=False, max_length=1, verbose_name=b'Known languages')), + ('default_written_communication_language', models.BooleanField(default=False, max_length=1, verbose_name=b'Default language for document generation')), + ('phone_number', models.BooleanField(default=False, max_length=1, verbose_name=b'Phone number')), + ('phone_number_2', models.BooleanField(default=False, max_length=1, verbose_name=b'Phone number 2')), + ('phone_number_3', models.BooleanField(default=False, max_length=1, verbose_name=b'Phone number 3')), + ('email', models.BooleanField(default=False, max_length=1, verbose_name=b'E-mail')), + ('date_born', models.BooleanField(default=False, max_length=1, verbose_name=b'Date of birth')), + ('address', models.BooleanField(default=False, max_length=1, verbose_name=b'Address')), + ('postal_code', models.BooleanField(default=False, max_length=1, verbose_name=b'Postal code')), + ('city', models.BooleanField(default=False, max_length=1, verbose_name=b'City')), + ('country', models.BooleanField(default=False, max_length=1, verbose_name=b'Country')), + ('dead', models.BooleanField(default=True, max_length=1, verbose_name=b'Deceased')), + ], + ), + migrations.RunPython(create_default_subject_columns), + migrations.RenameField( + model_name='studysubjectlist', + old_name='visible_columns', + new_name='visible_subject_study_columns', + ), + migrations.AddField( + model_name='studysubjectlist', + name='visible_subject_columns', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='web.SubjectColumns'), + preserve_default=False, + ), + ] diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py index 2b7f7dba19788a6778d0430ed60436cc0c6c4e88..5226d98e619ba915c24b935149c896c839ea5d34 100644 --- a/smash/web/models/__init__.py +++ b/smash/web/models/__init__.py @@ -8,6 +8,9 @@ from flying_team import FlyingTeam from location import Location from appointment_type_link import AppointmentTypeLink from country import Country +from subject_columns import SubjectColumns +from study_columns import StudyColumns +from study import Study from room import Room from visit import Visit from worker import Worker @@ -19,12 +22,13 @@ from item import Item from language import Language from subject import Subject from study_subject import StudySubject +from study_subject_list import StudySubjectList from contact_attempt import ContactAttempt from mail_template import MailTemplate from missing_subject import MissingSubject from inconsistent_subject import InconsistentSubject, InconsistentField - -__all__ = [FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, Subject, StudySubject, +__all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, + Subject, StudySubject, StudySubjectList, SubjectColumns, Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate, AppointmentTypeLink, MissingSubject, - InconsistentSubject, InconsistentField, Country] + InconsistentSubject, InconsistentField, Country, StudyColumns] diff --git a/smash/web/models/configuration_item.py b/smash/web/models/configuration_item.py index 37db0488bb25d9b370e567f3acc5445d7ebe2a8e..cdaa6fbbabdabbd8c082e1ed41f3eaeb6c736ce8 100644 --- a/smash/web/models/configuration_item.py +++ b/smash/web/models/configuration_item.py @@ -8,6 +8,9 @@ from web.models.constants import CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE, class ConfigurationItem(models.Model): + class Meta: + app_label = 'web' + type = models.CharField(max_length=50, verbose_name='Type', editable=False diff --git a/smash/web/models/constants.py b/smash/web/models/constants.py index d2defa4d3d1f8a52f4f40fe60fc0dcf71f0963be..fe6ade3b9b0c61fb9df46206f7c427d0735157f9 100644 --- a/smash/web/models/constants.py +++ b/smash/web/models/constants.py @@ -76,4 +76,8 @@ REDCAP_TOKEN_CONFIGURATION_TYPE = "REDCAP_TOKEN_CONFIGURATION_TYPE" REDCAP_BASE_URL_CONFIGURATION_TYPE = "REDCAP_BASE_URL_CONFIGURATION_TYPE" COUNTRY_OTHER_ID = 1 -COUNTRY_AFGHANISTAN_ID = 2 \ No newline at end of file +COUNTRY_AFGHANISTAN_ID = 2 + +# id of the singleton Study, +# TODO remove after allowing many studies per Smasch instance +GLOBAL_STUDY_ID = 1 diff --git a/smash/web/models/study.py b/smash/web/models/study.py new file mode 100644 index 0000000000000000000000000000000000000000..d3b4090d33f589d100ce6c723f5af783597903bf --- /dev/null +++ b/smash/web/models/study.py @@ -0,0 +1,22 @@ +# coding=utf-8 +from django.db import models + +from web.models.study_columns import StudyColumns + + +class Study(models.Model): + class Meta: + app_label = 'web' + + name = models.CharField(max_length=255, verbose_name='Name') + + columns = models.OneToOneField( + StudyColumns, + on_delete=models.CASCADE, + ) + + def __str__(self): + return "%s" % self.name + + def __unicode__(self): + return "%s" % self.name diff --git a/smash/web/models/study_columns.py b/smash/web/models/study_columns.py new file mode 100644 index 0000000000000000000000000000000000000000..15142bd56913678159e4d92ef53928d0f7440ad0 --- /dev/null +++ b/smash/web/models/study_columns.py @@ -0,0 +1,79 @@ +# coding=utf-8 +from django.db import models + +from web.models.constants import BOOL_CHOICES + + +class StudyColumns(models.Model): + class Meta: + app_label = 'web' + + postponed = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Postponed', + default=True + ) + + datetime_contact_reminder = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Please make a contact on' + ) + type = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Type' + ) + + default_location = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Default appointment location', + ) + + flying_team = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Default flying team location (if applicable)', + ) + + screening_number = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Screening number', + ) + nd_number = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='ND number', + ) + mpower_id = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='MPower ID' + ) + comments = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Comments' + ) + referral = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Referred by' + ) + diagnosis = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Diagnosis' + ) + year_of_diagnosis = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Year of diagnosis (YYYY)' + ) + + information_sent = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Information sent', + ) + pd_in_family = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='PD in family', + ) + resigned = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Resigned', + ) + resign_reason = models.BooleanField(choices=BOOL_CHOICES, + default=True, + verbose_name='Resign reason' + ) diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py index 7fdc2eaca43b9bfffe90bca16f298dc88056d1ec..8bbeae76e223005167fc857d97465eff1c92af4f 100644 --- a/smash/web/models/study_subject.py +++ b/smash/web/models/study_subject.py @@ -33,6 +33,12 @@ class StudySubject(models.Model): null=False, ) + study = models.ForeignKey("web.Study", + verbose_name='Study', + editable=False, + null=False, + ) + postponed = models.BooleanField(choices=BOOL_CHOICES, verbose_name='Postponed', default=False @@ -44,28 +50,31 @@ class StudySubject(models.Model): ) type = models.CharField(max_length=1, choices=SUBJECT_TYPE_CHOICES.items(), - verbose_name='Type' + verbose_name='Type', + null=True, + blank=True ) default_location = models.ForeignKey(Location, verbose_name='Default appointment location', + null=True, + blank=True ) flying_team = models.ForeignKey("web.FlyingTeam", verbose_name='Default flying team location (if applicable)', - null=True, blank=True + null=True, + blank=True ) screening_number = models.CharField(max_length=50, - unique=True, - verbose_name='Screening number', blank=False, null=False + verbose_name='Screening number', + blank=True, + null=True ) nd_number = models.CharField(max_length=25, blank=True, verbose_name='ND number', - validators=[ - RegexValidator('^(ND[0-9]{4}|)$', - message="ND number should look as follows: NDxxxx")] ) mpower_id = models.CharField(max_length=20, blank=True, diff --git a/smash/web/models/study_subject_list.py b/smash/web/models/study_subject_list.py new file mode 100644 index 0000000000000000000000000000000000000000..fae16c14fb03cecdcae5d75adaffcf746c217513 --- /dev/null +++ b/smash/web/models/study_subject_list.py @@ -0,0 +1,44 @@ +# coding=utf-8 +from django.db import models + +from web.models import Study, SubjectColumns, StudyColumns + +SUBJECT_LIST_GENERIC = "GENERIC" +SUBJECT_LIST_NO_VISIT = "NO_VISIT" +SUBJECT_LIST_REQUIRE_CONTACT = "REQUIRE_CONTACT" + +SUBJECT_LIST_CHOICES = { + SUBJECT_LIST_GENERIC: 'Generic', + SUBJECT_LIST_NO_VISIT: 'Subjects without visit', + SUBJECT_LIST_REQUIRE_CONTACT: 'Subjects required contact', +} + + +class StudySubjectList(models.Model): + class Meta: + app_label = 'web' + + study = models.ForeignKey( + Study, + on_delete=models.CASCADE, + null=False, + ) + + visible_subject_study_columns = models.ForeignKey( + StudyColumns, + on_delete=models.CASCADE, + null=False, + ) + + visible_subject_columns = models.ForeignKey( + SubjectColumns, + on_delete=models.CASCADE, + null=False, + ) + + type = models.CharField(max_length=50, + choices=SUBJECT_LIST_CHOICES.items(), + verbose_name='Type o list', + null=True, + blank=True + ) diff --git a/smash/web/models/subject_columns.py b/smash/web/models/subject_columns.py new file mode 100644 index 0000000000000000000000000000000000000000..ad1779b9302c403efedaa7aece05176080290857 --- /dev/null +++ b/smash/web/models/subject_columns.py @@ -0,0 +1,81 @@ +# coding=utf-8 +from django.db import models + + +class SubjectColumns(models.Model): + class Meta: + app_label = 'web' + + sex = models.BooleanField(max_length=1, + default=False, + verbose_name='Sex', + ) + + first_name = models.BooleanField(max_length=1, + default=True, + verbose_name='First name' + ) + + last_name = models.BooleanField(max_length=1, + default=True, + verbose_name='Last name' + ) + + languages = models.BooleanField(max_length=1, + default=False, + verbose_name='Known languages' + ) + + default_written_communication_language = models.BooleanField(max_length=1, + default=False, + verbose_name='Default language for document generation' + ) + phone_number = models.BooleanField(max_length=1, + default=False, + verbose_name='Phone number' + ) + + phone_number_2 = models.BooleanField(max_length=1, + default=False, + verbose_name='Phone number 2' + ) + + phone_number_3 = models.BooleanField(max_length=1, + default=False, + verbose_name='Phone number 3' + ) + + email = models.BooleanField(max_length=1, + default=False, + verbose_name='E-mail' + ) + + date_born = models.BooleanField(max_length=1, + default=False, + verbose_name='Date of birth' + ) + + address = models.BooleanField(max_length=1, + default=False, + verbose_name='Address' + ) + + postal_code = models.BooleanField(max_length=1, + default=False, + verbose_name='Postal code' + ) + + city = models.BooleanField(max_length=1, + default=False, + verbose_name='City' + ) + + country = models.BooleanField(max_length=1, + default=False, + verbose_name='Country' + ) + + dead = models.BooleanField(max_length=1, + default=True, + verbose_name='Deceased', + ) diff --git a/smash/web/redcap_connector.py b/smash/web/redcap_connector.py index 8a2828583aee2e92ff795c73ae3182fc505d5e9b..ff09df743be6cd71ce5826ed375330df967e4df6 100644 --- a/smash/web/redcap_connector.py +++ b/smash/web/redcap_connector.py @@ -21,14 +21,18 @@ RED_CAP_LANGUAGE_2_FIELD = 'dm_language_2' RED_CAP_LANGUAGE_1_FIELD = 'dm_language_1' +# noinspection SpellCheckingInspection RED_CAP_MPOWER_ID_FIELD = 'dm_mpowerid' RED_CAP_DEAD_FIELD = 'dm_death' +# noinspection SpellCheckingInspection RED_CAP_SEX_FIELD = 'cdisc_dm_sex' +# noinspection SpellCheckingInspection RED_CAP_DATE_BORN_FIELD = 'cdisc_dm_brthdtc' +# noinspection SpellCheckingInspection RED_CAP_ND_NUMBER_FIELD = 'cdisc_dm_usubjd' logger = logging.getLogger(__name__) diff --git a/smash/web/static/js/subject.js b/smash/web/static/js/subject.js index 1cc539c908db2fbfbef26a52cb0113663ec24bab..d6f5ac20e5bb8c737c667e3a333594a69fde9303 100644 --- a/smash/web/static/js/subject.js +++ b/smash/web/static/js/subject.js @@ -1,3 +1,10 @@ +if (!String.prototype.startsWith) { + String.prototype.startsWith = function (searchString, position) { + position = position || 0; + return this.indexOf(searchString, position) === position; + }; +} + function createColumn(dataType, name, filter, visible, renderFunction) { if (renderFunction === undefined) { renderFunction = function (data, type, row, meta) { @@ -13,39 +20,31 @@ function createColumn(dataType, name, filter, visible, renderFunction) { }; } -function getColumns(type, getSubjectEditUrl) { +function getColumns(columns, getSubjectEditUrl) { var result = []; - // don't confuse end user - // result.push(createColumn("id", "Id", null, false)); - result.push(createColumn("nd_number", "ND", "string_filter", true)); - result.push(createColumn("screening_number", "Screening", "string_filter", true)); - result.push(createColumn("first_name", "First name", "string_filter", true)); - result.push(createColumn("last_name", "Last name", "string_filter", true)); - result.push(createColumn("date_born", "Date of birth", null, false)); - result.push(createColumn("default_location", "Location", "location_filter", true)); - result.push(createColumn("dead", "Deceased", "yes_no_filter", true)); - result.push(createColumn("resigned", "Resigned", "yes_no_filter", true)); - result.push(createColumn("postponed", "Postponed", "yes_no_filter", true)); - result.push(createColumn("information_sent", "Info sent", "yes_no_filter", true)); - result.push(createColumn("type", "Type", "type_filter", true)); - result.push(createColumn("id", "Edit", null, true, function (data, type, row, meta) { - var url = getSubjectEditUrl(row.id.toString()); - - return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>'; - })); - for (var i = 1; i <= 8; i++) { - var renderFunction = (function () { - var x = i; - return function (data, type, row, meta) { - return create_visit_row(row.visits[x - 1]); - }; - })(); - - result.push(createColumn("visit_" + i, "Visit " + i, "visit_filter", true, renderFunction)); - + for (var i = 0; i < columns.length; i++) { + var columnRow = columns[i]; + if (columnRow.type === "edit") { + result.push(createColumn("id", columnRow.name, columnRow.filter, columnRow.visible, function (data, type, row) { + var url = getSubjectEditUrl(row.id.toString()); + + return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>'; + })); + } else if (columnRow.type.startsWith("visit")) { + var renderFunction = (function () { + var x = i; + return function (data, type, row) { + return create_visit_row(row.visits[x - 1]); + }; + })(); + + result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, renderFunction)); + + } else { + result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible)); + } } return result; - } function createHeader(columnsDefinition) { diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html index 7f97eb8619ff275cbe4f294b43ee2449b76a966e..2c39cc33e2518e07eb385778dd682e48d11b94b0 100644 --- a/smash/web/templates/subjects/index.html +++ b/smash/web/templates/subjects/index.html @@ -52,16 +52,18 @@ worker_locations.push({id: location.id, name: location.name}); {% endfor %} - createSubjectsTable({ - worker_locations: worker_locations, - subject_types_url: "{% url 'web.api.subject_types' %}", - locations_url: "{% url 'web.api.locations' %}", - subjects_url: "{% url 'web.api.subjects' list_type %}", - tableElement: document.getElementById("table"), - columns: getColumns("{{ list_type }}", getSubjectEditUrl), - checkboxesElement: document.getElementById("visible-column-checkboxes") - }) - ; + $.get("{% url 'web.api.subjects.columns' list_type %}", function (data) { + createSubjectsTable({ + worker_locations: worker_locations, + subject_types_url: "{% url 'web.api.subject_types' %}", + locations_url: "{% url 'web.api.locations' %}", + subjects_url: "{% url 'web.api.subjects' list_type %}", + tableElement: document.getElementById("table"), + columns: getColumns(data.columns, getSubjectEditUrl), + checkboxesElement: document.getElementById("visible-column-checkboxes") + }) + }); + </script> {% endblock scripts %} diff --git a/smash/web/tests/api_views/test_daily_planning.py b/smash/web/tests/api_views/test_daily_planning.py index abaea166149c9dbf02967115040209f5fcaa9cb8..0d48c8d15ed527d6704f168a77cdd0b47f94dfad 100644 --- a/smash/web/tests/api_views/test_daily_planning.py +++ b/smash/web/tests/api_views/test_daily_planning.py @@ -10,8 +10,8 @@ from django.urls import reverse from web.api_views.daily_planning import get_workers_for_daily_planning, get_generic_appointment_events from web.models import Worker, Availability, Holiday, AppointmentTypeLink from web.models.constants import TUESDAY_AS_DAY_OF_WEEK -from web.tests.functions import create_worker, create_study_subject, create_appointment, create_flying_team, create_visit, \ - create_appointment_type, get_test_location +from web.tests.functions import create_worker, create_study_subject, create_appointment, create_flying_team, \ + create_visit, create_appointment_type, get_test_location class TestApi(TestCase): diff --git a/smash/web/tests/api_views/test_subject.py b/smash/web/tests/api_views/test_subject.py index 672266db6f3a6f4e0ba6792081b2329d73f332c7..7dc320a195db39709542773cd26735e09dfc0cdd 100644 --- a/smash/web/tests/api_views/test_subject.py +++ b/smash/web/tests/api_views/test_subject.py @@ -8,10 +8,13 @@ from django.test import Client from django.test import TestCase from django.urls import reverse -from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject, SUBJECT_LIST_GENERIC, \ - SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT -from web.models import StudySubject, Appointment -from web.tests.functions import create_study_subject, create_worker, create_get_suffix, create_visit, create_appointment +from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject +from web.models import StudySubject, Appointment, Study +from web.models.constants import GLOBAL_STUDY_ID +from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \ + StudySubjectList +from web.tests.functions import create_study_subject, create_worker, create_get_suffix, create_visit, \ + create_appointment, create_empty_study_columns from web.views.notifications import get_today_midnight_date logger = logging.getLogger(__name__) @@ -46,6 +49,35 @@ class TestApi(TestCase): self.assertTrue(city_name in cities) + def test_get_columns(self): + response = self.client.get( + reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC})) + self.assertEqual(response.status_code, 200) + + columns = json.loads(response.content)['columns'] + self.assertTrue(len(columns) >= 20) + + def test_get_columns_when_no_list_is_available(self): + StudySubjectList.objects.all().delete() + response = self.client.get( + reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC})) + self.assertEqual(response.status_code, 200) + + columns = json.loads(response.content)['columns'] + self.assertTrue(len(columns) > 0) + + def test_get_columns_when_study_has_no_data_columns(self): + study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] + study.columns = create_empty_study_columns() + study.save() + + response = self.client.get( + reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC})) + self.assertEqual(response.status_code, 200) + + columns = json.loads(response.content)['columns'] + self.assertTrue(len(columns) < 20) + def test_referrals(self): referral_name = "some referral" diff --git a/smash/web/tests/forms/test_StudySubjectAddForm.py b/smash/web/tests/forms/test_StudySubjectAddForm.py index a417a5bb3f11f562dbf2121b02c4074d3f5f45ff..29b4a7b7715b80db243e0a3b30053f1a7f3ba7b7 100644 --- a/smash/web/tests/forms/test_StudySubjectAddForm.py +++ b/smash/web/tests/forms/test_StudySubjectAddForm.py @@ -1,9 +1,10 @@ import logging -from web.forms import StudySubjectAddForm, get_new_screening_number +from web.forms.study_subject_forms import get_new_screening_number +from web.forms import StudySubjectAddForm from web.models.constants import SUBJECT_TYPE_CHOICES_CONTROL from web.tests import LoggedInWithWorkerTestCase -from web.tests.functions import create_study_subject, create_subject +from web.tests.functions import create_study_subject, create_subject, get_test_study, create_empty_study logger = logging.getLogger(__name__) @@ -14,6 +15,7 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase): location = self.worker.locations.all()[0] self.subject = create_subject() + self.study = get_test_study() self.sample_data = { 'type': SUBJECT_TYPE_CHOICES_CONTROL, 'default_location': location.id, @@ -22,22 +24,32 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase): } def test_validation(self): - form = StudySubjectAddForm(data=self.sample_data, user=self.user) + form = StudySubjectAddForm(data=self.sample_data, user=self.user, study=self.study) form.is_valid() self.assertTrue(form.is_valid()) + def test_validation_for_study_without_columns(self): + form = StudySubjectAddForm(data=self.sample_data, user=self.user, study=create_empty_study()) + self.assertTrue(form.is_valid()) + + def test_validate_nd_number(self): + self.sample_data['nd_number'] = 'invalid nd number' + form = StudySubjectAddForm(data=self.sample_data, user=self.user, study=self.study) + self.assertFalse(form.is_valid()) + self.assertTrue("nd_number" in form.errors) + def test_invalid(self): form_data = self.sample_data form_data['screening_number'] = "123" - form = StudySubjectAddForm(data=form_data, user=self.user) + form = StudySubjectAddForm(data=form_data, user=self.user, study=self.study) form.is_valid() form.instance.subject_id = self.subject.id self.assertTrue(form.is_valid()) self.assertIsNone(form.fields['year_of_diagnosis'].initial) form.save() - form2 = StudySubjectAddForm(data=form_data, user=self.user) + form2 = StudySubjectAddForm(data=form_data, user=self.user, study=self.study) validation_status = form2.is_valid() self.assertFalse(validation_status) self.assertTrue("screening_number" in form2.errors) @@ -46,14 +58,14 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase): form_data = self.sample_data form_data['nd_number'] = "ND0123" - form = StudySubjectAddForm(data=form_data, user=self.user) + form = StudySubjectAddForm(data=form_data, user=self.user, study=self.study) form.is_valid() self.assertTrue(form.is_valid()) form.instance.subject_id = self.subject.id form.save() form_data['screening_number'] = "2" - form2 = StudySubjectAddForm(data=form_data, user=self.user) + form2 = StudySubjectAddForm(data=form_data, user=self.user, study=self.study) validation_status = form2.is_valid() self.assertFalse(validation_status) self.assertTrue("nd_number" in form2.errors) @@ -62,14 +74,14 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase): form_data = self.sample_data form_data['mpower_id'] = "123" - form = StudySubjectAddForm(data=form_data, user=self.user) + form = StudySubjectAddForm(data=form_data, user=self.user, study=self.study) form.is_valid() self.assertTrue(form.is_valid()) form.instance.subject_id = self.subject.id form.save() form_data['screening_number'] = "2" - form2 = StudySubjectAddForm(data=form_data, user=self.user) + form2 = StudySubjectAddForm(data=form_data, user=self.user, study=self.study) validation_status = form2.is_valid() self.assertFalse(validation_status) self.assertTrue("mpower_id" in form2.errors) diff --git a/smash/web/tests/forms/test_StudySubjectEditForm.py b/smash/web/tests/forms/test_StudySubjectEditForm.py index 5f838cff2724de1c31ae353a5e828d426d3bb002..1e7a5c0919c5b3ed775b23e1cf4bb1b3ab538f59 100644 --- a/smash/web/tests/forms/test_StudySubjectEditForm.py +++ b/smash/web/tests/forms/test_StudySubjectEditForm.py @@ -3,7 +3,7 @@ import logging from web.forms import StudySubjectEditForm from web.models import StudySubject from web.tests import LoggedInWithWorkerTestCase -from web.tests.functions import create_study_subject +from web.tests.functions import create_study_subject, create_empty_study logger = logging.getLogger(__name__) @@ -31,6 +31,14 @@ class StudySubjectEditFormTests(LoggedInWithWorkerTestCase): save_status = edit_form.is_valid() self.assertTrue(save_status) + def test_validation_with_empty_study(self): + self.study_subject.study = create_empty_study() + self.study_subject.save() + + edit_form = StudySubjectEditForm(self.sample_data) + save_status = edit_form.is_valid() + self.assertTrue(save_status) + def test_invalid_nd_number_edit(self): study_subject2 = create_study_subject(124) study_subject2.nd_number = "ND0124" @@ -38,7 +46,7 @@ class StudySubjectEditFormTests(LoggedInWithWorkerTestCase): study_subject2.save() self.sample_data['nd_number'] = "ND0124" - edit_form = StudySubjectEditForm(self.sample_data) + edit_form = StudySubjectEditForm(self.sample_data, instance=self.study_subject) save_status = edit_form.is_valid() self.assertTrue("nd_number" in edit_form.errors) diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py index adb1eaf8511656ec1b92688405d4979d893ccb53..6994935f76045a59b011f429d58b408386c7152d 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -3,8 +3,8 @@ import os from django.contrib.auth.models import User -from web.models import Location, AppointmentType, StudySubject, Worker, Visit, Appointment, ConfigurationItem, Language, \ - ContactAttempt, FlyingTeam, Availability, Subject +from web.models import Location, AppointmentType, StudySubject, Worker, Visit, Appointment, ConfigurationItem, \ + Language, ContactAttempt, FlyingTeam, Availability, Subject, Study, StudyColumns from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, REDCAP_BASE_URL_CONFIGURATION_TYPE, \ SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL, CONTACT_TYPES_PHONE, \ MONDAY_AS_DAY_OF_WEEK, COUNTRY_AFGHANISTAN_ID @@ -23,6 +23,56 @@ def create_location(name="test"): return Location.objects.create(name=name) +def create_empty_study_columns(): + study_columns = StudyColumns.objects.create( + postponed=False, + datetime_contact_reminder=False, + type=False, + default_location=False, + flying_team=False, + screening_number=False, + nd_number=False, + mpower_id=False, + comments=False, + referral=False, + diagnosis=False, + year_of_diagnosis=False, + information_sent=False, + pd_in_family=False, + resigned=False, + resign_reason=False, + ) + + return study_columns + + +def create_study(name="test"): + study_columns = StudyColumns.objects.create() + return Study.objects.create(name=name, columns=study_columns) + + +def create_empty_study(name="test"): + study_columns = StudyColumns.objects.create( + postponed=False, + datetime_contact_reminder=False, + type=False, + default_location=False, + flying_team=False, + screening_number=False, + nd_number=False, + mpower_id=False, + comments=False, + referral=False, + diagnosis=False, + year_of_diagnosis=False, + information_sent=False, + pd_in_family=False, + resigned=False, + resign_reason=False + ) + return Study.objects.create(name=name, columns=study_columns) + + def get_test_location(): locations = Location.objects.filter(name="test") if len(locations) > 0: @@ -31,6 +81,14 @@ def get_test_location(): return create_location() +def get_test_study(): + studies = Study.objects.filter(name="test-study") + if len(studies) > 0: + return studies[0] + else: + return create_study("test-study") + + def create_appointment_type(): return AppointmentType.objects.create( code="C", @@ -70,6 +128,7 @@ def create_study_subject(subject_id=1, subject=None): default_location=get_test_location(), type=SUBJECT_TYPE_CHOICES_CONTROL, screening_number="piotr's number" + str(subject_id), + study=get_test_study(), subject=subject ) diff --git a/smash/web/tests/models/test_study.py b/smash/web/tests/models/test_study.py new file mode 100644 index 0000000000000000000000000000000000000000..9c9e02f77c08015bd59a4ef19149945caa5a0e6b --- /dev/null +++ b/smash/web/tests/models/test_study.py @@ -0,0 +1,14 @@ +import logging + +from django.test import TestCase + +from web.tests.functions import create_study + +logger = logging.getLogger(__name__) + + +class StudyTests(TestCase): + def test_image_img(self): + study = create_study() + + self.assertTrue(study.name in str(study)) diff --git a/smash/web/tests/view/test_appointments.py b/smash/web/tests/view/test_appointments.py index 97f26eb5c7abae165eb7f89d2766c299f9245e35..c187af91ed8d55d33f861d6f956cb998e3663946 100644 --- a/smash/web/tests/view/test_appointments.py +++ b/smash/web/tests/view/test_appointments.py @@ -3,12 +3,11 @@ import logging from django.urls import reverse -from web.forms import AppointmentEditForm, StudySubjectEditForm, SubjectEditForm +from web.forms import AppointmentEditForm, SubjectEditForm, StudySubjectEditForm from web.models import Appointment, StudySubject from web.tests import LoggedInTestCase from web.tests.functions import create_study_subject, create_visit, create_appointment, create_worker, \ - create_flying_team, \ - format_form_field + create_flying_team, format_form_field from web.views.notifications import get_today_midnight_date logger = logging.getLogger(__name__) diff --git a/smash/web/tests/view/test_subjects.py b/smash/web/tests/view/test_subjects.py index 83820e0109d406de796320d2cdabb668df8c145a..cf9d49777f4ebd58988db0c48c3e8df2d6eb509e 100644 --- a/smash/web/tests/view/test_subjects.py +++ b/smash/web/tests/view/test_subjects.py @@ -3,13 +3,13 @@ import logging from django.urls import reverse -from web.forms import StudySubjectAddForm, StudySubjectEditForm, SubjectEditForm, SubjectAddForm +from web.forms import SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm from web.models import MailTemplate, StudySubject from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL, SUBJECT_TYPE_CHOICES_PATIENT, \ COUNTRY_AFGHANISTAN_ID, COUNTRY_OTHER_ID, MAIL_TEMPLATE_CONTEXT_SUBJECT from web.tests import LoggedInWithWorkerTestCase from web.tests.functions import create_study_subject, create_visit, create_appointment, get_test_location, \ - create_language, get_resource_path + create_language, get_resource_path, get_test_study from web.views.notifications import get_today_midnight_date logger = logging.getLogger(__name__) @@ -19,6 +19,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase): def setUp(self): super(SubjectsViewTests, self).setUp() self.study_subject = create_study_subject() + self.study = get_test_study() def test_render_subjects_add(self): self.worker.save() @@ -105,7 +106,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase): return form_data def create_add_form_data_for_study_subject(self): - form_study_subject = StudySubjectAddForm(prefix="study_subject", user=self.user) + form_study_subject = StudySubjectAddForm(prefix="study_subject", user=self.user, study=self.study) form_subject = SubjectAddForm(prefix="subject") form_data = {} for key, value in form_study_subject.initial.items(): diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py index e7f08ea311fe1581c2c1363606c0842f11f572a2..938e713058cbd8ce402bf708122e81d489bfd728 100644 --- a/smash/web/views/appointment.py +++ b/smash/web/views/appointment.py @@ -7,8 +7,8 @@ from django.core.exceptions import ValidationError from django.shortcuts import get_object_or_404, redirect from . import wrap_response -from ..forms import AppointmentDetailForm, AppointmentAddForm, AppointmentEditForm, StudySubjectEditForm, \ - SubjectEditForm +from ..forms import AppointmentDetailForm, AppointmentAddForm, AppointmentEditForm, SubjectEditForm, \ + StudySubjectEditForm from ..models import Appointment, StudySubject, MailTemplate APPOINTMENT_LIST_GENERIC = "GENERIC" diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py index 53ceafe61a78afb0d1e260df5cfb4e8248e547b5..5825db2be3767e1075badcd3096e6580cfa20cec 100644 --- a/smash/web/views/subject.py +++ b/smash/web/views/subject.py @@ -4,13 +4,11 @@ import logging from django.contrib import messages from django.shortcuts import redirect, get_object_or_404 +from ..models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT from . import wrap_response -from ..forms import StudySubjectAddForm, StudySubjectEditForm, VisitDetailForm, SubjectEditForm, SubjectAddForm -from ..models import StudySubject, MailTemplate, Worker - -SUBJECT_LIST_GENERIC = "GENERIC" -SUBJECT_LIST_NO_VISIT = "NO_VISIT" -SUBJECT_LIST_REQUIRE_CONTACT = "REQUIRE_CONTACT" +from ..forms import VisitDetailForm, SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm +from ..models import StudySubject, MailTemplate, Worker, Study +from ..models.constants import GLOBAL_STUDY_ID logger = logging.getLogger(__name__) @@ -24,8 +22,10 @@ def subjects(request): def subject_add(request): + study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] if request.method == 'POST': - study_subject_form = StudySubjectAddForm(request.POST, request.FILES, prefix="study_subject", user=request.user) + study_subject_form = StudySubjectAddForm(request.POST, request.FILES, prefix="study_subject", user=request.user, + study=study) subject_form = SubjectAddForm(request.POST, request.FILES, prefix="subject") if study_subject_form.is_valid() and subject_form.is_valid(): @@ -38,7 +38,7 @@ def subject_add(request): messages.add_message(request, messages.ERROR, 'Invalid data. Please fix data and try again.') else: - study_subject_form = StudySubjectAddForm(user=request.user, prefix="study_subject") + study_subject_form = StudySubjectAddForm(user=request.user, prefix="study_subject", study=study) subject_form = SubjectAddForm(prefix="subject") return wrap_response(request, 'subjects/add.html', @@ -77,7 +77,8 @@ def subject_edit(request, id): # check if subject was marked as dead or resigned if subject_form.cleaned_data['dead'] and not was_dead: study_subject.subject.mark_as_dead() - if study_subject_form.cleaned_data['resigned'] and not was_resigned: + if study_subject.study.columns.resigned \ + and study_subject_form.cleaned_data['resigned'] and not was_resigned: study_subject.mark_as_resigned() messages.success(request, "Modifications saved") if '_continue' in request.POST: diff --git a/smash/web/views/visit.py b/smash/web/views/visit.py index 253270a4afe020cb5efaaccbc9654a4c1882f4b6..0dd67c3b3f67cabb631f9b08ee00359081b1694a 100644 --- a/smash/web/views/visit.py +++ b/smash/web/views/visit.py @@ -7,7 +7,7 @@ from notifications import get_active_visits_with_missing_appointments, get_unfin get_approaching_visits_without_appointments, get_approaching_visits_for_mail_contact, get_exceeded_visits, \ waiting_for_appointment from . import wrap_response -from ..forms import VisitDetailForm, StudySubjectDetailForm, VisitAddForm, SubjectDetailForm +from ..forms import VisitDetailForm, VisitAddForm, SubjectDetailForm, StudySubjectDetailForm from ..models import Visit, Appointment, StudySubject, MailTemplate logger = logging.getLogger(__name__)