From 5e1141669653923855a4ab706db49298d24902ef Mon Sep 17 00:00:00 2001 From: Carlos Vega <carlos.vega@uni.lu> Date: Wed, 24 Oct 2018 21:41:57 +0200 Subject: [PATCH] added field nd_number_study_subject_regex added checks for forms added check function in Study and StudySubject to avoid repeating a regex inline removed previous checks in which the regex was rewritten now the used regex is the one defined in the given study added tests and migrations changed functions in tests/functions to adapt the tests --- smash/web/forms/study_forms.py | 18 ++++-- smash/web/forms/study_subject_forms.py | 2 +- .../web/migrations/0119_auto_20181024_1158.py | 25 ++++++++ ...120_study_nd_number_study_subject_regex.py | 20 +++++++ .../web/migrations/0121_auto_20181024_1859.py | 20 +++++++ smash/web/models/study.py | 19 ++++++ smash/web/models/study_subject.py | 26 +++++++-- smash/web/templates/study/edit.html | 10 +++- smash/web/tests/forms/test_study_forms.py | 40 +++++++++++++ smash/web/tests/functions.py | 22 +++++-- smash/web/tests/models/test_study.py | 14 ++++- smash/web/tests/models/test_study_subject.py | 58 ++++++++++++++----- smash/web/tests/view/test_appointments.py | 2 +- smash/web/views/appointment.py | 22 +++++-- 14 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 smash/web/migrations/0119_auto_20181024_1158.py create mode 100644 smash/web/migrations/0120_study_nd_number_study_subject_regex.py create mode 100644 smash/web/migrations/0121_auto_20181024_1859.py create mode 100644 smash/web/tests/forms/test_study_forms.py diff --git a/smash/web/forms/study_forms.py b/smash/web/forms/study_forms.py index 76c51a42..916f2316 100644 --- a/smash/web/forms/study_forms.py +++ b/smash/web/forms/study_forms.py @@ -1,8 +1,7 @@ import logging -from django.forms import ModelForm - -from web.models import Study, StudyNotificationParameters, StudyColumns +from django.forms import ModelForm, ValidationError +from web.models import Study, StudyNotificationParameters, StudyColumns, StudySubject logger = logging.getLogger(__name__) @@ -12,6 +11,16 @@ class StudyEditForm(ModelForm): def __init__(self, *args, **kwargs): super(StudyEditForm, self).__init__(*args, **kwargs) + def clean_nd_number_study_subject_regex(self): + nd_number_study_subject_regex = self.cleaned_data.get( + 'nd_number_study_subject_regex') + + if StudySubject.check_nd_number_regex(nd_number_study_subject_regex) == False: + raise ValidationError( + 'Please enter a valid nd_number_study_subject_regex regex.') + + return nd_number_study_subject_regex + class Meta: model = Study fields = '__all__' @@ -21,7 +30,8 @@ class StudyEditForm(ModelForm): class StudyNotificationParametersEditForm(ModelForm): def __init__(self, *args, **kwargs): - super(StudyNotificationParametersEditForm, self).__init__(*args, **kwargs) + super(StudyNotificationParametersEditForm, + self).__init__(*args, **kwargs) class Meta: model = StudyNotificationParameters diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py index 44afb257..76d4d773 100644 --- a/smash/web/forms/study_subject_forms.py +++ b/smash/web/forms/study_subject_forms.py @@ -197,7 +197,7 @@ 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: + if not self.study.check_nd_number(nd_number): self.add_error('nd_number', "Invalid ND number") else: subjects_from_db = StudySubject.objects.filter(nd_number=nd_number, study=self.study) diff --git a/smash/web/migrations/0119_auto_20181024_1158.py b/smash/web/migrations/0119_auto_20181024_1158.py new file mode 100644 index 00000000..cab12fa3 --- /dev/null +++ b/smash/web/migrations/0119_auto_20181024_1158.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2018-10-24 11:58 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0118_voucher_activity_type'), + ] + + operations = [ + migrations.AlterField( + model_name='studyvisitlist', + name='type', + field=models.CharField(choices=[(b'UNFINISHED', b'Unfinished visits'), (b'APPROACHING_WITHOUT_APPOINTMENTS', b'Approaching visits'), (b'APPROACHING_FOR_MAIL_CONTACT', b'Post mail for approaching visits'), (b'GENERIC', b'Generic visit list'), (b'MISSING_APPOINTMENTS', b'Visits with missing appointments'), (b'EXCEEDED_TIME', b'Exceeded visit time')], max_length=50, verbose_name=b'Type of list'), + ), + migrations.AlterField( + model_name='workerstudyrole', + name='role', + field=models.CharField(choices=[(b'DOCTOR', b'Doctor'), (b'NURSE', b'Nurse'), (b'PSYCHOLOGIST', b'Psychologist'), (b'TECHNICIAN', b'Technician'), (b'SECRETARY', b'Secretary'), (b'PROJECT MANAGER', b'Project Manager')], max_length=20, verbose_name=b'Role'), + ), + ] diff --git a/smash/web/migrations/0120_study_nd_number_study_subject_regex.py b/smash/web/migrations/0120_study_nd_number_study_subject_regex.py new file mode 100644 index 00000000..9b4146ed --- /dev/null +++ b/smash/web/migrations/0120_study_nd_number_study_subject_regex.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2018-10-24 12:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0119_auto_20181024_1158'), + ] + + operations = [ + migrations.AddField( + model_name='study', + name='nd_number_study_subject_regex', + field=models.CharField(default=b'^ND\\d{4}$', help_text=b'Defines the regex to check the identification number used for each study subject.', max_length=255, verbose_name=b'Study Subject ND Number Regex'), + ), + ] diff --git a/smash/web/migrations/0121_auto_20181024_1859.py b/smash/web/migrations/0121_auto_20181024_1859.py new file mode 100644 index 00000000..2e0b4654 --- /dev/null +++ b/smash/web/migrations/0121_auto_20181024_1859.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2018-10-24 18:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0120_study_nd_number_study_subject_regex'), + ] + + operations = [ + migrations.AlterField( + model_name='study', + name='nd_number_study_subject_regex', + field=models.CharField(default=b'^ND\\d{4}$', help_text=b'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=b'Study Subject ND Number Regex'), + ), + ] diff --git a/smash/web/models/study.py b/smash/web/models/study.py index 4501a3be..831465d0 100644 --- a/smash/web/models/study.py +++ b/smash/web/models/study.py @@ -3,13 +3,20 @@ from django.db import models from web.models import StudyColumns, StudyNotificationParameters +import re + class Study(models.Model): + class Meta: app_label = 'web' name = models.CharField(max_length=255, verbose_name='Name') + nd_number_study_subject_regex = models.CharField( + max_length=255, verbose_name='Study Subject ND Number Regex', default=r'^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.') + columns = models.OneToOneField( StudyColumns, on_delete=models.CASCADE, @@ -24,8 +31,20 @@ class Study(models.Model): verbose_name="Auto create follow up visit" ) + def check_nd_number(self, nd_number): + regex = re.compile(self.nd_number_study_subject_regex) + return regex.match(nd_number) is not None + def __str__(self): return "%s" % self.name def __unicode__(self): return "%s" % self.name + + @staticmethod + def get_by_id(study_id): + study = Study.objects.filter(id=study_id) + if len(study) > 0: + return study[0] + else: + return None diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py index 38541389..e6242553 100644 --- a/smash/web/models/study_subject.py +++ b/smash/web/models/study_subject.py @@ -1,5 +1,6 @@ # coding=utf-8 import logging +import re from django.db import models from web.models import VoucherType, Appointment, Location, Visit @@ -7,7 +8,9 @@ from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES, FILE_STORAG logger = logging.getLogger(__name__) + class StudySubject(models.Model): + class Meta: app_label = 'web' @@ -18,7 +21,8 @@ class StudySubject(models.Model): visit.save() def finish_all_appointments(self): - appointments = Appointment.objects.filter(visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED) + appointments = Appointment.objects.filter( + visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED) for appointment in appointments: appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED appointment.save() @@ -164,18 +168,28 @@ class StudySubject(models.Model): for part in parts: chunks = part.strip().split('-') if len(chunks) == 2: - letter, number = chunks - tupl = (letter, int(number)) + letter, number = chunks + tupl = (letter, int(number)) else: - logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format(len(chunks), self.screening_number)) - tupl = (part.strip(), None) + logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format( + len(chunks), self.screening_number)) + tupl = (part.strip(), None) if pattern is not None and pattern in part: matches.append(tupl) else: reminder.append(tupl) return matches + sorted(reminder, reverse=reverse) - + + @staticmethod + def check_nd_number_regex(regex_str): + nd_numbers = StudySubject.objects.all().values_list('nd_number', flat=True) + regex = re.compile(regex_str) + for nd_number in nd_numbers: + if regex.match(nd_number) is None: + return False + return True + def __str__(self): return "%s %s" % (self.subject.first_name, self.subject.last_name) diff --git a/smash/web/templates/study/edit.html b/smash/web/templates/study/edit.html index 54be5ce7..e1e8d594 100644 --- a/smash/web/templates/study/edit.html +++ b/smash/web/templates/study/edit.html @@ -6,7 +6,12 @@ {{ block.super }} <!-- DataTables --> <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}"> - + <style type="text/css"> + .tooltip-inner { + max-width: 350px; + width: 350px; + } + </style> {% include "includes/datepicker.css.html" %} {% include "includes/datetimepicker.css.html" %} {% endblock styles %} @@ -45,6 +50,9 @@ <div class="col-md-6 form-group {% if field.errors %}has-error{% endif %}"> <label for="{# TODO #}" class="col-sm-4 control-label"> {{ field.label }} + {% if field.help_text %} + <i class="fa fa-info-circle" aria-hidden="true" data-toggle="tooltip" data-placement="bottom" title="{{field.help_text}}"></i> + {% endif %} </label> <div class="col-sm-8"> diff --git a/smash/web/tests/forms/test_study_forms.py b/smash/web/tests/forms/test_study_forms.py new file mode 100644 index 00000000..a150a83c --- /dev/null +++ b/smash/web/tests/forms/test_study_forms.py @@ -0,0 +1,40 @@ +from django.test import TestCase +from django.forms import ValidationError +from web.tests.functions import create_study_subject +from web.forms.study_forms import StudyEditForm +from web.models.study import Study +from web.models.study_subject import StudySubject + +class StudyFormTests(TestCase): + + def test_study_default_regex(self): + # this will add a studysubject with a NDnumber + StudySubject.objects.all().delete() + create_study_subject(nd_number='ND0001') + form = StudyEditForm() + # set default regex + nd_number_study_subject_regex_default = Study._meta.get_field( + 'nd_number_study_subject_regex').get_default() + form.cleaned_data = { + 'nd_number_study_subject_regex': nd_number_study_subject_regex_default} + self.assertTrue(form.clean_nd_number_study_subject_regex() + == nd_number_study_subject_regex_default) + # test wrong regex + form = StudyEditForm() + nd_number_study_subject_regex_default = r'^nd\d{5}$' + form.cleaned_data = { + 'nd_number_study_subject_regex': nd_number_study_subject_regex_default} + self.assertRaises( + ValidationError, form.clean_nd_number_study_subject_regex) + + def test_study_other_regex(self): + StudySubject.objects.all().delete() + # this will add a studysubject with a NDnumber + create_study_subject(nd_number='nd00001') + form = StudyEditForm() + # test new regex + nd_number_study_subject_regex_default = r'^nd\d{5}$' + form.cleaned_data = { + 'nd_number_study_subject_regex': nd_number_study_subject_regex_default} + self.assertTrue(form.clean_nd_number_study_subject_regex() + == nd_number_study_subject_regex_default) diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py index 3af08171..db061dd7 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -186,16 +186,22 @@ def create_subject(): ) -def create_study_subject(subject_id=1, subject=None): +def create_study_subject(subject_id=1, subject=None, nd_number='ND0001'): if subject is None: subject = create_subject() - return StudySubject.objects.create( + study_subject = StudySubject.objects.create( default_location=get_test_location(), type=SUBJECT_TYPE_CHOICES_CONTROL, screening_number="piotr's number" + str(subject_id), study=get_test_study(), subject=subject ) + if nd_number is not None: + study_subject.nd_number = nd_number + study_subject.save() + + return study_subject + def create_study_subject_with_multiple_screening_numbers(subject_id=1, subject=None): if subject is None: @@ -242,7 +248,8 @@ def create_worker(user=None, with_test_location=False): if with_test_location: worker.locations = [get_test_location()] worker.save() - WorkerStudyRole.objects.create(worker=worker, study_id=GLOBAL_STUDY_ID, role=ROLE_CHOICES_DOCTOR) + WorkerStudyRole.objects.create( + worker=worker, study_id=GLOBAL_STUDY_ID, role=ROLE_CHOICES_DOCTOR) return worker @@ -255,7 +262,8 @@ def create_voucher_partner(): unit="LCSB", phone_number="0123456789" ) - WorkerStudyRole.objects.create(worker=worker, study_id=GLOBAL_STUDY_ID, role=WORKER_VOUCHER_PARTNER) + WorkerStudyRole.objects.create( + worker=worker, study_id=GLOBAL_STUDY_ID, role=WORKER_VOUCHER_PARTNER) return worker @@ -349,10 +357,12 @@ def format_form_field(value): def prepare_test_redcap_connection(): Language.objects.create(name="Finnish").save() Language.objects.create(name="Italian").save() - token_item = ConfigurationItem.objects.filter(type=REDCAP_TOKEN_CONFIGURATION_TYPE)[0] + token_item = ConfigurationItem.objects.filter( + type=REDCAP_TOKEN_CONFIGURATION_TYPE)[0] # noinspection SpellCheckingInspection token_item.value = "5C75EEC3DBDDA5218B6ACC0424B3F695" token_item.save() - url_item = ConfigurationItem.objects.filter(type=REDCAP_BASE_URL_CONFIGURATION_TYPE)[0] + url_item = ConfigurationItem.objects.filter( + type=REDCAP_BASE_URL_CONFIGURATION_TYPE)[0] url_item.value = "https://luxparktest.org/redcap/" url_item.save() diff --git a/smash/web/tests/models/test_study.py b/smash/web/tests/models/test_study.py index 9c9e02f7..c535aa57 100644 --- a/smash/web/tests/models/test_study.py +++ b/smash/web/tests/models/test_study.py @@ -1,14 +1,22 @@ import logging from django.test import TestCase - -from web.tests.functions import create_study +from django.forms import ValidationError +from web.tests.functions import create_study, create_study_subject +from web.forms.study_forms import StudyEditForm +from web.models.study import Study +from web.models.study_subject import StudySubject logger = logging.getLogger(__name__) class StudyTests(TestCase): + def test_image_img(self): study = create_study() - self.assertTrue(study.name in str(study)) + + def test_check_nd_number(self): + study = create_study() + # check the default regex + self.assertTrue(study.check_nd_number('ND0001')) diff --git a/smash/web/tests/models/test_study_subject.py b/smash/web/tests/models/test_study_subject.py index b70add8c..f91762f3 100644 --- a/smash/web/tests/models/test_study_subject.py +++ b/smash/web/tests/models/test_study_subject.py @@ -2,41 +2,73 @@ from django.test import TestCase from web.models import Appointment from web.models import Visit +from web.models import StudySubject, Study from web.tests.functions import create_study_subject, create_appointment, create_study_subject_with_multiple_screening_numbers from web.tests.functions import create_visit class SubjectModelTests(TestCase): + def test_mark_as_resigned(self): subject = create_study_subject() visit = create_visit(subject) 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.assertEquals(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + self.assertEquals( + Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) + + def test_check_nd_number_regex(self): + # 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() + self.assertTrue(StudySubject.check_nd_number_regex( + nd_number_study_subject_regex_default)) + # this will add a studysubject with a NDnumber + study_subject = create_study_subject(nd_number='ND0001') + + self.assertTrue(StudySubject.check_nd_number_regex( + nd_number_study_subject_regex_default)) + # delete everything + StudySubject.objects.all().delete() + # this will add a studysubject with a NDnumber + create_study_subject(nd_number='ND00001') + self.assertFalse(StudySubject.check_nd_number_regex( + nd_number_study_subject_regex_default)) def test_sort_matched_screening_first(self): def create_result(phrase, subject_id=1): phrase = phrase.format(subject_id=subject_id) phrase = phrase.split(';') - for i,r in enumerate(phrase): + for i, r in enumerate(phrase): letter, num = phrase[i].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)) - \ No newline at end of file + 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)) diff --git a/smash/web/tests/view/test_appointments.py b/smash/web/tests/view/test_appointments.py index a1544f21..8f24b339 100644 --- a/smash/web/tests/view/test_appointments.py +++ b/smash/web/tests/view/test_appointments.py @@ -102,7 +102,7 @@ class AppointmentsViewTests(LoggedInTestCase): self.assertEqual(Appointment.APPOINTMENT_STATUS_FINISHED, appointment_result.status) def test_save_appointments_edit_with_invalid_nd_number(self): - subject = create_study_subject() + subject = create_study_subject(nd_number=None) visit = create_visit(subject) appointment = create_appointment(visit, get_today_midnight_date()) diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py index c4a9ddf1..ee45e0d1 100644 --- a/smash/web/views/appointment.py +++ b/smash/web/views/appointment.py @@ -1,17 +1,18 @@ # coding=utf-8 import logging import re - +import datetime from django.contrib import messages from django.core.exceptions import ValidationError from django.shortcuts import get_object_or_404, redirect +from web.models.constants import GLOBAL_STUDY_ID from web.models.appointment_list import APPOINTMENT_LIST_APPROACHING, APPOINTMENT_LIST_GENERIC, \ APPOINTMENT_LIST_UNFINISHED from . import wrap_response from web.forms import AppointmentDetailForm, AppointmentEditForm, AppointmentAddForm, SubjectEditForm, \ StudySubjectEditForm -from ..models import Appointment, StudySubject, MailTemplate +from ..models import Appointment, StudySubject, MailTemplate, Visit, Study logger = logging.getLogger(__name__) @@ -40,6 +41,13 @@ def appointment_details(request, id): def appointment_add(request, visit_id=None): + if visit_id is not None: + visit = get_object_or_404(Visit, id=visit_id) + visit_start = visit.datetime_begin.strftime("%Y-%m-%d") + visit_end = visit.datetime_end.strftime("%Y-%m-%d") + else: + visit_start = datetime.datetime.today().strftime("%Y-%m-%d") + visit_end = datetime.datetime.today().strftime("%Y-%m-%d") if request.method == 'POST': form = AppointmentAddForm(request.POST, request.FILES, user=request.user) if form.is_valid(): @@ -49,11 +57,16 @@ def appointment_add(request, visit_id=None): return redirect('web.views.appointments') else: return redirect('web.views.visit_details', id=visit_id) + else: + raise ValidationError("Invalid request: Errors: {}. Non field errors: {}".format(form.errors, form.non_field_errors())) + else: form = AppointmentAddForm(user=request.user) return wrap_response(request, 'appointments/add.html', - {'form': form, 'visitID': visit_id, 'full_list': APPOINTMENT_LIST_GENERIC}) + {'form': form, 'visitID': visit_id, 'isGeneral': visit_id is None, + 'visit_start': visit_start, 'visit_end': visit_end, + 'full_list': APPOINTMENT_LIST_GENERIC}) def appointment_edit(request, id): @@ -61,6 +74,7 @@ def appointment_edit(request, id): study_subject_form = None subject_form = None contact_attempts = None + study = Study.get_by_id(GLOBAL_STUDY_ID) if request.method == 'POST': appointment_form = AppointmentEditForm(request.POST, @@ -89,7 +103,7 @@ def appointment_edit(request, id): status=Appointment.APPOINTMENT_STATUS_FINISHED).count() == 0: adjust_date = True if appointment_form.cleaned_data["status"] == Appointment.APPOINTMENT_STATUS_FINISHED: - if re.match('ND[0-9][0-9][0-9][0-9]', study_subject_form.cleaned_data["nd_number"]) is None: + if not study.check_nd_number(study_subject_form.cleaned_data["nd_number"]): study_subject_form.add_error('nd_number', ValidationError("invalid ND number")) is_valid_form = False if is_valid_form: -- GitLab