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

Procedure group api #9

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
10 changes: 5 additions & 5 deletions src/mds/api/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class JSONResponse(HttpResponse):
data
message content
"""
def __init__(self, data):
HttpResponse.__init__(self, data, mimetype="application/json; charset=utf-8")
def __init__(self, data, status):
HttpResponse.__init__(self, data, mimetype="application/json; charset=utf-8", status=status)
self['X-JSON'] = data

def fail(data, code=404, errors=[]):
Expand All @@ -63,7 +63,7 @@ def fail(data, code=404, errors=[]):
'code' : code,
'message': data,
'errors': errors, }
return response
return JSONResponse(response, status=code)

def succeed(data, code=200):
''' Success response as a python dict with data '''
Expand All @@ -81,15 +81,15 @@ def succeed(data, code=200):
response = {'status': 'SUCCESS',
'code' : code,
'message': data, }
return response
return JSONResponse(response, status=code)

def error(exception):
errors = traceback.format_exception_only(*sys.exc_info()[:2])
response = {'status': 'FAILURE',
'code' : code,
'message': None,
'errors': errors, }
return response
return JSONResponse(response, status=code)

def unauthorized(message):
return fail(message, Codes.UNAUTHORIZED)
13 changes: 12 additions & 1 deletion src/mds/core/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
'ObservationForm',
'SubjectForm',
'ProcedureForm',
'ProcedureGroupForm',
'SessionForm',
]

Expand Down Expand Up @@ -88,7 +89,17 @@ class ProcedureForm(forms.ModelForm):

class Meta:
model = Procedure


class ProcedureGroupForm(forms.ModelForm):
""" A simple procedure group form
"""
class Meta:
model = ProcedureGroup
fields = "__all__"
widgets = {
'procedures': forms.CheckboxSelectMultiple
}

class SubjectForm(forms.ModelForm):
""" A simple patient form
"""
Expand Down
106 changes: 106 additions & 0 deletions src/mds/core/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.core.files.base import ContentFile
from django.db.models import Max, Q

from piston.handler import BaseHandler
from piston.resource import Resource
Expand Down Expand Up @@ -38,6 +40,7 @@
'ObservationHandler',
'ObserverHandler',
'ProcedureHandler',
'ProcedureGroupHandler',
'DocHandler' ,
'SessionHandler',
'SubjectHandler',
Expand Down Expand Up @@ -226,6 +229,37 @@ class ProcedureHandler(DispatchingHandler):
"voided",
)
signals = { LOGGER:( EventSignal(), EventSignalHandler(Event))}

def create(self,request, uuid=None, *args, **kwargs):
if 'json' in request.META.get('CONTENT_TYPE', ''):
payload = cjson.decode(request.read())
# for now only support version, author, title, description
valid_object = False
supported_attributes = (
'title',
'author',
'description',
'source_file_content',
'version'
)
instance = Procedure()
for attr in supported_attributes:
value = payload.get(attr, None)
if value and attr != 'source_file_content':
setattr(instance, attr, value)
if attr == 'title':
valid_object = True
elif value and attr == 'source_file_content':
instance.src.save(instance.title, ContentFile(value))

if valid_object:
instance.save()
return succeed({'uuid':instance.uuid})
else:
return fail('Missing mandatory title attribute for procedure', 400)

else:
return super(ProcedureHandler, self).create(request, uuid, *args, **kwargs)

def _read_by_uuid(self,request,uuid):
""" Returns the procedure file instead of the verbose representation on
Expand All @@ -235,6 +269,78 @@ def _read_by_uuid(self,request,uuid):
obj = model.objects.get(uuid=uuid)
return open(obj.src.path).read()

@logged
class ProcedureGroupHandler(DispatchingHandler):
allowed_methods = ('GET', 'POST', 'PUT')
model = ProcedureGroup
form = ProcedureGroupForm
fields = (
"uuid",
"title",
"description",
"author",
"procedures",
"modified",
"created",
"voided",
)
def sync(self, request, uuid):
proceduregroup = ProcedureGroup.objects.filter(uuid=uuid)
if not proceduregroup:
return fail('Procedure group not found', 404)
else:
proceduregroup = proceduregroup[0]
if 'json' not in request.META.get('CONTENT_TYPE', ''):
return fail('Unexpected content type', 400)
server_procedures = proceduregroup.procedures.all()
request_payload = cjson.decode(request.read())
server_procedures = server_procedures.filter(voided=False).values('title').annotate(max_version=Max('version'))
procedure_titles = map(lambda procedure: procedure['title'], server_procedures)
# Get procedures with a newer version
procedures_to_update_clause = Q()
request_procedures = request_payload.get('procedures', {})
for server_procedure in server_procedures:
request_procedure_version = request_procedures.get(server_procedure['title'], None)
# If there is a version mismatch between the most recent non-voided procedure version on the server and client, return the most updated version to the client
if not request_procedure_version or request_procedure_version != server_procedure['max_version']:
where_clause = (Q(title=server_procedure['title']) & Q(version=server_procedure['max_version']))
procedures_to_update_clause = procedures_to_update_clause | where_clause
procedures_to_update = Procedure.objects.filter(procedures_to_update_clause) if len(procedures_to_update_clause) != 0 else []
updated_procedures = map( \
lambda procedure:{'title': procedure.title, 'author': procedure.author, 'description': procedure.description, 'version': procedure.version, 'source_file_content': procedure.src.read().decode('utf-8')}, \
procedures_to_update \
)
# Find procedures that don't exist in the group
unknown_request_procedures = filter(lambda request_procedure_title: request_procedure_title not in procedure_titles, request_procedures.keys())
return_payload = {'updated_procedures': updated_procedures, 'unknown_procedures': unknown_request_procedures}
return succeed(return_payload)

def read(self, request, uuid=None, **kwargs):
result_set = ProcedureGroup.objects.all()
if uuid:
result_set = result_set.filter(uuid=uuid)
author_filter = request.GET.get('author', None)
if author_filter:
result_set = result_set.filter(author=author_filter)
title_filter = request.GET.get('title', None)
if title_filter:
result_set = result_set.filter(title=title_filter)
if not result_set and uuid:
return fail('Could not find procedure group with given uuid')
elif uuid:
result_set = result_set[0]
return succeed(result_set)
def create(self,request, uuid=None, *args, **kwargs):
if 'sync' == kwargs.get('op', None):
if uuid:
return self.sync(request, uuid)
else:
return fail('Not Found', 404)
else:
super(ProcedureGroupHandler, self).create(request, uuid, args, kwargs)
signals = { LOGGER:( EventSignal(), EventSignalHandler(Event))}


@logged
class SubjectHandler(DispatchingHandler):
""" Handles subject requests. """
Expand Down
2 changes: 2 additions & 0 deletions src/mds/core/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .observation import Observation
from .observer import Observer
from .procedure import Procedure
from .procedure_group import ProcedureGroup
from .subject import Subject
from mds.core.extensions.models import *

Expand All @@ -29,5 +30,6 @@
'Observation',
'Observer',
'Procedure',
'ProcedureGroup',
'Subject',
]
5 changes: 3 additions & 2 deletions src/mds/core/models/procedure.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Procedure(models.Model):

class Meta:
app_label = "core"
unique_together = ('title', 'version')
uuid = models.SlugField(max_length=36, unique=True, default=make_uuid, editable=False)
""" A universally unique identifier """

Expand All @@ -31,8 +32,8 @@ class Meta:
description = models.TextField()
""" Additional narrative information about the procedure. """

version = models.CharField(max_length=255, default="1.0")
""" The version string for this instance """
version = models.IntegerField(default=1)
""" The version for this instance """

src = models.FileField(upload_to='core/procedure', blank=True)
""" File storage location for the procedure """
Expand Down
41 changes: 41 additions & 0 deletions src/mds/core/models/procedure_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
""" A group of procedures. Note there is a many-to-many relationship between procedure and procedure groups

:Authors: Sana dev team
:Version: 2.0
"""

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

from mds.api.utils import make_uuid
@python_2_unicode_compatible
class ProcedureGroup(models.Model):
""" A group of procedures"""

class Meta:
app_label = "core"
uuid = models.SlugField(max_length=36, unique=True, default=make_uuid, editable=False)
""" A universally unique identifier """

created = models.DateTimeField(auto_now_add=True)
""" When the object was created """

modified = models.DateTimeField(auto_now=True)
""" updated on modification """

title = models.CharField(max_length=255)
""" A descriptive title for the procedure group. """

author = models.CharField(max_length=255)
""" The creator of the procedure group """

description = models.TextField()
""" Additional narrative information about the group of procedures. """

procedures = models.ManyToManyField('Procedure', related_name='groups')

voided = models.BooleanField(default=False)

def __str__(self):
return "%s" % (self.title)

3 changes: 2 additions & 1 deletion src/mds/core/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
'rsrc_subject',
'rsrc_observer',
'rsrc_procedure',
'rsrc_procedure_group',
'rsrc_encounter',
'rsrc_observation',
'rsrc_notification',
Expand All @@ -27,7 +28,7 @@
rsrc_subject = Resource(SubjectHandler)
rsrc_observer = Resource(ObserverHandler)
rsrc_procedure = Resource(ProcedureHandler)

rsrc_procedure_group = Resource(ProcedureGroupHandler)
rsrc_encounter = Resource(EncounterHandler)
rsrc_observation = Resource(ObservationHandler)

Expand Down
5 changes: 5 additions & 0 deletions src/mds/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@
url(r'^procedure/$', rsrc_procedure, name='procedure-list'),
url(r'^procedure/(?P<uuid>[^/]+)/$', rsrc_procedure, name='procedure'),

# procedure groups
url(r'^proceduregroup/$', rsrc_procedure_group, name='proceduregroup-list'),
url(r'^proceduregroup/(?P<uuid>[^/]+)/$', rsrc_procedure_group, name='proceduregroup'),
url(r'^proceduregroup/(?P<uuid>[^/]+)/sync/$', rsrc_procedure_group, name='proceduregroup-sync', kwargs={'op':'sync'}),

# subjects
url(r'^subject/$', rsrc_subject, name='subject-list'),
url(r'^subject/(?P<uuid>[^/]+)/$', rsrc_subject, name='subject'),
Expand Down
1 change: 1 addition & 0 deletions src/mds/mrs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,3 +536,4 @@ def convert_binary(binary):
result, message = FFmpeg().convert(binary, type, extension)
logging.debug('FFmpeg: %s %s' % (result, message))
return result
'''
1 change: 1 addition & 0 deletions src/mds/templates/web/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<link rel="stylesheet" href="{{ STATIC_PREFIX }}web/css/index.css">
<link rel="stylesheet" href="{{ STATIC_PREFIX }}web/css/lib/fontello.css" />
<link rel="stylesheet" href="{{ STATIC_PREFIX }}web/css/lib/normalize.css" />
<link rel="stylesheet" href="{{ STATIC_PREFIX }}web/css/chosen.min.css" />
<!--
link rel="stylesheet" href="{{ STATIC_PREFIX }}web/css/sidebar.css" /
-->
Expand Down
6 changes: 6 additions & 0 deletions src/mds/templates/web/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
<a href="{{ field.url }}"><img src="{{ STATIC_PREFIX }}web/img/binary.png"</a>
{% elif field.type == "object" %}
<a href="{{ field.url }}">{{ field.value }}</a>
{% elif field.type == "list" %}
<ul>
{% for el in field.value %}
<li> {{ el }} </li>
{% endfor %}
</ul>
{% else %}
{{ field.value }}
{% endif %}
Expand Down
1 change: 1 addition & 0 deletions src/mds/templates/web/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{% endblock %}
{% block content %}
<!-- "{% url 'web:'|add:model|add:'-edit' pk=object.id %}" -->
{{form.media}}
<form
enctype="multipart/form-data"
action="."
Expand Down
1 change: 1 addition & 0 deletions src/mds/templates/web/form_new.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
});
{% endblock %}
{% block content %}
{{ form.media }}
<form enctype="multipart/form-data" action="{% url 'web:'|add:model|add:'-create' %}" method="post">
{{ csrftoken }}
{% include "web/form_snippet.html" %}
Expand Down
12 changes: 12 additions & 0 deletions src/mds/web/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django import forms
from django.forms.extras.widgets import SelectDateWidget
from django.forms.widgets import PasswordInput
from django.contrib.admin.widgets import FilteredSelectMultiple

from django.contrib.auth.models import User

Expand All @@ -18,6 +19,7 @@

__all__ = [
"ProcedureForm",
"ProcedureGroupForm",
"EmptyEncounterForm",
"EncounterTaskForm",
"AllowReadonly",
Expand Down Expand Up @@ -145,6 +147,16 @@ class Meta:
class NewMeta:
readonly = ['uuid','created',]

class ProcedureGroupForm(forms.ModelForm):
""" A simple procedure group form
"""
procedures = forms.ModelMultipleChoiceField(queryset=Procedure.objects.all(), required=False)
class Media:
js = ('web/js/chosen.jquery.min.js','web/js/chosen-multiselect.js')
class Meta:
model = ProcedureGroup
fields = "__all__"

class UserInline(InlineFormSet):
model = User

Expand Down
4 changes: 4 additions & 0 deletions src/mds/web/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ def register(self, portal_view, **options):
'label': 'procedures',
'context_name':'procedure-list',
},
{
'label': 'Procedure Groups',
'context_name':'proceduregroup-list',
}
]
},

Expand Down
Loading