Skip to content

Commit

Permalink
Merge pull request #1837 from akvo/issue/1746-add-orgs
Browse files Browse the repository at this point in the history
[#1746] Add organisations in project editor
  • Loading branch information
KasperBrandt committed Oct 5, 2015
2 parents 14078c3 + 2cd8dae commit b4492e3
Show file tree
Hide file tree
Showing 7 changed files with 697 additions and 32 deletions.
33 changes: 33 additions & 0 deletions akvo/rsr/migrations/0035_auto_20151002_1511.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import akvo.rsr.fields


class Migration(migrations.Migration):

dependencies = [
('rsr', '0034_auto_20150916_1402'),
]

operations = [
migrations.AlterField(
model_name='organisation',
name='iati_org_id',
field=akvo.rsr.fields.ValidXMLCharField(null=True, default=None, max_length=75, blank=True, unique=True, verbose_name='IATI organisation ID', db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='organisation',
name='long_name',
field=akvo.rsr.fields.ValidXMLCharField(help_text='Full name of organisation (75 characters).', unique=True, max_length=75, verbose_name='long name', db_index=True),
preserve_default=True,
),
migrations.AlterField(
model_name='organisation',
name='organisation_type',
field=akvo.rsr.fields.ValidXMLCharField(choices=[(b'N', 'NGO'), (b'G', 'Governmental'), (b'C', 'Commercial'), (b'K', 'Knowledge institution')], max_length=1, blank=True, null=True, verbose_name='organisation type', db_index=True),
preserve_default=True,
),
]
8 changes: 5 additions & 3 deletions akvo/rsr/models/organisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,16 @@ def org_type_from_iati_type(cls, iati_type):
u'(25 characters).')
)
long_name = ValidXMLCharField(
_(u'long name'), blank=True, max_length=75, unique=True,
_(u'long name'), max_length=75, db_index=True, unique=True,
help_text=_(u'Full name of organisation (75 characters).'),
)
language = ValidXMLCharField(
_(u'language'), max_length=2, choices=settings.LANGUAGES, default='en',
help_text=_(u'The main language of the organisation'),
)
organisation_type = ValidXMLCharField(
_(u'organisation type'), max_length=1, db_index=True, choices=ORG_TYPES
_(u'organisation type'), max_length=1, db_index=True, choices=ORG_TYPES, blank=True,
null=True
)
new_organisation_type = models.IntegerField(
_(u'IATI organisation type'), db_index=True,
Expand All @@ -97,7 +98,8 @@ def org_type_from_iati_type(cls, iati_type):
u'matches your organisation.'),
)
iati_org_id = ValidXMLCharField(
_(u'IATI organisation ID'), max_length=75, blank=True, null=True, db_index=True, unique=True
_(u'IATI organisation ID'), max_length=75, blank=True, null=True, db_index=True,
unique=True, default=None
)
internal_org_ids = models.ManyToManyField(
'self', through='InternalOrganisationID', symmetrical=False,
Expand Down
239 changes: 226 additions & 13 deletions akvo/rsr/static/scripts-src/myrsr-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ function buildReactComponents(typeaheadOptions, typeaheadCallback, displayOption
return short;
}
if (!long) {
return short
return short;
}
return short + ' (' + long + ')';
}
Expand Down Expand Up @@ -950,17 +950,17 @@ function buildReactComponents(typeaheadOptions, typeaheadCallback, displayOption
setPageCompletionPercentage();
}

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

// If we already have the response cached, don't fetch it again
if (responses[url] !== null) {
if (responses[url] !== null && !forceReloadOrg) {
callback(responses[url]);
return;
}

// If the response is in localStorage, don't fetch it again
if (localStorageResponses !== null && localStorageResponses !== '') {
if (localStorageResponses !== null && localStorageResponses !== '' && !forceReloadOrg) {
if (localStorageResponses[url] !== undefined) {
var response = localStorageResponses[url];

Expand Down Expand Up @@ -1240,7 +1240,7 @@ function getOnClick(pName, parentElement) {
return onclick;
}

function updateTypeaheads() {
function updateTypeaheads(forceReloadOrg) {
var els, filterOption, labelText, helpText, API, inputType;

els1 = document.querySelectorAll('.related-project-input');
Expand Down Expand Up @@ -1269,7 +1269,7 @@ function updateTypeaheads() {
inputType = 'org';


updateTypeahead(els, filterOption, labelText, helpText, API, inputType);
updateTypeahead(els, filterOption, labelText, helpText, API, inputType, forceReloadOrg);

els = document.querySelectorAll('.transaction-provider-org-input');
labelText = defaultValues.provider_org_label;
Expand All @@ -1279,7 +1279,7 @@ function updateTypeaheads() {
inputType = 'org';


updateTypeahead(els, filterOption, labelText, helpText, API, inputType);
updateTypeahead(els, filterOption, labelText, helpText, API, inputType, forceReloadOrg);

els = document.querySelectorAll('.transaction-receiver-org-input');
labelText = defaultValues.recipient_org_label;
Expand All @@ -1289,16 +1289,26 @@ function updateTypeaheads() {
inputType = 'org';


updateTypeahead(els, filterOption, labelText, helpText, API, inputType);
updateTypeahead(els, filterOption, labelText, helpText, API, inputType, forceReloadOrg);

function updateTypeahead(els, filterOption, labelText, helpText, API, inputType) {
function updateTypeahead(els, filterOption, labelText, helpText, API, inputType, forceReloadOrg) {
for (var i = 0; i < els.length; i++) {
var el = els[i];

// Check if we've already rendered this typeahead
if (elHasClass(el, 'has-typeahead')) {
if (forceReloadOrg) {

continue;
// Remove the existing typeahead, then build a new
// one with the reloaded API response
var child = el.querySelector('div');
el.removeChild(child);
} else {

// Typeahead exists and we don't need to reload the API response.
// Do nothing.
continue;
}
}

var childSelector = el.getAttribute('data-child-id');
Expand All @@ -1320,14 +1330,14 @@ function updateTypeaheads() {
valueId = el.getAttribute('data-value');
}

var cb = getLoadAsync(childSelector, childClass, valueId, label, help, filterOption, inputType);
var cb = getLoadAsync(childSelector, childClass, valueId, label, help, filterOption, inputType, forceReloadOrg);
cb();
}
}

function getLoadAsync(childSelector, childClass, valueId, label, help, filterOption, inputType) {
function getLoadAsync(childSelector, childClass, valueId, label, help, filterOption, inputType, forceReloadOrg) {
var output = function() {
loadAsync(API, 0, MAX_RETRIES, getCallback(childSelector, childClass, valueId, label, help, filterOption, inputType));
loadAsync(API, 0, MAX_RETRIES, getCallback(childSelector, childClass, valueId, label, help, filterOption, inputType), forceReloadOrg);
};

return output;
Expand Down Expand Up @@ -2099,6 +2109,208 @@ function setUnsavedChangesMessage() {
};
}

/* Set each "add organisation" link to open the "add organisation"
** modal dialog on click */

function setModalOnClicks() {
var links = document.querySelectorAll('.add-organisation');

for (var i = 0; i < links.length; i++) {
var el = links[i];

el.removeEventListener('click');
el.addEventListener('click', function(e) {
e.preventDefault();
addOrgModal();
});
}
}

/* Show the "add organisation" modal dialog */
function addOrgModal() {

/* Remove the modal */
function cancelModal() {
var modal = document.querySelector('.modalParent');
modal.parentNode.removeChild(modal);
}

/* Submit the new org */
function submitModal() {
if (allInputsFilled()) {
var api_url, request, form, form_data, reporting_org_id;
// Add organisation to DB
form = document.querySelector('#addOrganisation');

form_data = serialize(form);
form_data = form_data.replace('iati_org_id=&', '');

reporting_org_id = document.querySelector('#reportingOrganisation').getAttributeNode("value").value;
form_data += '&content_owner=' + reporting_org_id;

api_url = '/rest/v1/organisation/?format=json';

request = new XMLHttpRequest();
request.open('POST', api_url, true);
request.setRequestHeader("X-CSRFToken", csrftoken);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

request.onload = function() {
if (request.status === 201) {

// This flag forces the fetching of a fresh API response
var forceReloadOrg = true;

updateTypeaheads(forceReloadOrg);
cancelModal();
} else if (request.status === 400) {
var response;
response = JSON.parse(request.responseText);

for (var key in response) {
if (response.hasOwnProperty(key)) {
var input = form.querySelector('#' + key);
var inputParent = input.parentNode;
var inputHelp = inputParent.querySelector('.help-block');
inputHelp.textContent = response[key];
elAddClass(inputHelp, 'help-block-error');
elAddClass(inputParent, 'has-error');
}
}
return false;
} else {
elAddClass(form, 'has-error');
return false;
}
};

request.onerror = function() {
// There was a connection error of some sort
elAddClass(form, 'has-error');
return false;
};

request.send(form_data);
}
}

function allInputsFilled() {
var allInputsFilledBoolean = true;
var shortName = document.querySelector('#name');
var shortNameHelp = document.querySelector('#name + label + .help-block');
var shortNameContainer = document.querySelector('.inputContainer.newOrgName');
var longName = document.querySelector('#long_name');
var longNameHelp = document.querySelector('#long_name + label + .help-block');
var longNameContainer = document.querySelector('.inputContainer.newOrgLongName');

if (shortName.value === '') {
shortNameHelp.textContent = defaultValues.blank_name;
elAddClass(shortNameHelp, 'help-block-error');
elAddClass(shortNameContainer, 'has-error');
allInputsFilledBoolean = false;
} else {
shortNameHelp.textContent = '';
elRemoveClass(shortNameHelp, 'help-block-error');
elRemoveClass(shortNameContainer, 'has-error');
}

if (longName.value === '') {
longNameHelp.textContent = defaultValues.blank_long_name;
elAddClass(longNameHelp, 'help-block-error');
elAddClass(longNameContainer, 'has-error');
allInputsFilledBoolean = false;
} else {
longNameHelp.textContent = '';
elRemoveClass(longNameHelp, 'help-block-error');
elRemoveClass(longNameContainer, 'has-error');
}

return allInputsFilledBoolean;
}

Modal = React.createClass({displayName: 'Modal',
render: function() {
return (
React.DOM.div( {className:"modalParent"},
React.DOM.div( {className:"modalBackground"}
),
React.DOM.div( {className:"modalContainer"},
React.DOM.div( {className:"orgModal"},
React.DOM.div( {className:"modalContents projectEdit"},
React.DOM.h4(null, defaultValues.add_new_organisation),
React.DOM.form( {id:"addOrganisation"},
React.DOM.div( {className:"row"},
React.DOM.div( {id:"addOrgGeneralError", className:"col-md-12"})
),
React.DOM.div( {className:"row"},
React.DOM.div( {className:"inputContainer newOrgName col-md-6"},
React.DOM.input( {name:"name", id:"name", type:"text", className:"form-control", maxLength:"25"}),
React.DOM.label( {htmlFor:"newOrgName", className:"control-label"}, defaultValues.name,React.DOM.span( {className:"mandatory"}, "*")),
React.DOM.p( {className:"help-block"}, defaultValues.max, " 25 ", defaultValues.characters)
),
React.DOM.div( {className:"inputContainer newOrgLongName col-md-6"},
React.DOM.input( {name:"long_name", id:"long_name", type:"text", className:"form-control", maxLength:"75"}),
React.DOM.label( {htmlFor:"newOrgLongName", className:"control-label"}, defaultValues.long_name,React.DOM.span( {className:"mandatory"}, "*")),
React.DOM.p( {className:"help-block"}, defaultValues.max, " 75 ", defaultValues.characters)
)
),
React.DOM.div( {className:"row"},
React.DOM.div( {className:"inputContainer newOrgIatiId col-md-6"},
React.DOM.input( {name:"iati_org_id", id:"iati_org_id", type:"text", className:"form-control", maxLength:"75"}),
React.DOM.label( {htmlFor:"newOrgIatiId", className:"control-label"}, defaultValues.iati_org_id),
React.DOM.p( {className:"help-block"}, defaultValues.max, " 75 ", defaultValues.characters)
),
React.DOM.div( {className:"IATIOrgTypeContainer inputContainer col-md-6"},
React.DOM.select( {name:"new_organisation_type", id:"newOrgIATIType", className:"form-control", value:"22"},
React.DOM.option( {value:"10"}, "10 - ", defaultValues.government),
React.DOM.option( {value:"15"}, "15 - ", defaultValues.other_public_sector),
React.DOM.option( {value:"21"}, "21 - ", defaultValues.international_ngo),
React.DOM.option( {value:"22"}, "22 - ", defaultValues.national_ngo),
React.DOM.option( {value:"23"}, "23 - ", defaultValues.regional_ngo),
React.DOM.option( {value:"30"}, "30 - ", defaultValues.public_private_partnership),
React.DOM.option( {value:"40"}, "40 - ", defaultValues.multilateral),
React.DOM.option( {value:"60"}, "60 - ", defaultValues.foundation),
React.DOM.option( {value:"70"}, "70 - ", defaultValues.private_sector),
React.DOM.option( {value:"80"}, "80 - ", defaultValues.academic_training_research)
),
React.DOM.label( {htmlFor:"newOrgIATIType", className:"control-label"}, defaultValues.org_type,React.DOM.span( {className:"mandatory"}, "*")),
React.DOM.p( {className:"help-block"})
)
),
React.DOM.div( {className:"row"},
React.DOM.div( {className:"descriptionContainer inputContainer col-md-12"},
React.DOM.label( {className:"control-label", htmlFor:"description"}, defaultValues.description),
React.DOM.textarea( {id:"description", className:"form-control", name:"description", rows:"3"}),
React.DOM.p( {className:"help-block"})
)
)
),
React.DOM.div( {className:"controls"},
React.DOM.button( {className:"modal-cancel btn btn-danger",
onClick:cancelModal},
React.DOM.span( {className:"glyphicon glyphicon-trash"}), " ", defaultValues.cancel
),
React.DOM.button( {className:"modal-save btn btn-success",
onClick:submitModal},
React.DOM.span( {className:"glyphicon glyphicon-plus"}), " ", defaultValues.add_new_organisation
)
)
)
)
)
)
);
}
});

React.render(
Modal(null ),

// Use the footer to prevent page scroll on injection
document.querySelector('footer')
);
}

/* General Helper Functions */

function elHasClass(el, className) {
Expand Down Expand Up @@ -2168,4 +2380,5 @@ document.addEventListener('DOMContentLoaded', function() {
setAllSectionsCompletionPercentage();
setAllSectionsChangeListerner();
setPageCompletionPercentage();
setModalOnClicks();
});
Loading

0 comments on commit b4492e3

Please sign in to comment.