diff --git a/elcid/api.py b/elcid/api.py index 1919aff17..7d0042034 100644 --- a/elcid/api.py +++ b/elcid/api.py @@ -7,6 +7,7 @@ from operator import itemgetter from django.conf import settings +from django.utils.functional import cached_property from django.utils.text import slugify from django.http import HttpResponseBadRequest from rest_framework import viewsets, status @@ -70,6 +71,30 @@ class LabTestResultsView(LoginRequiredViewset): """ base_name = 'lab_test_results_view' + @cached_property + def starred_results_dict(self): + starred_obs = lab_test_models.StarredObservation.objects.filter( + patient_id=self.kwargs["pk"] + ) + result = {} + for starred_ob in starred_obs: + key = ( + starred_ob.test_name, + starred_ob.lab_number, + starred_ob.observation_name, + ) + result[key] = starred_ob.id + return result + + def get_starred_id(self, test, observation): + key = ( + test.test_name, + test.lab_number, + observation.observation_name, + ) + return self.starred_results_dict.get(key) + + def get_non_comments_for_patient(self, patient): """ Returns all non comments for a patient, ensuring they are @@ -203,8 +228,9 @@ def serialise_long_form_instance(self, instance): serialised_observations.append( { 'name' : o.observation_name.rstrip('.'), - 'value': o.observation_value, - 'units': o.units + 'value': o.observation_value.replace("~", "\n"), + 'units': o.units, + 'star': self.get_starred_id(instance, o), } ) @@ -300,6 +326,58 @@ class InfectionServiceTestSummaryApi(LoginRequiredViewset): NUM_RESULTS = 5 + @cached_property + def starred_results_dict(self): + """ + A cached property of test_name, lab_number, observation_name to StarredObservation.id + """ + starred_obs = lab_test_models.StarredObservation.objects.filter( + patient_id=self.kwargs["pk"] + ) + result = {} + for starred_ob in starred_obs: + key = ( + starred_ob.test_name, + starred_ob.lab_number, + starred_ob.observation_name, + ) + result[key] = starred_ob.id + return result + + def get_starred(self, patient, ticker): + """ + If a lab test has been starred and is not already + in the ticker add it + """ + result = [] + already_starred = {i["star"] for i in ticker} + others_starred = [ + key for key, starred_id in self.starred_results_dict.items() + if starred_id not in already_starred + ] + if not others_starred: + return [] + observations = lab_test_models.Observation.objects.filter( + test__patient=patient, + test__test_name__in=[i[0] for i in others_starred], + test__lab_number__in=[i[1] for i in others_starred], + observation_name__in=[i[2] for i in others_starred], + ).select_related('test') + for observation in observations: + test = observation.test + starred_id = self.starred_results_dict.get(( + test.test_name, test.lab_number, observation.observation_name + )) + if starred_id: + timestamp = observation.observation_datetime + result.append({ + 'date_str': observation.observation_datetime.strftime('%d/%m/%Y %H:%M'), + 'timestamp': timestamp, + 'name': observation.observation_name, + 'value': "\n".join(observation.observation_value.strip().split("~")), + 'star': starred_id, + }) + return result def _get_antifungal_ticker_dict(self, test): """ @@ -311,26 +389,30 @@ def _get_antifungal_ticker_dict(self, test): test_name = test.test_name result_string = '' + star_id = None for observation in observations: - if observation.observation_name in self.ANTIFUNGAL_TESTS[test_name]: result_string += ' {} {}'.format( self.ANTIFUNGAL_SHORT_NAMES[observation.observation_name], observation.observation_value.split('~')[0] ) + if not star_id: + star_id = self.starred_results_dict.get( + (test_name, test.lab_number, observation.observation_name,) + ) display_name = '{} {}'.format( self.ANTIFUNGAL_SHORT_NAMES[test_name], test.site.replace('&', ' ').split(' ')[0] ) - return { 'date_str' : timestamp.strftime('%d/%m/%Y %H:%M'), 'timestamp': timestamp, 'name' : display_name, - 'value' : result_string.strip() + 'value' : result_string.strip(), + 'star' : star_id, } def get_antifungal_observations(self, patient): @@ -369,16 +451,30 @@ def get_antifungal_observations(self, patient): return ticker + def get_covid_ticker(self, patient): + """ + Get the covid ticker, add the property + 'star' which points is the id of the StarredObservation + if its been starred, otherwise its None + """ + ticker = covid_lab.get_covid_result_ticker(patient) + for t in ticker: + star_id = self.starred_results_dict.get( + (t["test_name"], t["lab_number"], t["name"],) + ) + t["star"] = star_id + t["observation_name"] = t["name"] + return ticker def get_ticker_observations(self, patient): """ Some results are displayed as a ticker in chronological order. """ - ticker = covid_lab.get_covid_result_ticker(patient) + ticker = self.get_covid_ticker(patient) ticker += self.get_antifungal_observations(patient) + ticker += self.get_starred(patient, ticker) ticker = list(reversed(sorted(ticker, key=lambda i: i['timestamp']))) - return ticker def get_recent_dates_to_observations(self, qs): diff --git a/elcid/assets/css/elcid.css b/elcid/assets/css/elcid.css index a060e6a5b..0a840085f 100644 --- a/elcid/assets/css/elcid.css +++ b/elcid/assets/css/elcid.css @@ -55,12 +55,11 @@ body { border-color: #0091C9; } -.radio-vertical.radio input[type="radio"] { +.radio-vertical.radio input[type=radio] { opacity: 1; z-index: 0; margin-top: 2px; } - .radio-vertical.radio label::before { content: none; } @@ -123,8 +122,7 @@ h5 { /* ngProgress overrides */ .ngProgressLiteBar { background-color: #D36C08; - -webkit-box-shadow: 0 0 10px 0; - box-shadow: 0 0 10px 0; + box-shadow: 0 0 10px 0; /* Inherits the font color */ } @@ -137,7 +135,6 @@ a.list-group-item:hover, a.list-group-item:focus { background-color: #4d5051; color: #FFF; } - a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.active:focus { background-color: #343637; } @@ -155,7 +152,6 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti display: inline-block; padding-top: 4px; } - .patient-row .label-danger { margin-top: 5px; display: inline-block; @@ -175,6 +171,8 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti padding-left: 8px; font-size: 1.1em; } + +/*# sourceMappingURL=elcid.css.map */ .label-warning { background-color: #A03110; } @@ -197,15 +195,9 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti * the same height whether the ext is on 2 lines or not * and to vertically/horizontally centre the text */ - display: -webkit-box; - display: -ms-flexbox; display: flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; + justify-content: center; + align-items: center; width: 100%; min-height: 64px; } @@ -217,25 +209,20 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti .patient-timeline-discussion { min-height: 120px; } - .patient-timeline-discussion ul { padding-left: 40px; } - .patient-timeline-discussion ul li { list-style-type: disc; } - .patient-timeline-discussion i { color: #333; } - .patient-timeline-discussion ol li { list-style-type: decimal; } -.no-touchevents -.patient-timeline-discussion i.edit { +.no-touchevents .patient-timeline-discussion i.edit { display: none; } @@ -258,35 +245,28 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti margin-left: 10px; margin-right: 10px; } - .lab-tests .lab-test-row { padding-top: 10px; margin-left: 10px; margin-right: 10px; padding-bottom: 10px; } - .lab-tests .lab-test-row.selectable:hover { background-color: #f5f5f5; } - .lab-tests .observation-graph-pane { padding-top: 10px; padding-bottom: 10px; } - .lab-tests .c3-region.too-high { fill: rgba(224, 47, 3, 0.5); } - .lab-tests .c3-region.too-low { fill: rgba(224, 47, 3, 0.5); } - .lab-tests .too-high { color: #E02F03; } - .lab-tests .too-low { color: #E02F03; } @@ -308,7 +288,6 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti .trends-panel { min-height: 395px; } - .trends-panel .no-results { padding-top: 120px; } @@ -329,7 +308,6 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti border-color: #006c96; border-width: 3px; } - .central-info-alert.alert.alert-info .btn { padding-left: 50px; padding-right: 50px; @@ -338,11 +316,9 @@ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.acti .panel tr.table-edit td { vertical-align: middle; } - .panel tr.table-edit i.edit { visibility: hidden; } - .panel tr.table-edit:hover i.edit { visibility: visible; } @@ -355,7 +331,6 @@ i.blood-culture-edit { div.blood-culture-edit:hover { background-color: #f4f8fc; } - div.blood-culture-edit:hover i { display: inline-block; font-size: 28px; @@ -383,11 +358,9 @@ div.add-isolate:hover { font-size: 18px; margin-left: -20px; } - .form-hint .success { color: #0091C9; } - .form-hint .failure { color: #f76c51; } @@ -438,18 +411,15 @@ div.add-isolate:hover { min-height: calc(100vh - 50px); padding-left: 1%; } - .left-menu h3 { padding-top: 18px; } - .left-menu a { width: 100%; display: block; padding: 2px; color: #FFF; } - .left-menu a:hover { background: #343637; text-decoration: none; @@ -467,12 +437,10 @@ div.add-isolate:hover { .ward-name-list .row { border-bottom: 1px solid #343637; } - .ward-name-list a { padding: 12px 6px 12px 6px; display: block; } - .ward-name-list a:hover { background: #343637; } @@ -516,23 +484,19 @@ div.add-isolate:hover { .tb-clinic-list { background-color: #FFF; } - .tb-clinic-list .date-header { padding-left: 10px; padding-bottom: 3px; border-bottom: 3px solid #D36C08; margin-bottom: 0; } - .tb-clinic-list .date-header .date-header-link { display: none; color: #999; } - .tb-clinic-list .date-header .date-header-link:hover { text-decoration: none; } - .tb-clinic-list .date-header:hover .date-header-link { display: inline; } @@ -566,4 +530,24 @@ div.add-isolate:hover { .nowrap { white-space: nowrap; } -/*# sourceMappingURL=elcid.css.map */ \ No newline at end of file + +.long-form-results-row .fa-star { + color: #9cc96b; +} +.long-form-results-row .star-obs-container { + display: inline-block; + width: 20px; +} +.long-form-results-row .star-obs { + visibility: hidden; + display: none; +} +.long-form-results-row:hover, .long-form-results-row.starred { + background-color: #f3f2f2; +} +.long-form-results-row:hover .star-obs, .long-form-results-row.starred .star-obs { + visibility: visible; + display: inline-block; +} + +/*# sourceMappingURL=elcid.css.map */ diff --git a/elcid/assets/css/elcid.css.map b/elcid/assets/css/elcid.css.map index d7375611a..a94d50f88 100644 --- a/elcid/assets/css/elcid.css.map +++ b/elcid/assets/css/elcid.css.map @@ -1,9 +1 @@ -{ - "version": 3, - "mappings": "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", - "sources": [ - "elcid.scss" - ], - "names": [], - "file": "elcid.css" -} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["elcid.scss"],"names":[],"mappings":"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","file":"elcid.css"} \ No newline at end of file diff --git a/elcid/assets/css/elcid.scss b/elcid/assets/css/elcid.scss index 778dfc8ac..6872ed906 100644 --- a/elcid/assets/css/elcid.scss +++ b/elcid/assets/css/elcid.scss @@ -602,3 +602,25 @@ div.add-isolate:hover{ .nowrap{ white-space: nowrap; } + +.long-form-results-row{ + .fa-star{ + color: $green; + } + .star-obs-container{ + display: inline-block; + width: 20px; + } + .star-obs{ + visibility: hidden; + display: none; + } + &:hover, &.starred{ + background-color: lighten($obsidian-light, 16%); + + .star-obs{ + visibility: visible; + display: inline-block; + } + } +} diff --git a/elcid/assets/js/elcid/controllers/result_view.js b/elcid/assets/js/elcid/controllers/result_view.js index 0d3e929e0..187c24258 100644 --- a/elcid/assets/js/elcid/controllers/result_view.js +++ b/elcid/assets/js/elcid/controllers/result_view.js @@ -1,11 +1,12 @@ angular.module('opal.controllers').controller('ResultView', function( - $scope, LabTestResults, ngProgressLite + $scope, LabTestResults, ngProgressLite, StarObservation ){ "use strict"; var vm = this; this.labTests = []; this.showAll = {}; + this.starObservation = StarObservation; this.parseFloat = parseFloat; this.Math = window.Math; @@ -55,5 +56,15 @@ angular.module('opal.controllers').controller('ResultView', function( vm.showAll[what] = true; } - vm.getLabTests($scope.patient); + /* + * If the view is the result page and the tests have not + * loaded then load the results. + * + * After we've loaded them, cache them. + */ + $scope.$watch('view', function(){ + if(!_.size(vm.lab_tests) && $scope.view === "test_results"){ + vm.getLabTests($scope.patient); + } + }) }); diff --git a/elcid/assets/js/elcid/directives.js b/elcid/assets/js/elcid/directives.js index a85444389..3d8454b47 100644 --- a/elcid/assets/js/elcid/directives.js +++ b/elcid/assets/js/elcid/directives.js @@ -105,19 +105,25 @@ directives.directive("populateLabTests", function(InitialPatientTestLoadStatus, episode ); - // make sure we are using the correct - // js object scope(ie this) patientLoadStatus.load(); scope.patientLoadStatus = patientLoadStatus; - - if(!scope.patientLoadStatus.isAbsent()){ - scope.patientLoadStatus.promise.then(function(){ - // success - LabTestSummaryLoader.load(patientId).then(function(result){ - scope.data = result; - }); - }); + var loadData = function(){ + if(!scope.patientLoadStatus.isAbsent()){ + scope.patientLoadStatus.promise.then(function(){ + // success + LabTestSummaryLoader.load(patientId).then(function(result){ + scope.data = result; + }); + }); + } } + scope.$watch('episode.category_name', function(){ + loadData(); + }); + scope.$watch('view', function(){ + loadData(); + }); + loadData(); } }; }); diff --git a/elcid/assets/js/elcid/services/lab_test_summary_loader.js b/elcid/assets/js/elcid/services/lab_test_summary_loader.js index 59ca15b54..57e801cf0 100644 --- a/elcid/assets/js/elcid/services/lab_test_summary_loader.js +++ b/elcid/assets/js/elcid/services/lab_test_summary_loader.js @@ -7,7 +7,7 @@ angular.module('opal.services').factory('LabTestSummaryLoader', function($q, $ht var load = function(patientId){ var deferred = $q.defer(); var patientUrl = url + '' + patientId + '/'; - $http({ cache: true, url: patientUrl, method: 'GET'}).then(function(response) { + $http.get(patientUrl).then(function(response) { deferred.resolve(response.data); }, function() { console.error("unable to load in the infection_service_summary_api"); diff --git a/elcid/templates/detail/infection_service.html b/elcid/templates/detail/infection_service.html index 7df310287..bd2671a6a 100644 --- a/elcid/templates/detail/infection_service.html +++ b/elcid/templates/detail/infection_service.html @@ -1,42 +1,44 @@ {% load panels %} {% load forms %} -
- {% record_panel models.Location %} - {% record_panel models.Allergies %} - {% record_panel models.ICUAdmission %} - {% teams_panel %} - {% if permissions.view_lab_test_trends %} -
-

- {% icon 'fa fa-crosshairs' %} Results -

-
- {% include "partials/lab_test_sparklines.html" with target="patient" %} +
+
+ {% record_panel models.Location %} + {% record_panel models.Allergies %} + {% record_panel models.ICUAdmission %} + {% teams_panel %} + {% if permissions.view_lab_test_trends %} +
+

+ {% icon 'fa fa-crosshairs' %} Results +

+
+ {% include "partials/lab_test_sparklines.html" with target="patient" %} +
-
- {% endif %} + {% endif %} - {% record_panel models.PrimaryDiagnosis %} - {% record_panel models.PastMedicalHistory %} - {% record_panel models.Diagnosis %} - {% record_panel models.RiskFactor %} - {% record_panel models.Travel %} - {% record_panel models.Procedure %} -
- {% record_panel models.MicrobiologyInput %} -
+ {% record_panel models.PrimaryDiagnosis %} + {% record_panel models.PastMedicalHistory %} + {% record_panel models.Diagnosis %} + {% record_panel models.RiskFactor %} + {% record_panel models.Travel %} + {% record_panel models.Procedure %} +
+ {% record_panel models.MicrobiologyInput %} +
- {% record_panel models.Line %} - {% include "partials/antimicrobial_panel.html" %} - {% include 'panels/other_medication.html' %} - {% include "partials/imaging_panel.html" %} - {% record_panel models.OtherInvestigation title="Other Investigations"%} - {% include "partials/edrm_panel.html" %} - {% include "panels/blood_culture_set.html" %} - {% record_panel models.FinalDiagnosis %} -
-
- {% record_panel models.InfectionServiceNote %} - {% include "inline_forms/clinical_timeline.html" %} + {% record_panel models.Line %} + {% include "partials/antimicrobial_panel.html" %} + {% include 'panels/other_medication.html' %} + {% include "partials/imaging_panel.html" %} + {% record_panel models.OtherInvestigation title="Other Investigations"%} + {% include "partials/edrm_panel.html" %} + {% include "panels/blood_culture_set.html" %} + {% record_panel models.FinalDiagnosis %} +
+
+ {% record_panel models.InfectionServiceNote %} + {% include "inline_forms/clinical_timeline.html" %} +
diff --git a/elcid/templates/detail/result.html b/elcid/templates/detail/result.html index ec6245285..44b56d858 100644 --- a/elcid/templates/detail/result.html +++ b/elcid/templates/detail/result.html @@ -1,113 +1,121 @@ {% load panels %} {% load forms %} -
+
-
+
+
-
-
- -
-
-

- Lab Tests -

-
-
- -
+
+
+

+ Lab Tests +

+
+
+
- - -
    -
  • -
    - [[ test_name ]] -
    - -
    - -
    - -
    - [[ instance.lab_number ]] -
    - [[ instance.date | displayDateTime]] -
    - [[ instance.site ]] -
    +
    + + + -
    -
+
+ +
+ + + + + + + + + + + + + + + + + + +
+ + [[ date | displayDate ]] + + +
[[ observation_name ]] + [[ resultView.lab_tests[test_name].instances.observation_ranges[observation_name] ]] + [[ resultView.lab_tests[test_name].instances.observation_units[observation_name] ]] + + +

+ [[ par_obs ]] +

+
+
+
+
-
+ + +
+
diff --git a/elcid/templates/partials/lab_test_sparklines.html b/elcid/templates/partials/lab_test_sparklines.html index f4483a13f..8999e77bd 100644 --- a/elcid/templates/partials/lab_test_sparklines.html +++ b/elcid/templates/partials/lab_test_sparklines.html @@ -16,11 +16,22 @@

No results found

-
-
[[ test.date_str ]]
-
[[ test.name ]]
-
[[ test.value ]]
-
+ +
+
[[ item.date_str ]]
+ +
[[ item.name ]]
+
[[ item.value ]]
+
+ +
[[ item.name ]]
+
+
+
+
+
+
+
diff --git a/plugins/covid/lab.py b/plugins/covid/lab.py index e7625f0f9..61b3e55f9 100644 --- a/plugins/covid/lab.py +++ b/plugins/covid/lab.py @@ -280,10 +280,12 @@ def get_covid_result_ticker(patient): data.append( { - 'date_str' : timestamp.strftime('%d/%m/%Y %H:%M'), - 'timestamp': timestamp, - 'name' : _get_covid_test(test).OBSERVATION_NAME, - 'value' : result_string + 'date_str' : timestamp.strftime('%d/%m/%Y %H:%M'), + 'timestamp' : timestamp, + 'name' : _get_covid_test(test).OBSERVATION_NAME, + 'value' : result_string, + 'test_name' : test.test_name, + 'lab_number': test.lab_number } ) diff --git a/plugins/labtests/api.py b/plugins/labtests/api.py new file mode 100644 index 000000000..25e122eee --- /dev/null +++ b/plugins/labtests/api.py @@ -0,0 +1,23 @@ +from rest_framework import status +from opal.core.api import SubrecordViewSet +from opal.core.views import json_response +from plugins.labtests.models import StarredObservation + + +class StarObservation(SubrecordViewSet): + model = StarredObservation + base_name = "star_observation" + + def create(self, request): + model = self.model() + model.update_from_dict(request.data, request.user) + return json_response( + {"id": model.id}, + status_code=status.HTTP_201_CREATED + ) + + def delete(self, _, pk): + self.model.filter(id=pk).delete() + return json_response( + 'deleted', status_code=status.HTTP_202_ACCEPTED + ) diff --git a/plugins/labtests/migrations/0009_starredobservation.py b/plugins/labtests/migrations/0009_starredobservation.py new file mode 100644 index 000000000..567f6c6fa --- /dev/null +++ b/plugins/labtests/migrations/0009_starredobservation.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0.13 on 2021-09-28 08:22 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('opal', '0040_auto_20201007_1346'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('labtests', '0008_observationhistory'), + ] + + operations = [ + migrations.CreateModel( + name='StarredObservation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('test_name', models.CharField(blank=True, max_length=256, null=True)), + ('lab_number', models.CharField(blank=True, max_length=256, null=True)), + ('observation_name', models.CharField(blank=True, max_length=256, null=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('patient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='opal.Patient')), + ], + ), + ] diff --git a/plugins/labtests/models.py b/plugins/labtests/models.py index f331fe658..8bd956607 100644 --- a/plugins/labtests/models.py +++ b/plugins/labtests/models.py @@ -4,6 +4,7 @@ from django.db import models from django.conf import settings from django.utils import timezone +from django.contrib.auth.models import User from opal.core import serialization from opal import models as omodels @@ -274,3 +275,32 @@ class ObservationHistory(AbstractObserveration): on_delete=models.CASCADE, related_name="observation_history" ) + + +class StarredObservation( + models.Model +): + patient = models.ForeignKey( + omodels.Patient, + on_delete=models.CASCADE, + ) + test_name = models.CharField(max_length=256, blank=True, null=True) + lab_number = models.CharField(max_length=256, blank=True, null=True) + observation_name = models.CharField(max_length=256, blank=True, null=True) + created_by = models.ForeignKey( + User, + blank=True, + null=True, + on_delete=models.SET_NULL, + verbose_name="Created By" + ) + created = models.DateTimeField(auto_now_add=True) + + def update_from_dict(self, data, user, **kwargs): + self.created_by = user + fields = [ + "patient_id", "test_name", "lab_number", "observation_name" + ] + for field in fields: + setattr(self, field, data[field]) + self.save() diff --git a/plugins/labtests/plugin.py b/plugins/labtests/plugin.py index 0114c53c6..bc749ee9a 100644 --- a/plugins/labtests/plugin.py +++ b/plugins/labtests/plugin.py @@ -4,6 +4,8 @@ from opal.core import plugins from plugins.labtests.urls import urlpatterns +from plugins.labtests import api + class LabtestsPlugin(plugins.OpalPlugin): """ @@ -12,13 +14,18 @@ class LabtestsPlugin(plugins.OpalPlugin): urls = urlpatterns javascripts = { # Add your javascripts here! - 'opal.labtests': [ + 'opal.services': [ + "labtests/js/services/star_observation.js" # 'js/labtests/app.js', # 'js/labtests/controllers/larry.js', # 'js/labtests/services/larry.js', ] } + apis = [ + (api.StarObservation.base_name, api.StarObservation,), + ] + def list_schemas(self): """ Return any patient list schemas that our plugin may define. @@ -30,4 +37,4 @@ def roles(self, user): Given a (Django) USER object, return any extra roles defined by our plugin. """ - return {} \ No newline at end of file + return {} diff --git a/plugins/labtests/static/labtests/js/services/star_observation.js b/plugins/labtests/static/labtests/js/services/star_observation.js new file mode 100644 index 000000000..8eaac4062 --- /dev/null +++ b/plugins/labtests/static/labtests/js/services/star_observation.js @@ -0,0 +1,23 @@ +angular.module('opal.services').service('StarObservation', function($http){ + "use strict"; + var url = '/api/v0.1/star_observation/' + return { + starObservation: function(patient_id, test_name, lab_number, observation_name, observation){ + var toSave = { + patient_id: patient_id, + test_name: test_name, + lab_number: lab_number, + observation_name: observation_name, + } + $http.post(url, toSave).then(function(createdStar) { + observation.star = createdStar.data.id; + }); + }, + unstarObservation: function(observation){ + var deleteUrl = url + observation.star + '/' + $http.delete(deleteUrl).then(function(){ + observation.star = undefined; + }) + } + } +}); diff --git a/plugins/labtests/tests/test_api.py b/plugins/labtests/tests/test_api.py new file mode 100644 index 000000000..b6550ddd9 --- /dev/null +++ b/plugins/labtests/tests/test_api.py @@ -0,0 +1,58 @@ +from opal.core.test import OpalTestCase +from rest_framework.reverse import reverse +from plugins.labtests import models as lab_models + + +class StarObservationTestCase(OpalTestCase): + def setUp(self): + self.request = self.rf.get("/") + self.list_url = reverse( + "star_observation-list", + request=self.request + ) + # initialise the property + self.user + self.assertTrue( + self.client.login( + username=self.USERNAME, + password=self.PASSWORD + ) + ) + self.patient, _ = self.new_patient_and_episode_please() + return super().setUp() + + def test_create(self): + self.client.post(self.list_url, { + "patient_id": self.patient.id, + "test_name": "Blood culture", + "lab_number": "111", + "observation_name": "Culture result", + }) + self.assertTrue( + lab_models.StarredObservation.objects.filter( + patient_id=self.patient.id, + test_name="Blood culture", + lab_number="111", + observation_name="Culture result", + created_by=self.user + ).exists() + ) + self.assertEqual(lab_models.StarredObservation.objects.count(), 1) + + def test_delete(self): + starred_obs = lab_models.StarredObservation.objects.create( + patient_id=self.patient.id, + test_name="Blood culture", + lab_number="111", + observation_name="Culture result", + created_by=self.user + ) + detail_url = reverse( + "star_observation-detail", + kwargs={'pk': starred_obs.id}, + request=self.request, + ) + self.client.delete(detail_url) + self.assertFalse( + lab_models.StarredObservation.objects.all().exists() + )