Skip to content

Commit

Permalink
Merge pull request #958 from openhealthcare/471-arbitrary-columns
Browse files Browse the repository at this point in the history
471 arbitrary columns
  • Loading branch information
fredkingham authored Jan 30, 2017
2 parents 4f2694e + 2ca2000 commit 2b3e63e
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ lists as tabs at the top of each member list.
To enable custom sort orders for individual `PatientList`s we introduce the `comparator_service` attribute.
This names an Angular service which will return a list of comparator functions.

#### PatientList Arbitrary columns

We now explicitly enable columns in spreadhseet lists that are not tied to subrecords. These can be
included in PatientList schema instances as explicit Column() entries.

#### Template re-naming

Modal_base has now been moved into a folder called base_templates. Its also now got a form_modal_base and a two_column_form_modal_base.
Expand Down
21 changes: 18 additions & 3 deletions doc/docs/guides/list_views.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,25 @@ link text to our list.

### Schemas

Schemas are lists of Subrecords that we would like to display in our list. By default we
render the subrecord display_template, and allow editing and addition of each subrecord in
place.
The schema attribute declares the columns of a PatientList. The entries in a schema may either
be `Subrecord` instances, or instances of `opal.core.patient_lists.Column`.

#### Custom Columns

Although most schema entries will be subrecords, it can be useful to have non-subrecord columns.
For instance because you want to allow a composite
column fo mulitple `Subrecords` or because we want to simply render arbitrary markup.

Columns require the title, and template_path to be set, and are simply included in the schema
list.

```python
class MyMarkupList(patient_lists.PatientList):
schema = [
patient_lists.Column(title='Foo', template_path='foo/bar')
]

```

### Template selection

Expand Down
73 changes: 73 additions & 0 deletions opal/core/patient_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,68 @@
from opal.core import discoverable, exceptions, metadata


class Column(object):

def __init__(self,
name=None, title=None, singleton=None, icon=None,
limit=None, template_path=None, detail_template_path=None):
"""
Set up initial properties from either models or explicit arguments
"""

self.name = name
self.title = title
self.single = singleton
self.icon = icon
self.list_limit = limit
self.template_path = template_path
self.detail_template_path = detail_template_path

required = ['title', 'template_path']
for attr in required:
if getattr(self, attr) is None:
raise ValueError(
'Column must have a {0}'.format(attr))

def to_dict(self, **kwargs):
return dict(
name=self.name,
title=self.title,
single=self.single,
icon=self.icon,
list_limit=self.list_limit,
template_path=self.template_path,
detail_template_path=self.detail_template_path
)


class ModelColumn(Column):

def __init__(self, patient_list, model):
self.model = model
self.patient_list = patient_list
from opal.models import Subrecord
if not issubclass(model, Subrecord):
raise ValueError('Model must be a opal.models.Subrecord subclass')

self.name = model.get_api_name()
self.title = model.get_display_name()
self.single = model._is_singleton
self.icon = getattr(model, '_icon', '')
self.list_limit = getattr(model, '_list_limit', None)
self.template_path = model.get_display_template(
patient_list=self.patient_list()
)
self.detail_template_path = model.get_detail_template(
patient_list=self.patient_list()
)

def to_dict(self, **kwargs):
dicted = super(ModelColumn, self).to_dict(**kwargs)
dicted['model_column'] = True
return dicted


class PatientList(discoverable.DiscoverableFeature,
discoverable.RestrictableFeature):
"""
Expand Down Expand Up @@ -43,6 +105,17 @@ def get_template_prefixes(self):
def schema(self):
raise ValueError("this needs to be implemented")

@classmethod
def schema_to_dicts(klass):
columns = []

for column in klass.schema:
if isinstance(column, Column):
columns.append(column.to_dict())
else:
columns.append(ModelColumn(klass, column).to_dict())
return columns

@property
def queryset(self):
raise ValueError("this needs to be implemented")
Expand Down
5 changes: 4 additions & 1 deletion opal/templates/patient_lists/spreadsheet_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ <h4 class="print-only">
>
{% for column in columns %}
<td {% ifequal column.title "Teams" %}class="screen-only"{% endifequal %} >
{% if column.model_column %}
<ul>
<li ng-repeat="item in row.{{column.name}} {% if column.list_limit %} |limitTo:{{ column.list_limit }}{% endif %} track by $index"
ng-dblclick="editNamedItem(row, '{{column.name}}', $index);$event.stopPropagation()"
Expand Down Expand Up @@ -174,9 +175,11 @@ <h4 class="print-only">
</span>
&nbsp;
</li>

{% endif %}
</ul>
{% else %}
{% include column.template_path %}
{% endif %}
</td>
{% endfor %}
</tr>
Expand Down
86 changes: 86 additions & 0 deletions opal/tests/test_patient_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,54 @@ class TestEmptyTabbedPatientListGroup(TabbedPatientListGroup):
Begin Tests
"""

class ColumnTestCase(OpalTestCase):

def test_set_non_inferred_attributes(self):
c = patient_lists.Column(
name='foo',
title='Foo',
singleton=True,
icon='fa-ya',
limit=5,
template_path='foo/bar',
detail_template_path='car/dar'
)
self.assertEqual(c.name, 'foo')
self.assertEqual(c.title, 'Foo')
self.assertEqual(c.single, True)
self.assertEqual(c.icon, 'fa-ya')
self.assertEqual(c.list_limit, 5)
self.assertEqual(c.template_path, 'foo/bar')
self.assertEqual(c.detail_template_path, 'car/dar')

def test_raises_if_no_title(self):
with self.assertRaises(ValueError):
patient_lists.Column(name='foo', template_path='foo/bar')

def test_raises_if_no_template_path(self):
with self.assertRaises(ValueError):
patient_lists.Column(title='Foo', name='foo')

class ModelColumnTestCase(OpalTestCase):

def test_sets_model(self):
c = patient_lists.ModelColumn(
MagicMock(name='mock list'),
models.Demographics
)
self.assertEqual(models.Demographics, c.model)

def test_pass_in_not_a_model(self):
with self.assertRaises(ValueError):
patient_lists.ModelColumn(None, OpalTestCase)

def test_to_dict_sets_model_column(self):
c = patient_lists.ModelColumn(
MagicMock(name='mock list'),
models.Demographics
)
self.assertEqual(True, c.to_dict()['model_column'])

class TestPatientList(OpalTestCase):

def setUp(self):
Expand All @@ -108,6 +156,44 @@ def test_get_queryset_default(self):
def test_visible_to(self):
self.assertTrue(TaggingTestPatientList.visible_to(self.user))

def test_schema_to_dicts(self):
dicts = [
{
'detail_template_path': 'records/demographics_detail.html',
'icon': '',
'list_limit': None,
'name': 'demographics',
'single': True,
'template_path': 'records/demographics.html',
'title': 'Demographics',
'model_column': True
}
]
self.assertEqual(dicts, TaggingTestPatientList.schema_to_dicts())

def test_schema_to_dicts_with_column(self):

class ColList(patient_lists.PatientList):
display_name = 'Columny List'

schema = [
patient_lists.Column(title='Foo', name='Bar',
template_path='foo/bar')
]

dicts = [
{
'detail_template_path': None,
'icon': None,
'list_limit': None,
'name': 'Bar',
'single': None,
'template_path': 'foo/bar',
'title': 'Foo'
}
]
self.assertEqual(dicts, ColList.schema_to_dicts())

def test_visible_to_restricted_only(self):
self.assertFalse(TaggingTestPatientList.visible_to(self.restricted_user))

Expand Down
1 change: 1 addition & 0 deletions opal/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ def get_slug(k): return 'the-slug'
list_limit = None,
template_path = 'records/colour.html',
detail_template_path = 'records/colour.html',
model_column=True,
)
]
context = view.get_column_context(slug='notarealthing')
Expand Down
20 changes: 1 addition & 19 deletions opal/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,7 @@ def get_column_context(self, **kwargs):
if not self.patient_list:
return []

context = []
for column in self.patient_list.schema:
column_context = {}
name = camelcase_to_underscore(column.__name__)
column_context['name'] = name
column_context['title'] = getattr(column, '_title',
name.replace('_', ' ').title())
column_context['single'] = column._is_singleton
column_context['icon'] = getattr(column, '_icon', '')
column_context['list_limit'] = getattr(column, '_list_limit', None)
column_context['template_path'] = column.get_display_template(
patient_list=self.patient_list()
)
column_context['detail_template_path'] = column.get_detail_template(
patient_list=self.patient_list()
)
context.append(column_context)

return context
return self.patient_list.schema_to_dicts()

def get_context_data(self, **kwargs):
context = super(PatientListTemplateView, self).get_context_data(**kwargs)
Expand Down

0 comments on commit 2b3e63e

Please sign in to comment.