Skip to content

Commit

Permalink
[#1447] IATI export: performance increase through React checks
Browse files Browse the repository at this point in the history
  • Loading branch information
KasperBrandt committed May 13, 2015
1 parent 8af3c9e commit 88388f4
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 18 deletions.
10 changes: 7 additions & 3 deletions akvo/iati/checks/v201.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'related_activity',
'legacy_data',
'conditions',
'results',
'crs_add',
'fss',
]
Expand Down Expand Up @@ -618,15 +619,18 @@ def results(self):

for result in self.project.results.all():
if not result.type:
checks.append((u'warning', u'result (id: %s) has no type '
self.all_checks_passed = False
checks.append((u'error', u'result (id: %s) has no type '
u'specified' % str(result.pk)))

if not result.title:
checks.append((u'warning', u'result (id: %s) has no title '
u'specified' % str(result.pk)))

if not result.indicators.all():
checks.append((u'warning', u'result (id: %s) has no indicators' % str(result.pk)))
if not result.indicators.all() and not result.description:
self.all_checks_passed = False
checks.append((u'error', u'result (id: %s) has no description and no '
u'indicator(s)' % str(result.pk)))

for indicator in result.indicators.all():
if not indicator.measure:
Expand Down
3 changes: 3 additions & 0 deletions akvo/rest/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
url(r'^user/(?P<pk>[0-9]+)/request_organisation/$',
views.request_organisation,
name='user_request_organisation'),
url(r'^project_iati_check/(?P<pk>[0-9]+)/$',
views.ProjectIatiCheckView.as_view(),
name='project_iati_check'),
)

# Typeahead
Expand Down
2 changes: 2 additions & 0 deletions akvo/rest/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from .project_document import ProjectDocumentViewSet
from .project_condition import ProjectConditionViewSet
from .project_contact import ProjectContactViewSet
from .project_iati_checks import ProjectIatiCheckView
from .project_location import ProjectLocationViewSet, MapProjectLocationViewSet
from .project_update import ProjectUpdateViewSet, ProjectUpdateExtraViewSet
from .project_update_location import ProjectUpdateLocationViewSet, MapProjectUpdateLocationViewSet
Expand Down Expand Up @@ -85,6 +86,7 @@
'ProjectContactViewSet',
'ProjectDocumentViewSet',
'ProjectExtraViewSet',
'ProjectIatiCheckView',
'ProjectLocationViewSet',
'ProjectUpdateExtraViewSet',
'ProjectUpdateLocationViewSet',
Expand Down
25 changes: 25 additions & 0 deletions akvo/rest/views/project_iati_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from akvo.rsr.models import Project
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response


class ProjectIatiCheckView(APIView):
"""
List the result of IATI checks of a Project.
"""
def get_object(self, pk):
try:
return Project.objects.get(pk=pk)
except Project.DoesNotExist:
raise Http404

def get(self, request, pk, format=None):
project = self.get_object(pk)
check_results = project.check_mandatory_fields()
response = {
'all_checks_passed': str(check_results[0]),
'checks': check_results[1],
}

return Response(response)
16 changes: 1 addition & 15 deletions akvo/rsr/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,24 +394,10 @@ def __init__(self, user, *args, **kwargs):
)


class CustomLabelModelChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
checks = obj.check_mandatory_fields()
if checks[0]:
return mark_safe(u'<span class="success">%s</span>' % obj.__unicode__())
else:
label = obj.__unicode__()
for check in checks[1]:
if check[0] == u'error':
label += u'<br>- %s' % check[1]
return mark_safe(u'<span class="error">%s</span>' % label)



class IatiExportForm(forms.ModelForm):
"""Form for adding an entry to the IATI export model."""
is_public = forms.BooleanField(required=False, label=_(u"Show IATI file on organisation page"))
projects = CustomLabelModelChoiceField(
projects = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Project.objects.all(),
label=_(u"Select the projects included in the export:")
Expand Down
92 changes: 92 additions & 0 deletions akvo/rsr/static/scripts-src/my-iati.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/** @jsx React.DOM */

// Akvo RSR is covered by the GNU Affero General Public License.
// See more details in the license.txt file located at the root folder of the
// Akvo RSR module. For additional details on the GNU license please see
// < http://www.gnu.org/licenses/agpl.html >.


function loadAsync(url, retryCount, retryLimit, label) {
var xmlHttp;

xmlHttp = new XMLHttpRequest();

xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == XMLHttpRequest.DONE) {

if(xmlHttp.status == 200){
processResponse(label, xmlHttp.responseText);
} else {
if (retryCount >= retryLimit) {
return;
} else {
retryCount = retryCount + 1;
loadAsync(url, retryCount, retryLimit);
}
}
} else {
return;
}
};

xmlHttp.open("GET", url, true);
xmlHttp.send();
}

function processResponse(label, response) {
var label_content, checks, all_checks_passed, span;

label_content = label.innerHTML;
checks = JSON.parse(response);

all_checks_passed = checks.all_checks_passed;

if (all_checks_passed === "True") {
span = document.createElement("span");
span.className = "success";
span.innerHTML = label_content;

label.innerHTML = '';
label.appendChild(span);

} else if (all_checks_passed === "False") {
span = document.createElement("span");
span.className = "error";
span.innerHTML = label_content;

label.innerHTML = '';
label.appendChild(span);
}
}

function getProjectLabels() {
var labels;

labels = document.getElementById('id_projects').getElementsByTagName('label');

for (var i = 0; i < labels.length; i++) {
var project_id;

project_id = labels[i].getElementsByTagName('input')[0].value;
loadAsync('/rest/v1/project_iati_check/' + project_id + '/?format=json', 0, 3, labels[i]);
}
}

function loadComponent(component_id) {
var Container;

Container = React.createClass({displayName: 'Container',
render: function() {
return (
React.DOM.a( {onClick:getProjectLabels}, "Perform checks")
);
}
});

React.render(
Container(null ),
document.getElementById(component_id)
);
}

loadComponent('react_iati_checks');
92 changes: 92 additions & 0 deletions akvo/rsr/static/scripts-src/my-iati.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/** @jsx React.DOM */

// Akvo RSR is covered by the GNU Affero General Public License.
// See more details in the license.txt file located at the root folder of the
// Akvo RSR module. For additional details on the GNU license please see
// < http://www.gnu.org/licenses/agpl.html >.


function loadAsync(url, retryCount, retryLimit, label) {
var xmlHttp;

xmlHttp = new XMLHttpRequest();

xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == XMLHttpRequest.DONE) {

if(xmlHttp.status == 200){
processResponse(label, xmlHttp.responseText);
} else {
if (retryCount >= retryLimit) {
return;
} else {
retryCount = retryCount + 1;
loadAsync(url, retryCount, retryLimit);
}
}
} else {
return;
}
};

xmlHttp.open("GET", url, true);
xmlHttp.send();
}

function processResponse(label, response) {
var label_content, checks, all_checks_passed, span;

label_content = label.innerHTML;
checks = JSON.parse(response);

all_checks_passed = checks.all_checks_passed;

if (all_checks_passed === "True") {
span = document.createElement("span");
span.className = "success";
span.innerHTML = label_content;

label.innerHTML = '';
label.appendChild(span);

} else if (all_checks_passed === "False") {
span = document.createElement("span");
span.className = "error";
span.innerHTML = label_content;

label.innerHTML = '';
label.appendChild(span);
}
}

function getProjectLabels() {
var labels;

labels = document.getElementById('id_projects').getElementsByTagName('label');

for (var i = 0; i < labels.length; i++) {
var project_id;

project_id = labels[i].getElementsByTagName('input')[0].value;
loadAsync('/rest/v1/project_iati_check/' + project_id + '/?format=json', 0, 3, labels[i]);
}
}

function loadComponent(component_id) {
var Container;

Container = React.createClass({
render: function() {
return (
<a onClick={getProjectLabels}>Perform checks</a>
);
}
});

React.render(
<Container />,
document.getElementById(component_id)
);
}

loadComponent('react_iati_checks');
6 changes: 6 additions & 0 deletions akvo/settings/40-pipeline.conf
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,10 @@ PIPELINE_JS = {
),
'output_filename': 'scripts/rsr-project-main.min.js',
},
'my_iati': {
'source_filenames': (
'scripts-src/my-iati.js',
),
'output_filename': 'scripts/my-iati.min.js',
},
}
10 changes: 10 additions & 0 deletions akvo/templates/myrsr/my_iati.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ <h3>{% trans "My IATI" %}{% if selected_org %} {% trans "for" %} {{selected_org.
</form>
</p>
{% elif project_count > 0 %}
<div id="react_iati_checks"></div>
<p>
<form method="POST" action="" id="iati_export_form">
{% csrf_token %}
Expand Down Expand Up @@ -82,3 +83,12 @@ <h4 class="detailedInfo">{% trans 'Existing IATI exports' %}</h4>
</div>
{% endif %}
{% endblock %}

{% block react_js %}
<script src="//fb.me/react-0.12.0.js"></script>
{% endblock react_js %}

{% block js %}
{{ block.super }}
{% compressed_js 'my_iati' %}
{% endblock js %}

0 comments on commit 88388f4

Please sign in to comment.