Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The cancer is gone -- phase 1 #38

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
'bootstrap3',
'simple_history',
'adminsortable',
'phonenumber_field',
]

LOCAL_APPS = [
Expand Down Expand Up @@ -318,6 +319,11 @@
# -----------------------------------------------------------------------------
CRISPY_TEMPLATE_PACK = 'bootstrap3'


# django-phonenumber-field
PHONENUMBER_DB_FORMAT = 'E164'
PHONENUMBER_DEFAULT_REGION = 'US'

# osler
# -----------------------------------------------------------------------------

Expand Down
6 changes: 3 additions & 3 deletions local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
depends_on:
- postgres
volumes:
- .:/app
- .:/app:delegated
env_file:
- ./.envs/.local/.django
- ./.envs/.local/.postgres
Expand All @@ -31,8 +31,8 @@ services:
image: osler_production_postgres
container_name: postgres
volumes:
- local_postgres_data:/var/lib/postgresql/data
- local_postgres_data_backups:/backups
- local_postgres_data:/var/lib/postgresql/data:cached
- local_postgres_data_backups:/backups:cached
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How often is the backup updated. Does it make sense to cache it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm totally honest, I have no idea. According to that article, the difference should be imperceptible to the user. I haven't really pushed on it that much but after I did this (and restarted Docker) it really improved the performance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to say to uncache the backups but keep postgresql cached, since the backup isn't read from very often.

env_file:
- ./.envs/.local/.postgres

Expand Down
82 changes: 51 additions & 31 deletions osler/core/forms.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
'''Forms for the Oser core components.'''

from django.forms import (
Form, CharField, ModelForm, EmailField, CheckboxSelectMultiple,
ModelMultipleChoiceField, CheckboxInput)
ModelMultipleChoiceField, CheckboxInput, HiddenInput)
from django.contrib.auth.forms import AuthenticationForm

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import Group, Permission

from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Field, Layout, Row, Column
from crispy_forms.bootstrap import InlineCheckboxes
Expand All @@ -21,8 +23,6 @@
class CustomCheckbox(Field):
template = 'core/custom_checkbox.html'

# pylint: disable=I0011,E1305


class DuplicatePatientForm(Form):
first_name = CharField(label='First Name')
Expand All @@ -35,12 +35,33 @@ def __init__(self, *args, **kwargs):
self.helper.add_input(Submit('submit', 'Submit'))


class PatientPhoneNumberForm(ModelForm):

class Meta:
model = models.PatientPhoneNumber
fields = ['phone_number', 'description', 'patient']

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.fields['patient'].widget = HiddenInput()

self.helper = FormHelper(self)
self.helper.form_method = 'post'
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-2'
self.helper.field_class = 'col-lg-4'
self.helper.add_input(Submit('submit', 'Submit'))


class PatientForm(ModelForm):
class Meta(object):
model = models.Patient
exclude = ['demographics']
if not settings.OSLER_DISPLAY_CASE_MANAGERS:
exclude.append('case_managers')
exclude = (
['demographics', 'phone'] +
[f'alternate_phone_{i}' for i in range(1,5)] +
[f'alternate_phone_{i}_owner' for i in range(1,5)] +
['case_managers'] if settings.OSLER_DISPLAY_CASE_MANAGERS else []
)

if settings.OSLER_DISPLAY_CASE_MANAGERS:
case_managers = ModelMultipleChoiceField(
Expand All @@ -51,6 +72,16 @@ class Meta(object):
.order_by("last_name")
)

phone = PhoneNumberField(
widget=PhoneNumberInternationalFallbackWidget,
required=False
)

description = CharField(
label='Phone Label',
required=False
)

def __init__(self, *args, **kwargs):
super(PatientForm, self).__init__(*args, **kwargs)

Expand All @@ -59,34 +90,22 @@ def __init__(self, *args, **kwargs):
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-2'
self.helper.field_class = 'col-lg-8'
self.fields['phone'].widget.attrs['autofocus'] = True
self.fields['middle_name'].widget.attrs['autofocus'] = True
self.helper['languages'].wrap(InlineCheckboxes)
self.helper['ethnicities'].wrap(InlineCheckboxes)
self.helper.add_input(Submit('submit', 'Submit'))
self.fields['address'].widget.attrs = {'placeholder': settings.OSLER_DEFAULT_ADDRESS}


def clean(self):

cleaned_data = super(ModelForm, self).clean()

N_ALTS = 5

alt_phones = ["alternate_phone_" + str(i) for i in range(1, N_ALTS)]
alt_owners = [phone + "_owner" for phone in alt_phones]

for (alt_phone, alt_owner) in zip(alt_phones, alt_owners):

if cleaned_data.get(alt_owner) and not cleaned_data.get(alt_phone):
self.add_error(
alt_phone,
"An Alternate Phone is required" +
" if a Alternate Phone Owner is specified")

if cleaned_data.get(alt_phone) and not cleaned_data.get(alt_owner):
self.add_error(
alt_owner,
"An Alternate Phone Owner is required" +
" if a Alternate Phone is specified")
if cleaned_data.get('description') and not cleaned_data.get('phone'):
self.add_error(
description,
"Phone number is required if a description is provided."
)


class AbstractActionItemForm(ModelForm):
Expand Down Expand Up @@ -125,13 +144,14 @@ class Meta(object):
model = User
fields = [
'name',
'first_name',
'last_name',
'phone',
'languages',
'gender',
'first_name',
'last_name',
'phone',
'languages',
'gender',
'groups'
]
widgets = {'phone': PhoneNumberInternationalFallbackWidget}

def __init__(self, *args, **kwargs):
super(UserInitForm, self).__init__(*args, **kwargs)
Expand Down
27 changes: 27 additions & 0 deletions osler/core/migrations/0008_patientphonenumber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.1.2 on 2021-01-17 04:58

from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields


class Migration(migrations.Migration):

dependencies = [
('core', '0007_make_encounters'),
]

operations = [
migrations.CreateModel(
name='PatientPhoneNumber',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region=None)),
('description', models.CharField(blank=True, max_length=256)),
('patient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='phone_number_set', to='core.patient')),
],
options={
'unique_together': {('patient', 'description'), ('patient', 'phone_number')},
},
),
]
40 changes: 34 additions & 6 deletions osler/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from simple_history.models import HistoricalRecords
from adminsortable.models import SortableMixin
from phonenumber_field.modelfields import PhoneNumberField

from osler.core import validators
from osler.core import utils
Expand All @@ -37,6 +38,35 @@ def short_name(self):
return self.name[0]


class PatientPhoneNumber(models.Model):
"""A phone number for a patient.

Tracks a "description" for each phone number, which is a free text
field intended to be used for something like "cell" or "home".
"""

class Meta:
unique_together = (
('patient', 'description'),
('patient', 'phone_number'),
)

patient = models.ForeignKey(
'patient',
related_name='phone_number_set',
on_delete=models.CASCADE
)

# phone number field from django-phonenumber-field
phone_number = PhoneNumberField()

description = models.CharField(
max_length=256, blank=True)

def __str__(self):
return "%s (%s)" % (self.phone_number, self.description)


class ContactMethod(models.Model):
'''Simple text-contiaining class for storing the method of contacting a
patient for followup followed up with (i.e. phone, email, etc.)'''
Expand Down Expand Up @@ -343,13 +373,11 @@ def last_seen(self):
return now().date()

def all_phones(self):
'''Returns a list of tuples of the form (phone, owner) of all the
phones associated with this patient.'''
"""Returns a list of tuples of the form (phone, owner) of all the
phones associated with this patient."""

phones = [(self.phone, '')]
phones.extend([(getattr(self, 'alternate_phone_'+str(i)),
getattr(self, 'alternate_phone_'+str(i)+'_owner'))
for i in range(1, 5)])
phones = [(p.phone_number, p.description)
for p in self.phone_number_set.all()]

return phones

Expand Down
20 changes: 17 additions & 3 deletions osler/core/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class LanguageFactory(DjangoModelFactory):
class Meta:
model = models.Language

name = factory.Iterator(["English", "German", "Spanish", "Klingon"])
name = factory.Faker('language_name')


class ContactMethodFactory(DjangoModelFactory):
Expand Down Expand Up @@ -84,8 +84,8 @@ class Meta:
first_name = "Juggie"
last_name = "Brodeltein"
middle_name = "Bayer"
phone = '+49 178 236 5288',
languages = factory.SubFactory(LanguageFactory),
phone = factory.Faker('phone_number')
languages = factory.SubFactory(LanguageFactory)
gender = factory.SubFactory(GenderFactory)
address = 'Schulstrasse 9'
city = 'Munich'
Expand Down Expand Up @@ -132,6 +132,20 @@ def case_managers(self, create, extracted, **kwargs):
self.case_managers.add(manager)


class PatientPhoneNumberFactory(DjangoModelFactory):

class Meta:
model = models.PatientPhoneNumber

# frustratingly, PhoneNumberField doesn't support very varied input
# formats, like those produced by faker. We should probably fix this.
# phone_number = factory.Faker('phone_number')
phone_number = '425 243 9115'

patient = factory.SubFactory(PatientFactory)
description = factory.Faker('text', max_nb_chars=15)


class DocumentTypeFactory(DjangoModelFactory):

class Meta:
Expand Down
31 changes: 8 additions & 23 deletions osler/core/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ def setUp(self):

self.valid_pt_dict = factory.build(
dict, FACTORY_CLASS=factories.PatientFactory)
print(self.valid_pt_dict)

def phone_description_only_when_phone_provided(self):

del self.valid_pt_dict['phone']
form = forms.PatientForm(data=self.valid_pt_dict)
assert len(form['description'].errors) > 0


def test_form_casemanager_options(self):
"""PatientForm only offers valid case managers as options.
Expand Down Expand Up @@ -105,26 +113,3 @@ def test_form_casemanager_options(self):
form_data['case_managers'] = [pvds[2].pk]
form = forms.PatientForm(data=form_data)
assert len(form['case_managers'].errors) == 0

def test_missing_alt_phone(self):
'''Missing the alternative phone w/o alt phone owner should fail.'''
form_data = self.valid_pt_dict

form_data['alternate_phone_1_owner'] = "Jamal"
# omit 'alternate_phone', should get an error

form = forms.PatientForm(data=form_data)

# and expect an error to be on the empty altphone field
self.assertNotEqual(len(form['alternate_phone_1'].errors), 0)

def test_missing_alt_phone_owner(self):
'''Missing the alt phone owner w/o alt phone should fail.'''
form_data = self.valid_pt_dict

form_data['alternate_phone_1'] = "4258612322"
# omit 'alternate_phone', should get an error

form = forms.PatientForm(data=form_data)
# we expect errors on the empty alternate_phone_1_owner field
self.assertNotEqual(len(form['alternate_phone_1_owner'].errors), 0)
Loading