Skip to content

Commit

Permalink
Merge branch 'v0.11.0' into 1502-test-options
Browse files Browse the repository at this point in the history
  • Loading branch information
fredkingham committed Apr 18, 2018
2 parents 510ac3b + 06c818e commit 83cc6f7
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 37 deletions.
4 changes: 3 additions & 1 deletion .appveyor.yml
Expand Up @@ -5,7 +5,9 @@ environment:

install:
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- "pip install --disable-pip-version-check --user --upgrade pip"
# This is only pinning to a version rather than --upgrade pip until
# such times as the latest Pip doesn't error out with `cannot import name main`
- "pip install --disable-pip-version-check --user pip==9.0.3"
- "%CMD_IN_ENV% python setup.py develop"
- "pip install -r test-requirements.txt"

Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Expand Up @@ -14,6 +14,9 @@ Episode manager, which no longer accepts a `episode_history` kwarg.
A refactor in the way that the core APIs are registered by Opal means that
importing `opal.core.api` in a plugin API no longer results in circular imports.

Fixes a bug whereby episodes were serialising differently depending on whether
the code path went via `.to_dict()` or `.objects.serialised()`.


### 0.10.1 (Minor Release)

Expand All @@ -30,11 +33,17 @@ Fonts are now served from Opal's static assets rather than from the Google CDN.

Print/screen differences are now in opal.css with media tags.

#### google analytics is now deferred
#### 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
Expand Down
22 changes: 19 additions & 3 deletions opal/core/pathway/static/js/pathway/directives.js
Expand Up @@ -200,7 +200,9 @@ directives.directive("pathwayLink", function($parse){
};
});

directives.directive("openPathway", function($parse, $rootScope, Referencedata, Metadata, $modal, episodeLoader){
directives.directive("openPathway", function($parse, $rootScope, $modal, $q,
Referencedata, Metadata,
episodeLoader){
/*
* the open modal pathway directive will open a modal pathway for you
* you can if you use the attribute pathway-callback="{{ some_function }}"
Expand Down Expand Up @@ -230,7 +232,10 @@ directives.directive("openPathway", function($parse, $rootScope, Referencedata,
pathwayCallback = function(){};
}
var template = "/pathway/templates/" + pathwaySlug + ".html?is_modal=True";
return $modal.open({

var deferred = $q.defer();

$modal.open({
controller : 'ModalPathwayCtrl',
templateUrl: template,
size : 'lg',
Expand All @@ -254,7 +259,18 @@ directives.directive("openPathway", function($parse, $rootScope, Referencedata,
metadata: function(){ return Metadata.load(); },
referencedata: function(){ return Referencedata.load(); },
}
});
}).result.then(
function(what){
$rootScope.state = 'normal';
deferred.resolve(what);
},
function(what_now){
$rootScope.state = 'normal';
deferred.resolve(what_now);
}
)

return deferred.promise;
});
}
};
Expand Down
42 changes: 37 additions & 5 deletions opal/core/pathway/static/js/test/directives.test.js
Expand Up @@ -5,6 +5,7 @@ describe('pathway directives', function(){
var referencedata, metadata, pathwayLoader;
beforeEach(module('opal.controllers'));
beforeEach(module('opal.directives', function($provide){

mockModal = {open: function(){}};
spyOn(mockModal, "open").and.returnValue({
result: {
Expand Down Expand Up @@ -41,11 +42,11 @@ describe('pathway directives', function(){
}));

beforeEach(inject(function($injector){
var $rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
$httpBackend = $injector.get('$httpBackend');
$compile = $injector.get('$compile');
$parse = $injector.get('$parse');
$rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
$httpBackend = $injector.get('$httpBackend');
$compile = $injector.get('$compile');
$parse = $injector.get('$parse');
pathwayLoader = $injector.get('pathwayLoader');
}));

Expand Down Expand Up @@ -141,6 +142,37 @@ describe('pathway directives', function(){
var resolves = mockModal.open.calls.mostRecent().args[0].resolve;
expect(resolves.episode()).toBe('trees');
});

it('should set up the state variable', function() {
var markup = '<a href="#" open-pathway="someFakePathway"></a>';
mockModal.open.and.returnValue({result: {then:function(cb, eb){
// Conspicuously not calling cb();
}}})
element = $compile(markup)(scope);
$(element).click();
expect($rootScope.state).toEqual('modal');
});

it('should reset the state variable', function() {
var markup = '<a href="#" open-pathway="someFakePathway"></a>';
element = $compile(markup)(scope);
scope.$digest();
$(element).click();
scope.$digest();
expect($rootScope.state).toEqual('normal');
});

it('should reset the state variable in case of rejection', function() {
var markup = '<a href="#" open-pathway="someFakePathway"></a>';
mockModal.open.and.returnValue({result: {then:function(cb, eb){
eb() // Conspicuously calling the rejection case
}}})
element = $compile(markup)(scope);
$(element).click();
scope.$digest();
expect($rootScope.state).toEqual('normal');
});

});


Expand Down
37 changes: 35 additions & 2 deletions opal/core/scaffold.py
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down
15 changes: 13 additions & 2 deletions opal/managers.py
Expand Up @@ -65,10 +65,16 @@ def serialised_episode_subrecords(self, episodes, user):
in a nested hashtable where the outer key is the episode id,
the inner key the subrecord API name.
"""
episode_subs = defaultdict(lambda: defaultdict(list))
episode_subs = defaultdict(dict)
episode_ids = [e.id for e in episodes]

for model in episode_subrecords():
name = model.get_api_name()

# Ensure there is an empty list for serialisation
for episode_id in episode_ids:
episode_subs[episode_id][name] = []

subrecords = prefetch(
model.objects.filter(episode__in=episodes)
)
Expand All @@ -78,6 +84,7 @@ def serialised_episode_subrecords(self, episodes, user):

for sub in subrecords:
episode_subs[sub.episode_id][name].append(sub.to_dict(user))

return episode_subs

def serialised(self, user, episodes, historic_tags=False):
Expand All @@ -87,11 +94,15 @@ def serialised(self, user, episodes, historic_tags=False):
If HISTORIC_TAGS is Truthy, return deleted tags as well.
"""
patient_ids = [e.patient_id for e in episodes]
patient_subs = defaultdict(lambda: defaultdict(list))
patient_subs = defaultdict(dict)

episode_subs = self.serialised_episode_subrecords(episodes, user)
for model in patient_subrecords():
name = model.get_api_name()

for patient_id in patient_ids:
patient_subs[patient_id][name] = []

subrecords = prefetch(
model.objects.filter(patient__in=patient_ids)
)
Expand Down
22 changes: 11 additions & 11 deletions opal/models.py
Expand Up @@ -874,28 +874,28 @@ def to_dict(self, user, shallow=False):
for model in patient_subrecords():
subrecords = model.objects.filter(patient_id=self.patient.id)

if subrecords:
d[model.get_api_name()] = [
subrecord.to_dict(user) for subrecord in subrecords
]
d[model.get_api_name()] = [
subrecord.to_dict(user) for subrecord in subrecords
]

for model in episode_subrecords():
subrecords = model.objects.filter(episode_id=self.id)

if subrecords:
d[model.get_api_name()] = [
subrecord.to_dict(user) for subrecord in subrecords
]
d[model.get_api_name()] = [
subrecord.to_dict(user) for subrecord in subrecords
]

d['tagging'] = self.tagging_dict(user)
return d


class Subrecord(UpdatesFromDictMixin, ToDictMixin, TrackedModel, models.Model):
consistency_token = models.CharField(max_length=8)
_is_singleton = False
_advanced_searchable = True
_is_singleton = False
_advanced_searchable = True
_exclude_from_subrecords = False

consistency_token = models.CharField(max_length=8)

class Meta:
abstract = True

Expand Down
14 changes: 7 additions & 7 deletions opal/templates/forms/investigation_form.html
Expand Up @@ -15,7 +15,7 @@
{% radio label="IgG" model="editing.investigation.vca_igg" show="testType == 'micro_test_ebv_serology'" lookuplist="['pending', 'positive', 'negative', 'equivocal', 'not done']" %}
{% radio label="EBNA IgG" model="editing.investigation.ebna_igg" show="testType == 'micro_test_ebv_serology'" lookuplist="['pending', 'positive', 'negative', 'equivocal', 'not done']" %}

{% radio label="Result" model="editing.investigation.result" show="testType == 'micro_test_hiv'" lookuplist="['pending', 'antibody positive', 'p24 antigen only', 'negative']" %}
{% radio label="Result" model="editing.investigation.result" show="testType == 'micro_test_hiv'" lookuplist="['pending', 'antibody positive', 'p24 antigen only', 'negative']" element_name="micro_test_hiv" %}

{% radio label="HBsAg" model="editing.investigation.hbsag" show="testType == 'micro_test_hepititis_b_serology'" lookuplist="['pending', 'positive', 'negative', 'equivocal', 'not done']" %}
{% radio label="anti-HbS" model="editing.investigation.anti_hbs" show="testType == 'micro_test_hepititis_b_serology'" lookuplist="['pending', 'positive', 'negative', 'equivocal', 'not done']" %}
Expand All @@ -25,9 +25,9 @@
{% input label="RPR" model="editing.investigation.rpr" show="testType == 'micro_test_syphilis_serology'" %}
{% radio label="TPPA" model="editing.investigation.tppa" show="testType == 'micro_test_syphilis_serology'" lookuplist="['pending', 'positive', 'negative']" %}

{% radio label="Result" model="editing.investigation.result" show="testType == 'micro_test_single_test_pos_neg'" lookuplist="['pending', 'positive', 'negative']" element_name="test_pos_neg" %}
{% radio label="Result" model="editing.investigation.result" show="testType == 'micro_test_single_test_pos_neg'" lookuplist="['pending', 'positive', 'negative']" element_name="micro_test_single_test_pos_neg" %}

{% radio label="Result" model="editing.investigation.result" show="testType == 'micro_test_single_test_pos_neg_equiv'" lookuplist="['pending', 'positive', 'negative', 'equivocal', 'not done']" element_name="test_pos_neg_equiv" %}
{% radio label="Result" model="editing.investigation.result" show="testType == 'micro_test_single_test_pos_neg_equiv'" lookuplist="['pending', 'positive', 'negative', 'equivocal', 'not done']" element_name="micro_test_single_test_pos_neg_equiv" %}

{% radio label="IgG" model="editing.investigation.igg" show="testType == 'micro_test_single_igg_test'" lookuplist="['pending', 'positive', 'negative', 'equivocal', 'not done']" %}

Expand Down Expand Up @@ -101,17 +101,17 @@
<div class="col-sm-8">

<label class="radio-inline">
<input class="" type="radio" value="pending" ng-model="editing.investigation.result" name="leish_pcr">
<input class="" type="radio" value="pending" ng-model="editing.investigation.result" name="micro_test_leishmaniasis_pcr">
pending
</label>

<label class="radio-inline">
<input class="" type="radio" value="positive" ng-model="editing.investigation.result" name="leish_pcr">
<input class="" type="radio" value="positive" ng-model="editing.investigation.result" name="micro_test_leishmaniasis_pcr">
positive
</label>

<label class="radio-inline">
<input class="" type="radio" value="negative" ng-model="editing.investigation.result" name="leish_pcr">
<input class="" type="radio" value="negative" ng-model="editing.investigation.result" name="micro_test_leishmaniasis_pcr">
negative
</label>

Expand Down Expand Up @@ -538,7 +538,7 @@
<div class="form-group">
<label class="control-label col-sm-3">Result</label>
<div class="col-sm-8">
<input class="form-control" type="text" ng-model="editing.investigation.result">
<input class="form-control" type="text" ng-model="editing.investigation.result" name="micro_test_other">
</div>
</div>

Expand Down

0 comments on commit 83cc6f7

Please sign in to comment.