Skip to content
Snippets Groups Projects
Commit 34ce8645 authored by Carlos Vega's avatar Carlos Vega
Browse files

Merge branch 'master' into feature/flag_tooltip_in_daily_planning

parents 921d909f d0152cb0
No related branches found
No related tags found
1 merge request!177Feature/flag tooltip in daily planning
Pipeline #7158 passed
Showing
with 1005 additions and 40 deletions
......@@ -18,3 +18,7 @@ pyexcel==0.5.3
pycurl==7.43.0
django-stronghold==0.2.9
timeout-decorator==0.4.0
Unidecode==1.0.22
Faker==0.9.2
pandas==0.23.4
numpy==1.15.2
\ No newline at end of file
This diff is collapsed.
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
......
......@@ -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)
......
# -*- 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'),
),
]
# -*- 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'),
),
]
# -*- 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'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2018-10-25 07:45
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('web', '0124_auto_20181017_1532'),
('web', '0121_auto_20181024_1859'),
]
operations = [
]
......@@ -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,28 @@ 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
@property
def has_voucher_types(self):
return self.columns.voucher_types
@property
def has_vouchers(self):
return self.columns.vouchers
@staticmethod
def get_by_id(study_id):
study = Study.objects.filter(id=study_id)
if len(study) > 0:
return study[0]
else:
return None
# 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)
......
......@@ -66,12 +66,14 @@
</a>
</li>
{% if study.has_vouchers %}
<li data-desc="vouchers">
<a href="{% url 'web.views.vouchers' %}">
<i class="fa fa-user-md"></i>
<span>Vouchers</span>
</a>
</li>
{% endif %}
<li data-desc="configuration" class="treeview">
<a href="#">
......@@ -83,8 +85,12 @@
<ul class="treeview-menu">
<li><a href="{% url 'web.views.configuration' %}">General</a></li>
<li><a href="{% url 'web.views.languages' %}">Languages</a></li>
{% if study.has_voucher_types %}
<li><a href="{% url 'web.views.voucher_types' %}">Voucher types</a></li>
{% endif %}
{% if study.has_vouchers %}
<li><a href="{% url 'web.views.workers' 'VOUCHER_PARTNER' %}">Voucher partners</a></li>
{% endif %}
<li><a href="{% url 'web.views.workers' 'HEALTH_PARTNER' %}">Health partners</a></li>
<li><a href="{% url 'web.views.edit_study' study_id %}">Study</a></li>
</ul>
......
......@@ -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">
......
......@@ -24,7 +24,7 @@
<div class="box box-info">
<div class="box-header with-border">
<a href="{% url 'web.views.subject_edit' id %}"
class="btn btn-block btn-default">Subject</a>
class="btn btn-block btn-default">Go back to Subject (discard changes)</a>
{% if allow_add_visit %}
<a href="{% url 'web.views.visit_add' id %}" type="button" class="btn btn-block btn-default">
Add visit</a>
......
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)
......@@ -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
......@@ -358,10 +366,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()
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'))
......@@ -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))
......@@ -137,7 +137,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())
......
......@@ -5,7 +5,7 @@ from django_common.auth_backends import User
from web.forms import WorkerForm
from web.models import Worker
from web.models.worker_study_role import WORKER_STAFF, ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER
from web.models.worker_study_role import WORKER_STAFF, ROLE_CHOICES_DOCTOR, WORKER_HEALTH_PARTNER, WORKER_VOUCHER_PARTNER
from web.tests import create_worker
from web.tests.functions import create_language, create_location, create_availability, format_form_field
from .. import LoggedInTestCase
......@@ -16,10 +16,18 @@ logger = logging.getLogger(__name__)
class WorkerViewTests(LoggedInTestCase):
def test_render_workers_list_request(self):
create_worker()
response = self.client.get(reverse('web.views.workers'))
self.assertEqual(response.status_code, 200)
def test_render_worker_type_request(self):
create_worker()
response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_STAFF}))
self.assertEqual(response.status_code, 200)
response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_HEALTH_PARTNER}))
self.assertEqual(response.status_code, 200)
response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_VOUCHER_PARTNER}))
self.assertEqual(response.status_code, 200)
def test_render_add_worker_request(self):
response = self.client.get(reverse('web.views.worker_add', kwargs={'worker_type': WORKER_STAFF}))
self.assertEqual(response.status_code, 200)
......
......@@ -5,7 +5,7 @@ from django.views.generic.base import ContextMixin
from web.models.constants import GLOBAL_STUDY_ID
from notifications import get_notifications
from ..models import Worker
from ..models import Worker, Study
handler404 = 'web.views.e404_page_not_found'
handler500 = 'web.views.e500_error'
......@@ -54,11 +54,13 @@ def extend_context(params, request):
role = '<No worker information>'
notifications = get_notifications(request.user)
final_params = params.copy()
study = Study.get_by_id(GLOBAL_STUDY_ID)
final_params.update({
'person': person,
'role': role,
'notifications': notifications,
'study_id': GLOBAL_STUDY_ID
'study_id': GLOBAL_STUDY_ID,
'study' : study
})
return final_params
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment