diff --git a/smash/web/forms.py b/smash/web/forms.py index 0c062b1afd94b821ddb37abf7e28aa7bff2e53ec..2b50d8ba57e22de782dde8cbb113038cb629c391 100644 --- a/smash/web/forms.py +++ b/smash/web/forms.py @@ -1,11 +1,13 @@ import datetime import logging +import re from collections import OrderedDict from django import forms from django.forms import ModelForm, Form from django.utils.dates import MONTHS +from models import Study, StudyColumns from web.models import Subject, StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, \ AppointmentTypeLink, \ Availability, Holiday @@ -41,11 +43,16 @@ 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") + 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_country(self, cleaned_data): @@ -59,11 +66,12 @@ def validate_subject_resign_reason(self, cleaned_data): 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") + 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") def get_worker_from_args(kwargs): @@ -83,6 +91,65 @@ def get_study_from_args(kwargs): return study +def prepare_study_subject_fields(fields, study): + if study.columns.default_location: + fields['default_location'].required = True + else: + del fields['default_location'] + + if study.columns.type: + fields['type'].required = True + else: + del fields['type'] + + if not study.columns.nd_number: + del fields['nd_number'] + + if not study.columns.datetime_contact_reminder: + del fields['datetime_contact_reminder'] + + if not study.columns.postponed: + del fields['postponed'] + + if not study.columns.flying_team: + del fields['flying_team'] + + if not study.columns.mpower_id: + del fields['mpower_id'] + + if not study.columns.comments: + del fields['comments'] + + if not study.columns.referral: + del fields['referral'] + + if not study.columns.diagnosis: + del fields['diagnosis'] + + if not study.columns.year_of_diagnosis: + del fields['year_of_diagnosis'] + + if not study.columns.information_sent: + del fields['information_sent'] + + if not study.columns.pd_in_family: + del fields['pd_in_family'] + + if not study.columns.resigned and 'resigned' in fields: + del fields['resigned'] + + if not study.columns.resign_reason and 'resign_reason' in fields: + del fields['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") + + class StudySubjectAddForm(ModelForm): datetime_contact_reminder = forms.DateTimeField(label="Contact on", widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), @@ -99,7 +166,7 @@ class StudySubjectAddForm(ModelForm): self.study = get_study_from_args(kwargs) super(ModelForm, self).__init__(*args, **kwargs) - self.fields['screening_number'].required = False + prepare_study_subject_fields(fields=self.fields, study=self.study) def save(self, commit=True): self.instance.study_id = self.study.id @@ -116,12 +183,9 @@ class StudySubjectAddForm(ModelForm): def clean(self): cleaned_data = super(StudySubjectAddForm, self).clean() screening_number = self.build_screening_number(cleaned_data) - if screening_number is not None: + if screening_number is not None and self.study.columns.screening_number: 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_screening_number(self, cleaned_data) validate_subject_nd_number(self, cleaned_data) validate_subject_mpower_number(self, cleaned_data) return cleaned_data @@ -178,6 +242,11 @@ class StudySubjectEditForm(ModelForm): instance = getattr(self, 'instance', None) if instance and instance.id: self.fields['screening_number'].widget.attrs['readonly'] = True + if instance and instance.study_id: + self.study = Study.objects.filter(id=instance.study_id)[0] + else: + self.study = Study(columns=StudyColumns()) + if was_resigned: self.fields['resigned'].disabled = True 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/models/study_subject.py b/smash/web/models/study_subject.py index 29af95317831116ccb0bf01b162c31ad8d191db1..8bbeae76e223005167fc857d97465eff1c92af4f 100644 --- a/smash/web/models/study_subject.py +++ b/smash/web/models/study_subject.py @@ -50,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/tests/forms/test_StudySubjectAddForm.py b/smash/web/tests/forms/test_StudySubjectAddForm.py index 1a206bffd2f15ed731c8739850be714ad12e958f..6efd5c311d50663df0bd3374fdd484a6b4d1314f 100644 --- a/smash/web/tests/forms/test_StudySubjectAddForm.py +++ b/smash/web/tests/forms/test_StudySubjectAddForm.py @@ -3,7 +3,7 @@ import logging from web.forms import StudySubjectAddForm, get_new_screening_number from web.models.constants import SUBJECT_TYPE_CHOICES_CONTROL from web.tests import LoggedInWithWorkerTestCase -from web.tests.functions import create_study_subject, create_subject, get_test_study +from web.tests.functions import create_study_subject, create_subject, get_test_study, create_empty_study logger = logging.getLogger(__name__) @@ -27,6 +27,11 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase): 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()) + form.is_valid() + self.assertTrue(form.is_valid()) + def test_invalid(self): form_data = self.sample_data form_data['screening_number'] = "123" diff --git a/smash/web/tests/forms/test_StudySubjectEditForm.py b/smash/web/tests/forms/test_StudySubjectEditForm.py index 5f838cff2724de1c31ae353a5e828d426d3bb002..57395339e3a78c6d21c48968fbd08cfc3b930e60 100644 --- a/smash/web/tests/forms/test_StudySubjectEditForm.py +++ b/smash/web/tests/forms/test_StudySubjectEditForm.py @@ -38,7 +38,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 34401ae54e7827fd5cdef302e487ea9db8e07134..107bb0cb82d88b547e602297a5c2723764d89c93 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -28,6 +28,28 @@ def create_study(name="test"): 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: