diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8da9361..85ccfff52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +### 0.10.1 (Minor Release) + +#### Plugin API end points can now override application end points + +A change to the order that APIs are registered with Django Rest Framework allows +plugins to now override the core Opal application APIs. + +#### Fonts are now locally sourced + +Fonts are now served from Opal's static assets rather than from the Google CDN. + +#### print/screen stylesheets have been collapsed into opal.css + +Print/screen differences are now in opal.css with media tags. + +#### Google Analytics is now deferred + +The loading in of Google Analytics is now deferred to the bottom of the body +tag to allow the page to load without waiting on analytics scripts to load. + +#### Scaffold version control failures + +The `startplugin` and `startproject` commands initialize a git repository by +default. If we (The `subprocess` module) cannot find the `git` command, we now +continue with a message printed to screen rather than raising an exception. + +#### Episode.objects.serialised now uses select_related + +`ForeignKeyOrFreeText` fields now have their ForeignKey items preselected when +we use `Episode.objects.serialised`. This provides a speed boost for applications +with moderately heavy `ForeignKeyOrFreeText` usage. + +(Approx 30-40% in our tests.) + + ### 0.10.0 (Major Release) This is a major release with breaking changes from upstream dependencies. diff --git a/README.md b/README.md index 27f1ceb7a..ca31fe381 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ Opal ==== -[![Build Status](https://travis-ci.org/openhealthcare/opal.svg?branch=v0.10.0)](https://travis-ci.org/openhealthcare/opal) -[![Coverage Status](https://coveralls.io/repos/github/openhealthcare/opal/badge.svg?branch=v0.10.0)](https://coveralls.io/github/openhealthcare/opal?branch=v0.10.0) +[![Build Status](https://travis-ci.org/openhealthcare/opal.svg?branch=v0.10.1)](https://travis-ci.org/openhealthcare/opal) +[![Coverage Status](https://coveralls.io/repos/github/openhealthcare/opal/badge.svg?branch=v0.10.1)](https://coveralls.io/github/openhealthcare/opal?branch=v0.10.1) [![PyPI version](https://badge.fury.io/py/opal.svg)](https://badge.fury.io/py/opal) diff --git a/doc/docs/reference/upgrading.md b/doc/docs/reference/upgrading.md index 1be28911c..d14b45324 100644 --- a/doc/docs/reference/upgrading.md +++ b/doc/docs/reference/upgrading.md @@ -3,7 +3,21 @@ This document provides instructions for specific steps required to upgrading your Opal application to a later version where there are extra steps required. -### 0.9.1 -> 0.10.0 + +### 0.10.0 -> 0.10.1 + +#### Upgrading Opal + +How you do this depends on how you have configured your application, but updating your +requirements.txt to update the version should work. + + # requirements.txt + opal==0.10.1 + +There are no migrations or additional commands for this upgrae, and we are not aware of +any backwards incompatible changes. + +### 0.9.0 -> 0.10.0 #### Upgrading Opal @@ -103,21 +117,6 @@ logs into second will throw a CSRF failure because Django invalidates CSRF tokens on login. -### 0.9.0 -> 0.9.1 - -#### Upgrading Opal - -How you do this depends on how you have configured your application, but updating your -requirements.txt to update the version should work. - - # requirements.txt - opal==0.9.1 - -After re-installing (via for instance `pip install -r requirements.txt`) you will need to -run the migrations for Opal 0.9.1 - - $ python manage.py migrate opal - ### 0.8.3 -> 0.9.0 `episode.date_of_episode`, `episode.date_of_admission` and `episode.discharge_date` are all deprecated. diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml index 9aea5c071..57ff76b1c 100644 --- a/doc/mkdocs.yml +++ b/doc/mkdocs.yml @@ -117,7 +117,7 @@ dev_addr: 0.0.0.0:8965 include_next_prev: false extra: - version: v0.10.0 + version: v0.10.1 markdown_extensions: - fenced_code diff --git a/opal/_version.py b/opal/_version.py index 71d8e4615..c4bf81830 100644 --- a/opal/_version.py +++ b/opal/_version.py @@ -1,4 +1,4 @@ """ Declare our current version string """ -__version__ = '0.10.0' +__version__ = '0.10.1.rc1' diff --git a/opal/core/api.py b/opal/core/api.py index 47fff4a90..6ba20ab14 100644 --- a/opal/core/api.py +++ b/opal/core/api.py @@ -390,32 +390,39 @@ def retrieve(self, request, pk=None): return json_response(patientlist.to_dict(request.user)) -router.register('patient', PatientViewSet) -router.register('episode', EpisodeViewSet) -router.register('record', RecordViewSet) -router.register('userprofile', UserProfileViewSet) -router.register('user', UserViewSet) -router.register('tagging', TaggingViewSet) -router.register('patientlist', PatientListViewSet) -router.register('patientrecordaccess', PatientRecordAccessViewSet) +def register_plugin_apis(): + for plugin in plugins.OpalPlugin.list(): + for api in plugin.get_apis(): + router.register(*api) -router.register('referencedata', ReferenceDataViewSet) -router.register('metadata', MetadataViewSet) -for subrecord in subrecords(): - sub_name = subrecord.get_api_name() +def register_subrecords(): + for subrecord in subrecords(): + sub_name = subrecord.get_api_name() - class SubViewSet(SubrecordViewSet): - base_name = sub_name - model = subrecord + class SubViewSet(SubrecordViewSet): + base_name = sub_name + model = subrecord - router.register(sub_name, SubViewSet) + router.register(sub_name, SubViewSet) -def register_plugin_apis(): - for plugin in plugins.OpalPlugin.list(): - for api in plugin.get_apis(): - router.register(*api) +def initialize_router(): + # plugin apis get initialised first, so that plugins + # can + register_plugin_apis() + router.register('patient', PatientViewSet) + router.register('episode', EpisodeViewSet) + router.register('record', RecordViewSet) + router.register('userprofile', UserProfileViewSet) + router.register('user', UserViewSet) + router.register('tagging', TaggingViewSet) + router.register('patientlist', PatientListViewSet) + router.register('patientrecordaccess', PatientRecordAccessViewSet) + + router.register('referencedata', ReferenceDataViewSet) + router.register('metadata', MetadataViewSet) + register_subrecords() -register_plugin_apis() +initialize_router() diff --git a/opal/core/scaffold.py b/opal/core/scaffold.py index 815dc9d0b..4c72b4d05 100644 --- a/opal/core/scaffold.py +++ b/opal/core/scaffold.py @@ -2,9 +2,11 @@ Opal scaffolding and code generation """ import inspect +import errno import os import subprocess import sys + from django.core import management from django.utils.crypto import get_random_string from django.apps import apps @@ -57,6 +59,9 @@ def create_lookuplists(root_dir): def call(cmd, **kwargs): + """ + Call an external program in a subprocess + """ write("Calling: {}".format(' '.join(cmd))) try: subprocess.check_call(cmd, **kwargs) @@ -65,6 +70,26 @@ def call(cmd, **kwargs): sys.exit(1) +def call_if_exists(cmd, failure_message, **kwargs): + """ + Call an external program in a subprocess if it exists. + + Returns True. + + If it does not exist, write a failure message and return False + without raising an exception + """ + try: + call(cmd, **kwargs) + return True + except OSError as e: + if e.errno == errno.ENOENT: + write(failure_message) + return False + else: + raise + + def start_plugin(name, USERLAND): name = name @@ -103,7 +128,11 @@ def start_plugin(name, USERLAND): services = jsdir/'services' services.mkdir() # 5. Initialize git repo - call(('git', 'init'), cwd=root, stdout=subprocess.PIPE) + call_if_exists( + ('git', 'init'), + 'Unable to locate git; Skipping git repository initialization.', + cwd=root, stdout=subprocess.PIPE + ) write('Plugin complete at {0}'.format(reponame)) return @@ -252,7 +281,11 @@ def manage(command): manage('createopalsuperuser') # 10. Initialise git repo - call(('git', 'init'), cwd=project_dir, stdout=subprocess.PIPE) + call_if_exists( + ('git', 'init'), + 'Unable to locate git; Skipping git repository initialization.', + cwd=project_dir, stdout=subprocess.PIPE + ) # 11. Load referencedata shipped with Opal manage('load_lookup_lists') diff --git a/opal/managers.py b/opal/managers.py index 8baf4a081..05e428f62 100644 --- a/opal/managers.py +++ b/opal/managers.py @@ -10,6 +10,7 @@ from opal.core.subrecords import ( episode_subrecords, patient_subrecords ) +from opal.core.fields import ForeignKeyOrFreeText from functools import reduce @@ -32,6 +33,23 @@ def search(self, some_query): return qs +def prefetch(qs): + """ + Given a Queryset QS, examine the model for `ForeignKeyOrFreetext` + fields or `ManyToMany` fields and add `select_related` or + `prefetch_related` calls to the queryset as appropriate to reduce + the total number of database queries required to serialise the + contents of the queryset. + """ + for name, value in list(vars(qs.model).items()): + if isinstance(value, ForeignKeyOrFreeText): + qs = qs.select_related(value.fk_field_name) + + for related in qs.model._meta.many_to_many: + qs = qs.prefetch_related(related.attname) + return qs + + class EpisodeQueryset(models.QuerySet): def search(self, some_query): @@ -51,7 +69,9 @@ def serialised_episode_subrecords(self, episodes, user): for model in episode_subrecords(): name = model.get_api_name() - subrecords = model.objects.filter(episode__in=episodes) + subrecords = prefetch( + model.objects.filter(episode__in=episodes) + ) for related in model._meta.many_to_many: subrecords = subrecords.prefetch_related(related.attname) @@ -74,7 +94,9 @@ def serialised(self, user, episodes, episode_subs = self.serialised_episode_subrecords(episodes, user) for model in patient_subrecords(): name = model.get_api_name() - subrecords = model.objects.filter(patient__in=patient_ids) + subrecords = prefetch( + model.objects.filter(patient__in=patient_ids) + ) for sub in subrecords: patient_subs[sub.patient_id][name].append(sub.to_dict(user)) diff --git a/opal/static/css/opal.css b/opal/static/css/opal.css index 34afe77b7..2d759272d 100644 --- a/opal/static/css/opal.css +++ b/opal/static/css/opal.css @@ -1,3 +1,82 @@ +/* lato-300 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 300; + src: url('../fonts/lato-v14-latin-300.eot'); /* IE9 Compat Modes */ + src: local('Lato Light'), local('Lato-Light'), + url('../fonts/lato-v14-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/lato-v14-latin-300.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/lato-v14-latin-300.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato-v14-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/lato-v14-latin-300.svg#Lato') format('svg'); /* Legacy iOS */ +} +/* lato-regular - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 400; + src: url('../fonts/lato-v14-latin-regular.eot'); /* IE9 Compat Modes */ + src: local('Lato Regular'), local('Lato-Regular'), + url('../fonts/lato-v14-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/lato-v14-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/lato-v14-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato-v14-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/lato-v14-latin-regular.svg#Lato') format('svg'); /* Legacy iOS */ +} +/* lato-700 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 700; + src: url('../fonts/lato-v14-latin-700.eot'); /* IE9 Compat Modes */ + src: local('Lato Bold'), local('Lato-Bold'), + url('../fonts/lato-v14-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/lato-v14-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/lato-v14-latin-700.woff') format('woff'), /* Modern Browsers */ + url('../fonts/lato-v14-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/lato-v14-latin-700.svg#Lato') format('svg'); /* Legacy iOS */ +} + + +@media print{ + @page { + size: landscape; + } + .patient-list-container { + overflow: visible; + overflow-y: visible; + } + + li { + padding-top: 0px; + padding-bottom: 0px; + line-height: 12px; + } + + .screen-only { + display: none; + } + + /* + * it needs important because bootstrap + */ + .always-on-print-block{ + display: block !important; + } + + table { + font-size: 12px + } +} + +@media screen{ + .print-only { + display: none; + } +} + + /* Bootstrap resets */ html, .outer-container{ height: 100%; diff --git a/opal/static/css/print.css b/opal/static/css/print.css deleted file mode 100644 index fe081f6ab..000000000 --- a/opal/static/css/print.css +++ /dev/null @@ -1,30 +0,0 @@ -@media print{ - @page { - size: landscape; - } - .patient-list-container { - overflow: visible; - overflow-y: visible; - } -} - -li { - padding-top: 0px; - padding-bottom: 0px; - line-height: 12px; -} - -.screen-only { - display: none; -} - -/* -* it needs important because bootstrap -*/ -.always-on-print-block{ - display: block !important; -} - -table { - font-size: 12px -} diff --git a/opal/static/css/screen.css b/opal/static/css/screen.css deleted file mode 100644 index def51c29c..000000000 --- a/opal/static/css/screen.css +++ /dev/null @@ -1,3 +0,0 @@ -.print-only { - display: none; -} diff --git a/opal/static/fonts/lato-v14-latin-300.eot b/opal/static/fonts/lato-v14-latin-300.eot new file mode 100644 index 000000000..e8b79c348 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-300.eot differ diff --git a/opal/static/fonts/lato-v14-latin-300.svg b/opal/static/fonts/lato-v14-latin-300.svg new file mode 100644 index 000000000..11b626f87 --- /dev/null +++ b/opal/static/fonts/lato-v14-latin-300.svg @@ -0,0 +1,435 @@ + + + diff --git a/opal/static/fonts/lato-v14-latin-300.ttf b/opal/static/fonts/lato-v14-latin-300.ttf new file mode 100644 index 000000000..f326d26c2 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-300.ttf differ diff --git a/opal/static/fonts/lato-v14-latin-300.woff b/opal/static/fonts/lato-v14-latin-300.woff new file mode 100644 index 000000000..ab45ab76b Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-300.woff differ diff --git a/opal/static/fonts/lato-v14-latin-300.woff2 b/opal/static/fonts/lato-v14-latin-300.woff2 new file mode 100644 index 000000000..136337fdc Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-300.woff2 differ diff --git a/opal/static/fonts/lato-v14-latin-700.eot b/opal/static/fonts/lato-v14-latin-700.eot new file mode 100644 index 000000000..9d8bfb614 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-700.eot differ diff --git a/opal/static/fonts/lato-v14-latin-700.svg b/opal/static/fonts/lato-v14-latin-700.svg new file mode 100644 index 000000000..077653d20 --- /dev/null +++ b/opal/static/fonts/lato-v14-latin-700.svg @@ -0,0 +1,438 @@ + + + diff --git a/opal/static/fonts/lato-v14-latin-700.ttf b/opal/static/fonts/lato-v14-latin-700.ttf new file mode 100644 index 000000000..eeea013c5 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-700.ttf differ diff --git a/opal/static/fonts/lato-v14-latin-700.woff b/opal/static/fonts/lato-v14-latin-700.woff new file mode 100644 index 000000000..1d9d75bc6 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-700.woff differ diff --git a/opal/static/fonts/lato-v14-latin-700.woff2 b/opal/static/fonts/lato-v14-latin-700.woff2 new file mode 100644 index 000000000..d88f1af8c Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-700.woff2 differ diff --git a/opal/static/fonts/lato-v14-latin-regular.eot b/opal/static/fonts/lato-v14-latin-regular.eot new file mode 100644 index 000000000..2400e1284 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-regular.eot differ diff --git a/opal/static/fonts/lato-v14-latin-regular.svg b/opal/static/fonts/lato-v14-latin-regular.svg new file mode 100644 index 000000000..55b43fb86 --- /dev/null +++ b/opal/static/fonts/lato-v14-latin-regular.svg @@ -0,0 +1,435 @@ + + + diff --git a/opal/static/fonts/lato-v14-latin-regular.ttf b/opal/static/fonts/lato-v14-latin-regular.ttf new file mode 100644 index 000000000..fa245a8a8 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-regular.ttf differ diff --git a/opal/static/fonts/lato-v14-latin-regular.woff b/opal/static/fonts/lato-v14-latin-regular.woff new file mode 100644 index 000000000..97ab144d9 Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-regular.woff differ diff --git a/opal/static/fonts/lato-v14-latin-regular.woff2 b/opal/static/fonts/lato-v14-latin-regular.woff2 new file mode 100644 index 000000000..b14c76cab Binary files /dev/null and b/opal/static/fonts/lato-v14-latin-regular.woff2 differ diff --git a/opal/templates/base.html b/opal/templates/base.html index febfad8d5..9bde9a908 100644 --- a/opal/templates/base.html +++ b/opal/templates/base.html @@ -21,8 +21,8 @@ LOG_OUT_DURATION: {{OPAL_LOG_OUT_DURATION}} } var version = '{{VERSION_NUMBER}}'; - - + + {% compress js %} {% core_javascripts 'opal.upstream.deps' %} {% block opal_js %} @@ -36,21 +36,6 @@ {% if OPAL_FLOW_SERVICE %}'{{ OPAL_FLOW_SERVICE }}'{% else %}null{% endif %} ); - - {% core_javascripts 'opal.utils' %} {% core_javascripts 'opal.services' %} @@ -68,26 +53,20 @@ {% endblock opal_js %} {% endcompress %} - - - + - + - - - - - {% compress css %} - - {% plugin_stylesheets %} - {% application_stylesheets %} - {% endcompress %} + {% compress css %} + + {% plugin_stylesheets %} + {% application_stylesheets %} + {% endcompress %} @@ -119,6 +98,22 @@
}); + + +