Skip to content

Commit

Permalink
Merge pull request #829 from akvo/feature/822_v3_cleanup_jsx
Browse files Browse the repository at this point in the history
[#822 #824] Updated 'delete user' to 'remove organisation link of user'
  • Loading branch information
kardan committed Oct 22, 2014
2 parents 4d60da7 + ad0a86c commit b07de16
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 32 deletions.
1 change: 1 addition & 0 deletions akvo/rest/serializers/employment.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ class EmploymentSerializer(BaseRSRSerializer):
organisation_name = serializers.Field(source='organisation.name')
country_name = serializers.Field(source='country.name')


class Meta:
model = Employment
4 changes: 4 additions & 0 deletions akvo/rest/views/employment.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# 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 >.

from rest_framework import filters

from akvo.rsr.models import Employment

Expand All @@ -16,3 +17,6 @@ class EmploymentViewSet(BaseRSRViewSet):
"""
queryset = Employment.objects.all()
serializer_class = EmploymentSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('user', 'organisation',)

2 changes: 1 addition & 1 deletion akvo/rest/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ class BaseRSRViewSet(viewsets.ModelViewSet):
Base class used for the view sets for RSR models. Provides unified auth and perms settings.
"""
authentication_classes = (SessionAuthentication, TastyTokenAuthentication)
permission_classes = (RSRModelPermissions,)
permission_classes = (RSRModelPermissions,)
170 changes: 170 additions & 0 deletions akvo/rsr/static/rsr/v3/js/src/react-user-management.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/** @jsx React.DOM */

var Modal = ReactBootstrap.Modal;
var ModalTrigger = ReactBootstrap.ModalTrigger;
var Button = ReactBootstrap.Button;
var Table = ReactBootstrap.Table;

var ConfirmModal = React.createClass({displayName: 'ConfirmModal',
deleteEmployment: function() {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}

function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

var csrftoken = getCookie('csrftoken');
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});

$.ajax({
type: "DELETE",
url: "/rest/v1/employment/" + this.props.employment.id + '/?format=json',
success: function(data) {
this.handleDelete();
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},

handleDelete: function() {
this.props.onDeleteToggle();
},

render: function() {
return this.transferPropsTo(
Modal({title: "Remove link to organisation"},
React.DOM.div({className: "modal-body"},
'Are you sure you want to remove the link to this organisation: ' + this.props.employment.organisation_name + '?'
),
React.DOM.div({className: "modal-footer"},
Button({onClick: this.props.onRequestHide}, "Close"),
Button({onClick: this.deleteEmployment, bsStyle: "danger"}, "Remove")
)
)
);
}
});

var TriggerConfirmModal = React.createClass({displayName: 'TriggerConfirmModal',
render: function () {
return (
ModalTrigger({modal: ConfirmModal({employment: this.props.employment, onDeleteToggle: this.props.onDeleteToggle})},
Button({bsStyle: "danger", bsSize: "xsmall"}, "X")
)
);
}
});

var Employment = React.createClass({displayName: 'Employment',
getInitialState: function() {
return {visible: true};
},

onDelete: function() {
this.setState({visible: false});
},

render: function() {
return this.state.visible
? React.DOM.li(null, this.props.employment.organisation_name, " ", TriggerConfirmModal({employment: this.props.employment, onDeleteToggle: this.onDelete}))
: React.DOM.span(null);
}
});

var EmploymentList = React.createClass({displayName: 'EmploymentList',
getInitialState: function() {
return { employments: [] };
},

componentDidMount: function() {
$.get(this.props.source, function(result) {
var employments = result.results;
if (this.isMounted()) {
this.setState({
employments: employments
});
}
}.bind(this));
},

render: function () {
var employments = this.state.employments.map(function(employment) {
return (
Employment({employment: employment})
)
});
return (
React.DOM.ul(null, employments)
);
}
});

var UserRow = React.createClass({displayName: 'UserRow',
render: function() {
return (
React.DOM.tr(null,
React.DOM.td(null, this.props.user.email),
React.DOM.td(null, this.props.user.first_name),
React.DOM.td(null, this.props.user.last_name),
React.DOM.td(null, EmploymentList({source: "/rest/v1/employment/?format=json&user=" + this.props.user.id})),
React.DOM.td(null, React.DOM.i(null, "to do"))
)
);
}
});

var UserTable = React.createClass({displayName: 'UserTable',
getInitialState: function() {
return { users: [] };
},

componentDidMount: function() {
$.get(this.props.source, function(result) {
var users = result.results;
if (this.isMounted()) {
this.setState({
users: users
});
}
}.bind(this));
},

render: function() {
var users = this.state.users.map(function(user) {
return (
UserRow({user: user})
)
});
return (
Table({striped: true},
React.DOM.thead(null, React.DOM.tr(null, React.DOM.th(null, "Email"), React.DOM.th(null, "First name"), React.DOM.th(null, "Last name"), React.DOM.th(null, "Organisations"), React.DOM.th(null, "Permissions"))),
React.DOM.tbody(null, users)
)
);
}
});

React.renderComponent(UserTable({source: "/rest/v1/user/?format=json"}), document.getElementById('user_table'));
76 changes: 49 additions & 27 deletions .../rsr/static/rsr/v3/js/src/react-modal.jsx → ...c/rsr/v3/js/src/react-user-management.jsx
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var Button = ReactBootstrap.Button;
var Table = ReactBootstrap.Table;

var ConfirmModal = React.createClass({
deleteUser: function() {
deleteEmployment: function() {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
Expand Down Expand Up @@ -39,7 +39,7 @@ var ConfirmModal = React.createClass({

$.ajax({
type: "DELETE",
url: "/rest/v1/user/" + this.props.user.id + '/?format=json',
url: "/rest/v1/employment/" + this.props.employment.id + '/?format=json',
success: function(data) {
this.handleDelete();
}.bind(this),
Expand All @@ -55,13 +55,13 @@ var ConfirmModal = React.createClass({

render: function() {
return this.transferPropsTo(
<Modal title="Delete user">
<Modal title="Remove link to organisation">
<div className="modal-body">
{'Are you sure you want to delete user: ' + this.props.user.first_name + ' ' + this.props.user.last_name + '?'}
{'Are you sure you want to remove the link to this organisation: ' + this.props.employment.organisation_name + '?'}
</div>
<div className="modal-footer">
<Button onClick={this.props.onRequestHide}>Close</Button>
<Button onClick={this.deleteUser} bsStyle="danger">Delete user</Button>
<Button onClick={this.deleteEmployment} bsStyle="danger">Remove</Button>
</div>
</Modal>
);
Expand All @@ -71,46 +71,68 @@ var ConfirmModal = React.createClass({
var TriggerConfirmModal = React.createClass({
render: function () {
return (
<ModalTrigger modal={<ConfirmModal user={this.props.user} onDeleteToggle={this.props.onDeleteToggle} />}>
<Button bsStyle="danger">X</Button>
<ModalTrigger modal={<ConfirmModal employment={this.props.employment} onDeleteToggle={this.props.onDeleteToggle} />}>
<Button bsStyle="danger" bsSize="xsmall">X</Button>
</ModalTrigger>
);
}
});

var OrganisationList = React.createClass({
var Employment = React.createClass({
getInitialState: function() {
return {visible: true};
},

onDelete: function() {
this.setState({visible: false});
},

render: function() {
return this.state.visible
? <li>{this.props.employment.organisation_name} <TriggerConfirmModal employment={this.props.employment} onDeleteToggle={this.onDelete} /></li>
: <span/>;
}
});

var EmploymentList = React.createClass({
getInitialState: function() {
return { employments: [] };
},

componentDidMount: function() {
$.get(this.props.source, function(result) {
var employments = result.results;
if (this.isMounted()) {
this.setState({
employments: employments
});
}
}.bind(this));
},

render: function () {
var organisations = this.props.organisations.map(function(org) {
var employments = this.state.employments.map(function(employment) {
return (
<li>{org.name}</li>
<Employment employment={employment}/>
)
});
return (
<ul>{organisations}</ul>
<ul>{employments}</ul>
);
}
});

var UserRow = React.createClass({
getInitialState: function() {
return {visible: true};
},

onDelete: function() {
this.setState({visible: false});
},

render: function() {
return this.state.visible
? <tr>
return (
<tr>
<td>{this.props.user.email}</td>
<td>{this.props.user.first_name}</td>
<td>{this.props.user.last_name}</td>
<td><OrganisationList organisations={this.props.user.organisations} /></td>
<td><i>To do</i></td>
<td><TriggerConfirmModal user={this.props.user} onDeleteToggle={this.onDelete} /></td>
</tr>
: <tr><td><i>User Deleted</i></td><td></td><td></td><td></td><td></td><td></td></tr>;
<td><EmploymentList source={"/rest/v1/employment/?format=json&user=" + this.props.user.id} /></td>
<td><i>to do</i></td>
</tr>
);
}
});

Expand Down Expand Up @@ -138,7 +160,7 @@ var UserTable = React.createClass({
});
return (
<Table striped>
<thead><tr><th>Email</th><th>First name</th><th>Last name</th><th>Organisation</th><th>Permissions</th><th>Delete</th></tr></thead>
<thead><tr><th>Email</th><th>First name</th><th>Last name</th><th>Organisations</th><th>Permissions</th></tr></thead>
<tbody>{users}</tbody>
</Table>
);
Expand Down
Empty file modified akvo/rsr/static/rsr/v3/js/src/scripts.js
100644 → 100755
Empty file.
3 changes: 3 additions & 0 deletions akvo/settings/30-rsr.conf
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ REST_FRAMEWORK = {
),
# Harmonize datetime format across serializer formats
'DATETIME_FORMAT': 'iso-8601',
'DEFAULT_FILTER_BACKENDS': (
'rest_framework.filters.DjangoFilterBackend',
),
}

# django-rest-swagger settings
Expand Down
6 changes: 3 additions & 3 deletions akvo/settings/40-pipeline.conf
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,11 @@ PIPELINE_JS = {
),
'output_filename': 'rsr/v3/js/build/rsr_v3_libraries.min.js',
},
'rsr_v3_react': {
'rsr_v3_react_user_management': {
'source_filenames': (
'rsr/v3/js/src/react-modal.jsx',
'rsr/v3/js/src/react-user-management.jsx',
),
'output_filename': 'rsr/v3/js/build/rsr_v3_react.min.js',
'output_filename': 'rsr/v3/js/build/rsr_v3_user_management.min.js',
},


Expand Down
2 changes: 1 addition & 1 deletion akvo/templates/myrsr/user_management.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
{% block myrsr_main %}
<h1>User management</h1>
<div id="user_table"></div>
<script type="application/javascript" src="/static/rsr/v3/js/build/rsr_v3_react.min.js" charset="utf-8"></script>
<script type="application/javascript" src="/static/rsr/v3/js/build/rsr_v3_user_management.min.js" charset="utf-8"></script>
{% endblock %}

0 comments on commit b07de16

Please sign in to comment.