From 88388f40b818e0a9323dc548ab3dd0e930ae4240 Mon Sep 17 00:00:00 2001 From: Kasper Brandt Date: Wed, 13 May 2015 17:39:03 +0200 Subject: [PATCH] [#1447] IATI export: performance increase through React checks --- akvo/iati/checks/v201.py | 10 ++- akvo/rest/urls.py | 3 + akvo/rest/views/__init__.py | 2 + akvo/rest/views/project_iati_checks.py | 25 +++++++ akvo/rsr/forms.py | 16 +---- akvo/rsr/static/scripts-src/my-iati.js | 92 +++++++++++++++++++++++++ akvo/rsr/static/scripts-src/my-iati.jsx | 92 +++++++++++++++++++++++++ akvo/settings/40-pipeline.conf | 6 ++ akvo/templates/myrsr/my_iati.html | 10 +++ 9 files changed, 238 insertions(+), 18 deletions(-) create mode 100644 akvo/rest/views/project_iati_checks.py create mode 100644 akvo/rsr/static/scripts-src/my-iati.js create mode 100644 akvo/rsr/static/scripts-src/my-iati.jsx diff --git a/akvo/iati/checks/v201.py b/akvo/iati/checks/v201.py index 2147e9a4db..96f7c13009 100644 --- a/akvo/iati/checks/v201.py +++ b/akvo/iati/checks/v201.py @@ -26,6 +26,7 @@ 'related_activity', 'legacy_data', 'conditions', + 'results', 'crs_add', 'fss', ] @@ -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: diff --git a/akvo/rest/urls.py b/akvo/rest/urls.py index 48f2537109..ea1667e2a4 100644 --- a/akvo/rest/urls.py +++ b/akvo/rest/urls.py @@ -86,6 +86,9 @@ url(r'^user/(?P[0-9]+)/request_organisation/$', views.request_organisation, name='user_request_organisation'), + url(r'^project_iati_check/(?P[0-9]+)/$', + views.ProjectIatiCheckView.as_view(), + name='project_iati_check'), ) # Typeahead diff --git a/akvo/rest/views/__init__.py b/akvo/rest/views/__init__.py index b9caab0aec..f1cedc831a 100644 --- a/akvo/rest/views/__init__.py +++ b/akvo/rest/views/__init__.py @@ -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 @@ -85,6 +86,7 @@ 'ProjectContactViewSet', 'ProjectDocumentViewSet', 'ProjectExtraViewSet', + 'ProjectIatiCheckView', 'ProjectLocationViewSet', 'ProjectUpdateExtraViewSet', 'ProjectUpdateLocationViewSet', diff --git a/akvo/rest/views/project_iati_checks.py b/akvo/rest/views/project_iati_checks.py new file mode 100644 index 0000000000..2234730dda --- /dev/null +++ b/akvo/rest/views/project_iati_checks.py @@ -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) diff --git a/akvo/rsr/forms.py b/akvo/rsr/forms.py index 13cbbc4015..1cbc157a20 100644 --- a/akvo/rsr/forms.py +++ b/akvo/rsr/forms.py @@ -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'%s' % obj.__unicode__()) - else: - label = obj.__unicode__() - for check in checks[1]: - if check[0] == u'error': - label += u'
- %s' % check[1] - return mark_safe(u'%s' % 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:") diff --git a/akvo/rsr/static/scripts-src/my-iati.js b/akvo/rsr/static/scripts-src/my-iati.js new file mode 100644 index 0000000000..174c4a9e87 --- /dev/null +++ b/akvo/rsr/static/scripts-src/my-iati.js @@ -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'); diff --git a/akvo/rsr/static/scripts-src/my-iati.jsx b/akvo/rsr/static/scripts-src/my-iati.jsx new file mode 100644 index 0000000000..6d5abcdb9e --- /dev/null +++ b/akvo/rsr/static/scripts-src/my-iati.jsx @@ -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 ( + Perform checks + ); + } + }); + + React.render( + , + document.getElementById(component_id) + ); +} + +loadComponent('react_iati_checks'); diff --git a/akvo/settings/40-pipeline.conf b/akvo/settings/40-pipeline.conf index 495e3383e5..a8dbf0f45a 100644 --- a/akvo/settings/40-pipeline.conf +++ b/akvo/settings/40-pipeline.conf @@ -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', + }, } diff --git a/akvo/templates/myrsr/my_iati.html b/akvo/templates/myrsr/my_iati.html index 29d7252dca..6e63c122f6 100644 --- a/akvo/templates/myrsr/my_iati.html +++ b/akvo/templates/myrsr/my_iati.html @@ -41,6 +41,7 @@

{% trans "My IATI" %}{% if selected_org %} {% trans "for" %} {{selected_org.

{% elif project_count > 0 %} +

{% csrf_token %} @@ -82,3 +83,12 @@

{% trans 'Existing IATI exports' %}

{% endif %} {% endblock %} + +{% block react_js %} + +{% endblock react_js %} + +{% block js %} + {{ block.super }} + {% compressed_js 'my_iati' %} +{% endblock js %} \ No newline at end of file