diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py index 2032b605ec28e932be7a882b665ce958279a3803..3cff62ba0a40e722aaec121f6243d10aaa0e0dd0 100644 --- a/smash/web/api_views/subject.py +++ b/smash/web/api_views/subject.py @@ -65,7 +65,7 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction): elif order_column == "default_location": result = subjects_to_be_ordered.order_by(order_direction + 'default_location') elif order_column == "dead": - result = subjects_to_be_ordered.order_by(order_direction + 'dead') + result = subjects_to_be_ordered.order_by(order_direction + 'subject__dead') elif order_column == "resigned": result = subjects_to_be_ordered.order_by(order_direction + 'resigned') elif order_column == "information_sent": @@ -174,7 +174,7 @@ def get_subjects_filtered(subjects_to_be_filtered, filters): elif column == "screening_number": result = result.filter(screening_number__icontains=value) elif column == "dead": - result = result.filter(dead=(value == "true")) + result = result.filter(subject__dead=(value == "true")) elif column == "resigned": result = result.filter(resigned=(value == "true")) elif column == "postponed": @@ -256,7 +256,8 @@ def subjects(request, type): "recordsFiltered": count_filtered, "data": data, }) - except: + except Exception as e: + logger.error(e, exc_info=True) return e500_error(request) @@ -321,7 +322,7 @@ def serialize_subject(subject): "nd_number": subject.nd_number, "screening_number": subject.screening_number, "default_location": location, - "dead": get_yes_no(subject.dead), + "dead": get_yes_no(subject.subject.dead), "resigned": get_yes_no(subject.resigned), "postponed": get_yes_no(subject.postponed), "information_sent": get_yes_no(subject.information_sent), diff --git a/smash/web/forms.py b/smash/web/forms.py index 6bd614c22ef7daf4da39491ea0f59b8d67be60fe..86181a51e7d32281fe4e42b18b21029265e6b563 100644 --- a/smash/web/forms.py +++ b/smash/web/forms.py @@ -1,11 +1,13 @@ import datetime +import logging from collections import OrderedDict 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 Subject, 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.views.notifications import get_filter_locations @@ -35,6 +37,8 @@ TIMEPICKER_DATE_ATTRS = { START_YEAR_STATISTICS = 2015 APPOINTMENT_TYPES_FIELD_POSITION = 1 +logger = logging.getLogger(__name__) + def validate_subject_nd_number(self, cleaned_data): if cleaned_data['nd_number'] != "": @@ -160,18 +164,13 @@ class StudySubjectEditForm(ModelForm): ) def __init__(self, *args, **kwargs): - was_dead = kwargs.get('was_dead', False) was_resigned = kwargs.get('was_resigned', False) if 'was_resigned' in kwargs: kwargs.pop('was_resigned') - if 'was_dead' in kwargs: - kwargs.pop('was_dead') 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_dead: - self.fields['dead'].disabled = True if was_resigned: self.fields['resigned'].disabled = True @@ -187,7 +186,6 @@ class StudySubjectEditForm(ModelForm): exclude = ['subject'] - class WorkerAddForm(ModelForm): class Meta: model = Worker @@ -548,3 +546,25 @@ class SubjectAddForm(ModelForm): 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__' diff --git a/smash/web/migrations/0068_remove_studysubject_dead.py b/smash/web/migrations/0068_remove_studysubject_dead.py new file mode 100644 index 0000000000000000000000000000000000000000..75f6b95408308d0413cff30232afe1947145333e --- /dev/null +++ b/smash/web/migrations/0068_remove_studysubject_dead.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-27 15:29 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0067_subject'), + ] + + operations = [ + migrations.RemoveField( + model_name='studysubject', + name='dead', + ), + ] diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py index 2c52d3321aa374160d946ec19673a1b1bfe8c782..46d336063d21870a5aa3779be9319e36102b626e 100644 --- a/smash/web/models/study_subject.py +++ b/smash/web/models/study_subject.py @@ -23,11 +23,6 @@ class StudySubject(models.Model): appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED appointment.save() - def mark_as_dead(self): - self.dead = True - self.finish_all_visits() - self.finish_all_appointments() - def mark_as_resigned(self): self.resigned = True self.finish_all_visits() @@ -170,11 +165,6 @@ class StudySubject(models.Model): verbose_name='PD in family', default=None, ) - dead = models.BooleanField( - verbose_name='Deceased', - default=False, - editable=True - ) resigned = models.BooleanField( verbose_name='Resigned', default=False, diff --git a/smash/web/models/subject.py b/smash/web/models/subject.py index ee7836ec794e80e626fa9d322b84e1db82515cd0..ae09e54185c35374522fb59b8efb790af5428c9b 100644 --- a/smash/web/models/subject.py +++ b/smash/web/models/subject.py @@ -2,7 +2,7 @@ from django.db import models from constants import SEX_CHOICES, COUNTRY_OTHER_ID -from web.models import Country +from web.models import Country, Visit, Appointment from . import Language @@ -92,6 +92,24 @@ class Subject(models.Model): editable=True ) + def mark_as_dead(self): + self.dead = True + self.finish_all_visits() + self.finish_all_appointments() + + def finish_all_visits(self): + visits = Visit.objects.filter(subject__subject=self, is_finished=False) + for visit in visits: + visit.is_finished = True + visit.save() + + def finish_all_appointments(self): + appointments = Appointment.objects.filter(visit__subject__subject=self, + status=Appointment.APPOINTMENT_STATUS_SCHEDULED) + for appointment in appointments: + appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED + appointment.save() + def __str__(self): return "%s %s" % (self.first_name, self.last_name) diff --git a/smash/web/models/visit.py b/smash/web/models/visit.py index a1b4c9acd8aec04963df70995e16250a3aa7019d..ab3252a4b13637c0ce9535b6a44a1bebb15c1877 100644 --- a/smash/web/models/visit.py +++ b/smash/web/models/visit.py @@ -51,7 +51,7 @@ class Visit(models.Model): self.is_finished = True self.save() - if (not self.subject.dead) and (not self.subject.resigned): + if (not self.subject.subject.dead) and (not self.subject.resigned): visit_started = Visit.objects.filter(subject=self.subject).filter(visit_number=1)[0].datetime_begin follow_up_number = Visit.objects.filter(subject=self.subject).count() + 1 diff --git a/smash/web/redcap_connector.py b/smash/web/redcap_connector.py index de0e9bf9bd1b26d1557dffebe3849b8d88aa6ef9..2f9b2db64a94da2d556aeacdec8010f81a988b98 100644 --- a/smash/web/redcap_connector.py +++ b/smash/web/redcap_connector.py @@ -189,8 +189,8 @@ class RedcapConnector(object): if subject_date_born != redcap_subject_date_born: field = InconsistentField.create("date of birth", subject_date_born, redcap_subject_date_born) fields.append(field) - if subject.dead != red_cap_subject.dead: - field = InconsistentField.create("dead", str(subject.dead), str(red_cap_subject.dead)) + if subject.subject.dead != red_cap_subject.dead: + field = InconsistentField.create("dead", str(subject.subject.dead), str(red_cap_subject.dead)) fields.append(field) if different_string(subject.mpower_id, red_cap_subject.mpower_id): field = InconsistentField.create("mpower id", subject.mpower_id, red_cap_subject.mpower_id) diff --git a/smash/web/templates/subjects/edit.html b/smash/web/templates/subjects/edit.html index 609cfbe22dab89900bfeb0343eeacf2bb54dbe3c..38e92c879ac82bec5e92986f4d92929f959553d0 100644 --- a/smash/web/templates/subjects/edit.html +++ b/smash/web/templates/subjects/edit.html @@ -31,7 +31,7 @@ back (discard changes)</a> </p> <p class="col-md-2 pull-right"> - <a href="{% url 'web.views.subject_visit_details' subject.id %}" type="button" + <a href="{% url 'web.views.subject_visit_details' study_subject.id %}" type="button" class="btn btn-block btn-default">Subject's visits</a> </p> </div> @@ -48,7 +48,23 @@ <div class="col-md-12"> {% csrf_token %} - {% for field in form %} + {% for field in study_subject_form %} + <div class="col-md-6 form-group {% if field.errors %}has-error{% endif %}"> + <label for="{# TODO #}" class="col-sm-4 control-label"> + {{ field.label }} + </label> + + <div class="col-sm-8"> + {{ field|add_class:'form-control' }} + </div> + + {% if field.errors %} + <span class="help-block"> {{ field.errors }} </span> + {% endif %} + </div> + {% endfor %} + + {% for field in subject_form %} <div class="col-md-6 form-group {% if field.errors %}has-error{% endif %}"> <label for="{# TODO #}" class="col-sm-4 control-label"> {{ field.label }} @@ -88,9 +104,9 @@ </div><!-- /.col-md-12 --> </div><!-- /.row --> - {% include 'includes/mail_templates_box.html' with instance_id=subject.id %} + {% include 'includes/mail_templates_box.html' with instance_id=study_subject.id %} - {% include 'includes/contact_attempts_box.html' with subject=subject contact_attempts=contact_attempts %} + {% include 'includes/contact_attempts_box.html' with subject=study_subject contact_attempts=contact_attempts %} <div class="modal modal-danger fade" id="confirm-dead-resigned-mark-dialog" tabindex="-1" role="dialog"> diff --git a/smash/web/tests/api_views/test_subject.py b/smash/web/tests/api_views/test_subject.py index 576cd3540781f8313fab6fb6b91737edf4b95743..bc88318073e0f4c10eb94e76764f7efff9963f7a 100644 --- a/smash/web/tests/api_views/test_subject.py +++ b/smash/web/tests/api_views/test_subject.py @@ -1,22 +1,25 @@ # coding=utf-8 -import json import datetime +import json +import logging from django.contrib.auth.models import User from django.test import Client from django.test import TestCase from django.urls import reverse -from web.views.notifications import get_today_midnight_date 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, Visit +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.views.notifications import get_today_midnight_date + +logger = logging.getLogger(__name__) class TestApi(TestCase): def setUp(self): - self.subject = create_study_subject() + self.study_subject = create_study_subject() self.client = Client() username = 'piotr' password = 'top_secret' @@ -35,8 +38,8 @@ class TestApi(TestCase): self.assertFalse(city_name in cities) - self.subject.city = city_name - self.subject.save() + self.study_subject.city = city_name + self.study_subject.save() response = self.client.get(reverse('web.api.cities')) cities = json.loads(response.content)['cities'] @@ -53,8 +56,8 @@ class TestApi(TestCase): self.assertFalse(referral_name in referrals) - self.subject.referral = referral_name - self.subject.save() + self.study_subject.referral = referral_name + self.study_subject.save() response = self.client.get(reverse('web.api.referrals')) referrals = json.loads(response.content)['referrals'] @@ -79,8 +82,8 @@ class TestApi(TestCase): def test_subjects_general_search(self): name = "Piotrek" - self.subject.first_name = name - self.subject.save() + self.study_subject.first_name = name + self.study_subject.save() params = { "columns[0][search][value]": "another_name", @@ -116,7 +119,7 @@ class TestApi(TestCase): self.assertEqual(result[length - index - 1], subjects[index]) def test_subjects_sort_nd_number(self): - subject = self.subject + subject = self.study_subject subject.nd_number = "PPP" subject.save() @@ -127,14 +130,14 @@ class TestApi(TestCase): self.check_subject_ordered("nd_number", [subject, subject2]) def test_subjects_sort_id(self): - subject = self.subject + subject = self.study_subject subject2 = create_study_subject(2) self.check_subject_ordered("id", [subject, subject2]) def test_subjects_sort_date_born(self): - subject = self.subject + subject = self.study_subject subject.date_born = get_today_midnight_date() subject.save() @@ -145,12 +148,12 @@ class TestApi(TestCase): self.check_subject_ordered("date_born", [subject, subject2]) def test_subjects_sort_default_location(self): - subject = self.subject + subject = self.study_subject self.check_subject_ordered("default_location", [subject]) def test_subjects_sort_screening_number(self): - subject = self.subject + subject = self.study_subject subject.screening_number = "PPP" subject.save() @@ -161,7 +164,7 @@ class TestApi(TestCase): self.check_subject_ordered("screening_number", [subject, subject2]) def test_subjects_sort_last_name(self): - subject = self.subject + subject = self.study_subject subject.last_name = "XXX" subject.save() @@ -172,18 +175,18 @@ class TestApi(TestCase): self.check_subject_ordered("last_name", [subject, subject2]) def test_subjects_sort_dead(self): - subject = self.subject - subject.dead = True - subject.save() + study_subject = self.study_subject + study_subject.subject.dead = True + study_subject.subject.save() - subject2 = create_study_subject(2) - subject2.dead = False - subject2.save() + study_subject2 = create_study_subject(2) + study_subject2.subject.dead = False + study_subject2.subject.save() - self.check_subject_ordered("dead", [subject2, subject]) + self.check_subject_ordered("dead", [study_subject2, study_subject]) def test_subjects_sort_resigned(self): - subject = self.subject + subject = self.study_subject subject.resigned = True subject.save() @@ -194,7 +197,7 @@ class TestApi(TestCase): self.check_subject_ordered("resigned", [subject2, subject]) def test_subjects_sort_postponed(self): - subject = self.subject + subject = self.study_subject subject.postponed = True subject.save() @@ -205,19 +208,19 @@ class TestApi(TestCase): self.check_subject_ordered("postponed", [subject2, subject]) def test_subjects_filter_dead(self): - subject = self.subject + subject = self.study_subject.subject subject.dead = True subject.save() - subject2 = create_study_subject(2) - subject2.dead = False - subject2.save() + study_subject2 = create_study_subject(2) + study_subject2.subject.dead = False + study_subject2.subject.save() - self.check_subject_filtered([["dead", "true"]], [subject]) - self.check_subject_filtered([["dead", "false"]], [subject2]) + self.check_subject_filtered([["dead", "true"]], [self.study_subject]) + self.check_subject_filtered([["dead", "false"]], [study_subject2]) def test_subjects_filter_nd_number(self): - subject = self.subject + subject = self.study_subject subject.nd_number = "PPP" subject.save() @@ -228,7 +231,7 @@ class TestApi(TestCase): self.check_subject_filtered([["nd_number", "P"]], [subject]) def test_subjects_filter_screening_number(self): - subject = self.subject + subject = self.study_subject subject.screening_number = "PPP" subject.save() @@ -239,7 +242,7 @@ class TestApi(TestCase): self.check_subject_filtered([["screening_number", "Q"]], [subject2]) def test_subjects_filter_last_name(self): - subject = self.subject + subject = self.study_subject subject.last_name = "XXX" subject.save() @@ -250,7 +253,7 @@ class TestApi(TestCase): self.check_subject_filtered([["last_name", "Q"]], []) def test_subjects_filter_resigned(self): - subject = self.subject + subject = self.study_subject subject.resigned = True subject.save() @@ -262,7 +265,7 @@ class TestApi(TestCase): self.check_subject_filtered([["resigned", "false"]], [subject2]) def test_subjects_filter_postponed(self): - subject = self.subject + subject = self.study_subject subject.postponed = True subject.save() @@ -274,26 +277,26 @@ class TestApi(TestCase): self.check_subject_filtered([["postponed", "false"]], [subject2]) def test_subjects_filter_default_location(self): - subject = self.subject + subject = self.study_subject self.check_subject_filtered([["default_location", str(subject.default_location.id)]], [subject]) self.check_subject_filtered([["default_location", "-1"]], []) def test_subjects_filter_unknown(self): - subject = self.subject + subject = self.study_subject self.check_subject_filtered([["some_unknown", "unknown data"]], [subject]) self.check_subject_filtered([["", ""]], [subject]) def test_serialize_subject(self): - subject = self.subject - subject.dead = True + study_subject = self.study_subject + study_subject.subject.dead = True - subject_json = serialize_subject(subject) + subject_json = serialize_subject(study_subject) self.assertEqual("YES", subject_json["dead"]) def test_subjects_filter_visit_1_DONE(self): - subject = self.subject + subject = self.study_subject subject.dead = True subject.save() @@ -313,7 +316,7 @@ class TestApi(TestCase): self.check_subject_filtered([["visit_1", "UPCOMING"]], []) def test_subjects_filter_visit_1_MISSED(self): - subject = self.subject + subject = self.study_subject subject.dead = True subject.save() @@ -333,7 +336,7 @@ class TestApi(TestCase): self.check_subject_filtered([["visit_1", "UPCOMING"]], []) def test_subjects_filter_visit_1_EXCEED(self): - subject = self.subject + subject = self.study_subject subject.dead = True subject.save() @@ -352,7 +355,7 @@ class TestApi(TestCase): self.check_subject_filtered([["visit_1", "UPCOMING"]], []) def test_subjects_filter_visit_1_IN_PROGRESS(self): - subject = self.subject + subject = self.study_subject subject.dead = True subject.save() @@ -371,7 +374,7 @@ class TestApi(TestCase): self.check_subject_filtered([["visit_1", "UPCOMING"]], []) def test_subjects_filter_visit_1_SHOULD_BE_IN_PROGRESS(self): - subject = self.subject + subject = self.study_subject subject.dead = True subject.save() @@ -387,7 +390,7 @@ class TestApi(TestCase): self.check_subject_filtered([["visit_1", "UPCOMING"]], []) def test_subjects_filter_visit_1_UPCOMING(self): - subject = self.subject + subject = self.study_subject subject.dead = True subject.save() @@ -406,7 +409,7 @@ class TestApi(TestCase): self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], []) def test_subjects_filter_visit_1_visit_2_combined(self): - subject = self.subject + subject = self.study_subject subject.save() visit = create_visit(subject) diff --git a/smash/web/tests/models/test_study_subject.py b/smash/web/tests/models/test_study_subject.py new file mode 100644 index 0000000000000000000000000000000000000000..cebd56b769b72493aac87487444c0594b856bbcd --- /dev/null +++ b/smash/web/tests/models/test_study_subject.py @@ -0,0 +1,21 @@ +from django.test import TestCase + +from web.models import Appointment +from web.models import Visit +from web.tests.functions import create_study_subject, create_appointment +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 + 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) diff --git a/smash/web/tests/models/test_subject.py b/smash/web/tests/models/test_subject.py index 1b60733e30d2f01f3c15dcb22cfedeacbd078173..8bfdafdd9443d24be50449052c2337cb33f79997 100644 --- a/smash/web/tests/models/test_subject.py +++ b/smash/web/tests/models/test_subject.py @@ -1,17 +1,16 @@ -from django.contrib.auth.models import User -from django.test import Client from django.test import TestCase -from web.tests.functions import create_study_subject, create_appointment, create_worker -from web.tests.functions import create_visit from web.models import Appointment from web.models import Visit +from web.tests.functions import create_study_subject, create_appointment +from web.tests.functions import create_visit class SubjectModelTests(TestCase): def test_mark_as_dead(self): - subject = create_study_subject() - visit = create_visit(subject) + study_subject = create_study_subject() + subject = study_subject.subject + visit = create_visit(study_subject) appointment = create_appointment(visit) subject.mark_as_dead() @@ -21,16 +20,3 @@ class SubjectModelTests(TestCase): self.assertTrue(subject.dead) self.assertTrue(visit_finished) self.assertEquals(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status) - - 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 - 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) \ No newline at end of file diff --git a/smash/web/tests/models/test_visit.py b/smash/web/tests/models/test_visit.py index 6d5e7d8e541fe37214f61f48fdcd0346a7763bc0..b92a51bad03d90744ecbae19c14ec06c601a89ca 100644 --- a/smash/web/tests/models/test_visit.py +++ b/smash/web/tests/models/test_visit.py @@ -19,14 +19,14 @@ class VisitModelTests(TestCase): self.assertEquals(2, visit_count) def test_mark_as_finished_2(self): - subject = create_study_subject() - visit = create_visit(subject) - subject.dead = True - subject.save() + study_subject = create_study_subject() + visit = create_visit(study_subject) + study_subject.subject.dead = True + study_subject.subject.save() visit.mark_as_finished() - visit_count = Visit.objects.filter(subject=subject).count() + visit_count = Visit.objects.filter(subject=study_subject).count() self.assertEquals(1, visit_count) def test_mark_as_finished_3(self): diff --git a/smash/web/tests/test_RedcapConnector.py b/smash/web/tests/test_RedcapConnector.py index 3fc4c9d7cc4dc5b8bbf998e4b2becbc68e5fef94..e6faaf0dc6e92d1022d13c92b19ad4bfc627be8a 100644 --- a/smash/web/tests/test_RedcapConnector.py +++ b/smash/web/tests/test_RedcapConnector.py @@ -87,12 +87,12 @@ class TestRedcapConnector(TestCase): def test_create_inconsistent_data_for_dead(self): prepare_test_redcap_connection() - subject = create_study_subject() + study_subject = create_study_subject() - redcap_subject = self.create_redcap_subject_from_smash_subject(subject) - subject.dead = not subject.dead + redcap_subject = self.create_redcap_subject_from_smash_subject(study_subject) + study_subject.subject.dead = not study_subject.subject.dead - self.check_single_inconsistency(redcap_subject, subject) + self.check_single_inconsistency(redcap_subject, study_subject) def test_create_inconsistent_data_for_language(self): language = Language.objects.create(name="xx") @@ -142,7 +142,7 @@ class TestRedcapConnector(TestCase): for language in subject.languages.all(): redcap_subject.add_language(language) redcap_subject.mpower_id = subject.mpower_id - redcap_subject.dead = subject.dead + redcap_subject.dead = subject.subject.dead redcap_subject.date_born = subject.date_born redcap_subject.nd_number = subject.nd_number redcap_subject.sex = subject.sex diff --git a/smash/web/tests/view/test_notifications.py b/smash/web/tests/view/test_notifications.py index c4dddfab55ffbcc3b733c56d0d744d0d43849c1c..031a225c754c4a41bb50bc81a3f8fe93f1c2b3fd 100644 --- a/smash/web/tests/view/test_notifications.py +++ b/smash/web/tests/view/test_notifications.py @@ -225,9 +225,9 @@ class NotificationViewTests(LoggedInTestCase): def test_get_subject_with_no_visit_notifications_count_4(self): original_notification = get_subject_with_no_visit_notifications_count(self.user) - subject = create_study_subject() - subject.dead = True - subject.save() + study_subject = create_study_subject() + study_subject.subject.dead = True + study_subject.subject.save() notification = get_subject_with_no_visit_notifications_count(self.user) self.assertEquals(original_notification.count, notification.count) diff --git a/smash/web/tests/view/test_subjects.py b/smash/web/tests/view/test_subjects.py index a569464980e9dbf3a5059932f482c46f24f0cb9e..64bb47c568b411dd214411f0304f45a257c1ab28 100644 --- a/smash/web/tests/view/test_subjects.py +++ b/smash/web/tests/view/test_subjects.py @@ -1,185 +1,178 @@ import datetime +import logging from django.urls import reverse -from web.forms import StudySubjectAddForm, StudySubjectEditForm +from web.forms import StudySubjectAddForm, StudySubjectEditForm, SubjectEditForm, SubjectAddForm from web.models import StudySubject from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL, SUBJECT_TYPE_CHOICES_PATIENT, \ COUNTRY_AFGHANISTAN_ID, COUNTRY_OTHER_ID from web.tests import LoggedInWithWorkerTestCase -from web.tests.functions import create_study_subject, create_visit, create_appointment, get_test_location, \ - create_subject +from web.tests.functions import create_study_subject, create_visit, create_appointment, get_test_location from web.views.notifications import get_today_midnight_date +logger = logging.getLogger(__name__) + class SubjectsViewTests(LoggedInWithWorkerTestCase): def setUp(self): super(SubjectsViewTests, self).setUp() - self.subject = create_subject() + self.study_subject = create_study_subject() - def test_subjects_add(self): + def test_render_subjects_add(self): self.worker.save() response = self.client.get(reverse('web.views.subject_add')) self.assertEqual(response.status_code, 200) def test_render_subject_edit(self): - subject = create_study_subject() - - response = self.client.get(reverse('web.views.subject_edit', kwargs={'id': subject.id})) + response = self.client.get(reverse('web.views.subject_edit', kwargs={'id': self.study_subject.id})) self.assertEqual(response.status_code, 200) def test_render_subject_visit_details(self): - subject = create_study_subject() - visit = create_visit(subject) + visit = create_visit(self.study_subject) create_appointment(visit) - response = self.client.get(reverse('web.views.subject_visit_details', kwargs={'id': subject.id})) + response = self.client.get(reverse('web.views.subject_visit_details', kwargs={'id': self.study_subject.id})) self.assertEqual(response.status_code, 200) self.assertFalse("Add visit" in response.content) def test_render_subject_visit_details_without_visit(self): - subject = create_study_subject() - - response = self.client.get(reverse('web.views.subject_visit_details', kwargs={'id': subject.id})) + response = self.client.get(reverse('web.views.subject_visit_details', kwargs={'id': self.study_subject.id})) self.assertEqual(response.status_code, 200) self.assertTrue("Add visit" in response.content) def test_save_subject_edit_when_resigned_without_reason(self): - subject = create_study_subject() - form_subject = StudySubjectEditForm(instance=subject) - form_data = {} - for key, value in form_subject.initial.items(): - if value is not None: - form_data[key] = value + form_data = self.create_edit_form_data_for_study_subject() - form_data['dead'] = "True" - form_data['resigned'] = "True" + form_data['subject-dead'] = "True" + form_data['study_subject-resigned'] = "True" response = self.client.post( - reverse('web.views.subject_edit', kwargs={'id': subject.id}), data=form_data) + reverse('web.views.subject_edit', kwargs={'id': self.study_subject.id}), data=form_data) self.assertEqual(response.status_code, 200) self.assertTrue("Resign reason cannot be empty" in response.content) def test_save_subject_edit(self): - subject = create_study_subject() - form_subject = StudySubjectEditForm(instance=subject) - form_data = {} - for key, value in form_subject.initial.items(): - if value is not None: - form_data[key] = value + form_data = self.create_edit_form_data_for_study_subject() - form_data['dead'] = "True" - form_data['resigned'] = "True" - form_data['resign_reason'] = "doesn't want to participate" + form_data['subject-dead'] = "True" + form_data['study_subject-resigned'] = "True" + form_data['study_subject-resign_reason'] = "doesn't want to participate" response = self.client.post( - reverse('web.views.subject_edit', kwargs={'id': subject.id}), data=form_data) + reverse('web.views.subject_edit', kwargs={'id': self.study_subject.id}), data=form_data) self.assertEqual(response.status_code, 302) - updated_subject = StudySubject.objects.filter(id=subject.id)[0] - self.assertTrue(updated_subject.dead) - self.assertTrue(updated_subject.resigned) + updated_study_subject = StudySubject.objects.filter(id=self.study_subject.id)[0] + self.assertTrue(updated_study_subject.subject.dead) + self.assertTrue(updated_study_subject.resigned) - def test_subjects_add_2(self): - - form = StudySubjectAddForm(user=self.user) - form_data = {"subject": self.subject.id} - for key, value in form.initial.items(): + def create_edit_form_data_for_study_subject(self): + form_study_subject = StudySubjectEditForm(instance=self.study_subject, prefix="study_subject") + form_subject = SubjectEditForm(instance=self.study_subject.subject, prefix="subject") + form_data = {} + for key, value in form_study_subject.initial.items(): + if value is not None: + form_data['study_subject-{}'.format(key)] = value + for key, value in form_subject.initial.items(): if value is not None: - form_data[key] = value + form_data['subject-{}'.format(key)] = value + return form_data + def create_add_form_data_for_study_subject(self): + form_study_subject = StudySubjectAddForm(prefix="study_subject", user=self.user) + form_subject = SubjectAddForm(prefix="subject") + form_data = {} + for key, value in form_study_subject.initial.items(): + if value is not None: + form_data['study_subject-{}'.format(key)] = value + for key, value in form_subject.initial.items(): + if value is not None: + form_data['subject-{}'.format(key)] = value self.add_valid_form_data_for_subject_add(form_data) - form_data["type"] = SUBJECT_TYPE_CHOICES_CONTROL - form_data["default_location"] = get_test_location().id + return form_data + + def test_subjects_add_2(self): + form_data = self.create_add_form_data_for_study_subject() + + form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL + form_data["study_subject-default_location"] = get_test_location().id response = self.client.post(reverse('web.views.subject_add'), data=form_data) self.assertEqual(response.status_code, 302) response = self.client.get(response.url) self.assertContains(response, "Subject created") - subject = StudySubject.objects.all()[0] + subject = StudySubject.objects.all().order_by("-id")[0] self.assertEqual("L-001", subject.screening_number, "prefix should start by L" + " as default location prefix is not defined and subject type is control") def add_valid_form_data_for_subject_add(self, form_data): - form_data["country"] = COUNTRY_AFGHANISTAN_ID - form_data["first_name"] = "John" - form_data["last_name"] = "Doe" - form_data["sex"] = SEX_CHOICES_MALE - form_data["type"] = SUBJECT_TYPE_CHOICES_PATIENT - form_data["subject"] = self.subject.id + form_data["subject-country"] = COUNTRY_AFGHANISTAN_ID + form_data["subject-first_name"] = "John" + form_data["subject-last_name"] = "Doe" + form_data["subject-sex"] = SEX_CHOICES_MALE + form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_PATIENT + form_data["study_subject-subject"] = self.study_subject.id + + # TODO remove after refactoring + form_data["study_subject-country"] = COUNTRY_AFGHANISTAN_ID + form_data["study_subject-first_name"] = "John" + form_data["study_subject-last_name"] = "Doe" + form_data["study_subject-sex"] = SEX_CHOICES_MALE def test_subjects_add_patient(self): + form_data = self.create_add_form_data_for_study_subject() - form = StudySubjectAddForm(user=self.user) - form_data = {} - for key, value in form.initial.items(): - if value is not None: - form_data[key] = value - - self.add_valid_form_data_for_subject_add(form_data) - form_data["default_location"] = get_test_location().id + form_data["study_subject-default_location"] = get_test_location().id response = self.client.post(reverse('web.views.subject_add'), data=form_data) self.assertEqual(response.status_code, 302) response = self.client.get(response.url) self.assertContains(response, "Subject created") - subject = StudySubject.objects.all()[0] + subject = StudySubject.objects.all().order_by("-id")[0] self.assertEqual("P-001", subject.screening_number, "prefix should start by P" + " as default location prefix is not defined and subject type is patient") def test_subjects_add_invalid(self): + form_data = self.create_add_form_data_for_study_subject() + form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL + form_data["study_subject-default_location"] = get_test_location().id - form = StudySubjectAddForm(user=self.user) - form_data = {} - for key, value in form.initial.items(): - if value is not None: - form_data[key] = value - - self.add_valid_form_data_for_subject_add(form_data) - form_data["country"] = COUNTRY_OTHER_ID + form_data["subject-country"] = COUNTRY_OTHER_ID response = self.client.post(reverse('web.views.subject_add'), data=form_data) self.assertTrue("Invalid data" in response.content) def test_subjects_add_with_prefixed_location(self): - - form = StudySubjectAddForm(user=self.user) - form_data = {} - for key, value in form.initial.items(): - if value is not None: - form_data[key] = value + form_data = self.create_add_form_data_for_study_subject() + form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL self.add_valid_form_data_for_subject_add(form_data) location = get_test_location() location.prefix = 'X' location.save() - form_data["default_location"] = location.id + form_data["study_subject-default_location"] = get_test_location().id response = self.client.post(reverse('web.views.subject_add'), data=form_data) self.assertEqual(response.status_code, 302) response = self.client.get(response.url) self.assertContains(response, "Subject created") - subject = StudySubject.objects.all()[0] + subject = StudySubject.objects.all().order_by("-id")[0] self.assertEqual("X-001", subject.screening_number, "prefix should start by X as default location prefix is equal to 'X'") def test_render_subjects(self): - create_study_subject() - response = self.client.get(reverse('web.views.subjects')) self.assertEqual(response.status_code, 200) def test_render_subjects_with_no_visit(self): - create_study_subject() - response = self.client.get(reverse('web.views.subject_no_visits')) self.assertEqual(response.status_code, 200) def test_render_subjects_require_contact(self): - subject = create_study_subject() - subject.datetime_contact_reminder = get_today_midnight_date() + datetime.timedelta(days=-1) + self.study_subject.datetime_contact_reminder = get_today_midnight_date() + datetime.timedelta(days=-1) response = self.client.get(reverse('web.views.subject_require_contact')) self.assertEqual(response.status_code, 200) diff --git a/smash/web/views/notifications.py b/smash/web/views/notifications.py index 287dc783322647d03f9fa03423aed735b92a0bab..3829d3ebebc09360004a2730fad67c52dd0df520 100644 --- a/smash/web/views/notifications.py +++ b/smash/web/views/notifications.py @@ -133,7 +133,7 @@ def get_notifications(the_user): def get_subjects_with_no_visit(user): result = StudySubject.objects.annotate(my_count=Count(Case(When(visit__is_finished=False, then=1)))).filter( - dead=False, + subject__dead=False, resigned=False, my_count=0, default_location__in=get_filter_locations(user), @@ -147,7 +147,7 @@ def get_subjects_with_reminder(user): tomorrow = datetime.datetime.now() + datetime.timedelta(hours=1) result = StudySubject.objects.filter( - dead=False, + subject__dead=False, resigned=False, default_location__in=get_filter_locations(user), datetime_contact_reminder__lt=tomorrow, diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py index d094a250cb8a5ad6cb49635ef19f3393adb5a03d..2db7e9d9588ebfd31b126db58373c9d912ad1e41 100644 --- a/smash/web/views/subject.py +++ b/smash/web/views/subject.py @@ -1,15 +1,19 @@ # coding=utf-8 +import logging + from django.contrib import messages from django.shortcuts import redirect, get_object_or_404 from . import wrap_response -from ..forms import StudySubjectAddForm, StudySubjectEditForm, VisitDetailForm +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" +logger = logging.getLogger(__name__) + def subjects(request): context = { @@ -21,18 +25,23 @@ def subjects(request): def subject_add(request): if request.method == 'POST': - form = StudySubjectAddForm(request.POST, request.FILES, user=request.user) - if form.is_valid(): - form.save() + study_subject_form = StudySubjectAddForm(request.POST, request.FILES, prefix="study_subject", user=request.user) + + subject_form = SubjectAddForm(request.POST, request.FILES, prefix="subject") + if study_subject_form.is_valid() and subject_form.is_valid(): + study_subject_form.save() + subject_form.save() messages.add_message(request, messages.SUCCESS, 'Subject created') - return redirect('web.views.subject_edit', id=form.instance.id) + return redirect('web.views.subject_edit', id=study_subject_form.instance.id) else: messages.add_message(request, messages.ERROR, 'Invalid data. Please fix data and try again.') else: - form = StudySubjectAddForm(user=request.user) + study_subject_form = StudySubjectAddForm(user=request.user, prefix="study_subject") + subject_form = SubjectAddForm(prefix="subject") - return wrap_response(request, 'subjects/add.html', {'form': form}) + return wrap_response(request, 'subjects/add.html', + {'study_subject_form': study_subject_form, 'subject_form': subject_form}) def subject_no_visits(request): @@ -50,45 +59,53 @@ def subject_require_contact(request): def subject_edit(request, id): - the_subject = get_object_or_404(StudySubject, id=id) - contact_attempts = the_subject.contactattempt_set.order_by('-datetime_when').all() - was_dead = the_subject.dead - was_resigned = the_subject.resigned + study_subject = get_object_or_404(StudySubject, id=id) + contact_attempts = study_subject.contactattempt_set.order_by('-datetime_when').all() + was_dead = study_subject.subject.dead + was_resigned = study_subject.resigned if request.method == 'POST': - form = StudySubjectEditForm(request.POST, request.FILES, instance=the_subject, was_dead=was_dead, - was_resigned=was_resigned) - if form.is_valid(): - form.save() + study_subject_form = StudySubjectEditForm(request.POST, request.FILES, instance=study_subject, + was_resigned=was_resigned, prefix="study_subject" + ) + subject_form = SubjectEditForm(request.POST, request.FILES, instance=study_subject.subject, + was_dead=was_dead, prefix="subject" + ) + if study_subject_form.is_valid() and subject_form.is_valid(): + study_subject_form.save() + subject_form.save() # check if subject was marked as dead or resigned - if form.cleaned_data['dead'] and not was_dead: - the_subject.mark_as_dead() - if form.cleaned_data['resigned'] and not was_resigned: - the_subject.mark_as_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: + study_subject.mark_as_resigned() messages.success(request, "Modifications saved") if '_continue' in request.POST: - return redirect('web.views.subject_edit', id=the_subject.id) + return redirect('web.views.subject_edit', id=study_subject.id) return redirect('web.views.subjects') else: messages.add_message(request, messages.ERROR, 'Invalid data. Please fix data and try again.') else: - form = StudySubjectEditForm(instance=the_subject, was_dead=was_dead, was_resigned=was_resigned) + study_subject_form = StudySubjectEditForm(instance=study_subject, was_resigned=was_resigned, + prefix="study_subject") + subject_form = SubjectEditForm(instance=study_subject.subject, was_dead=was_dead, prefix="subject") languages = [] - if the_subject.default_written_communication_language: - languages.append(the_subject.default_written_communication_language) - languages.extend(the_subject.languages.all()) + if study_subject.default_written_communication_language: + languages.append(study_subject.default_written_communication_language) + languages.extend(study_subject.languages.all()) return wrap_response(request, 'subjects/edit.html', { - 'form': form, - 'subject': the_subject, + 'study_subject_form': study_subject_form, + 'subject_form': subject_form, + 'study_subject': study_subject, 'contact_attempts': contact_attempts, 'mail_templates': MailTemplate.get_subject_mail_templates(languages) }) def subject_visit_details(request, id): - subject_to_be_viewed = get_object_or_404(StudySubject, id=id) - visits = subject_to_be_viewed.visit_set.order_by("-visit_number").all() + study_subject_to_be_viewed = get_object_or_404(StudySubject, id=id) + visits = study_subject_to_be_viewed.visit_set.order_by("-visit_number").all() visits_data = [] allow_add_visit = True for visit in visits: