diff --git a/akvo/rsr/models/project_update.py b/akvo/rsr/models/project_update.py index b20cdb0b21..1f7e5bea25 100644 --- a/akvo/rsr/models/project_update.py +++ b/akvo/rsr/models/project_update.py @@ -82,26 +82,6 @@ def img(self, value=''): return value img.allow_tags = True - def edit_window_has_expired(self): - """Determine whether or not update timeout window has expired. - The timeout is controlled by settings.PROJECT_UPDATE_TIMEOUT and - defaults to 30 minutes. - """ - return (datetime.now() - self.created_at) > self.edit_timeout - - @property - def expires_at(self): - return to_gmt(self.created_at + self.edit_timeout) - - @property - def edit_timeout(self): - timeout_minutes = getattr(settings, 'PROJECT_UPDATE_TIMEOUT', 30) - return timedelta(minutes=timeout_minutes) - - @property - def edit_time_remaining(self): - return self.edit_timeout - self.created_at - @property def time_gmt(self): return to_gmt(self.created_at) diff --git a/akvo/rsr/static/scripts-src/my-updates.js b/akvo/rsr/static/scripts-src/my-updates.js new file mode 100644 index 0000000000..7753b56d57 --- /dev/null +++ b/akvo/rsr/static/scripts-src/my-updates.js @@ -0,0 +1,154 @@ +// 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 >. + +// DEFAULT VALUES +var defaultValues = JSON.parse(document.getElementById("default-values").innerHTML); + +// CSRF TOKEN +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 = cookies[i].trim(); + // 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; +} + +var csrftoken = getCookie('csrftoken'); + +// display error message in empty div below title +function setError(message) { + var errorNode = document.getElementById('projectUpdateError'); + errorNode.innerHTML = message; +} + +// display confirmation message before deleting update +function confirmDeleteUpdate(node) { + return function(e) { + e.preventDefault(); + + // check if delete button is enabled + if ((' ' + node.className + ' ').indexOf(' disabled ') == -1) { + + var updateId = node.id.split('-')[1]; + var confirmId = 'confirm-delete-' + updateId; + + var confirmNode = document.getElementById(confirmId); + node.setAttribute('class', 'delete-update disabled'); + + var sureNode = document.createElement('span'); + sureNode.innerHTML = defaultValues.sure_message; + + var yesNode = document.createElement('a'); + yesNode.setAttribute('style', 'color: green; margin-left: 5px;'); + yesNode.onclick = confirmDelete(yesNode, updateId); + yesNode.innerHTML = defaultValues.yes; + + var noNode = document.createElement('a'); + noNode.setAttribute('style', 'color: red; margin-left: 5px;'); + noNode.onclick = dismissConfirmationNo(sureNode, updateId); + noNode.innerHTML = defaultValues.no; + + sureNode.appendChild(yesNode); + sureNode.appendChild(noNode); + confirmNode.appendChild(sureNode); + + } + + }; +} + +function dismissConfirmationNo(sureNode, updateId) { + return function(e) { + e.preventDefault(); + + dismissConfirmation(sureNode, updateId); + }; +} + +function dismissConfirmation(sureNode, updateId) { + var parentNode = sureNode.parentNode; + parentNode.removeChild(sureNode); + + var updateNodeId = 'update-' + updateId; + deleteButtonNode = document.getElementById(updateNodeId); + deleteButtonNode.setAttribute('class', 'delete-update'); +} + +function confirmDelete(yesNode, updateId) { + return function(e) { + e.preventDefault(); + var sureNode = yesNode.parentNode; + sureNode.innerHTML = defaultValues.delete_progress; + // var parentNode = sureNode.parentNode; + // parentNode.removeChild(sureNode); + + deleteUpdate(sureNode, updateId); + }; +} + +// make api call to delete specified update +function deleteUpdate(sureNode, updateId) { + + var api_url = '/rest/v1/project_update/' + updateId + '/?format=json', + request = new XMLHttpRequest(); + + request.open('DELETE', api_url, true); + request.setRequestHeader("X-CSRFToken", csrftoken); + request.setRequestHeader("Content-type", "application/json"); + + request.onload = function() { + if (request.status >= 204 && request.status < 300) { + // Successfully deleted update, remove container. + removeUpdateContainer(updateId); + } else if (request.status == 404) { + // Update not found, most likely already deleted + dismissConfirmation(sureNode, updateId); + setError(defaultValues.error_delete); + } else { + // We reached our target server, but it returned an error + dismissConfirmation(sureNode, updateId); + setError(request.status + defaultValues.error_misc); + } + }; + + request.onerror = function() { + // There was a connection error of some sort + setError(defaultValues.error_connection); + return false; + }; + + request.send(); +} + +// remove update from screen once it has been delete +function removeUpdateContainer(updateId) { + var nodeId = 'update-' + updateId + '-container'; + var removeNode = document.getElementById(nodeId); + var parentNode = removeNode.parentNode; + parentNode.removeChild(removeNode); +} + +// add onlick to all delete buttons +function setDeleteUpdateOnClick() { + var deleteUpdateNodes = document.querySelectorAll('.delete-update'); + + if (deleteUpdateNodes !== null) { + for (var i = 0; i < deleteUpdateNodes.length; i++) { + deleteUpdateNodes[i].onclick = confirmDeleteUpdate(deleteUpdateNodes[i]); + } + } +} + +document.addEventListener('DOMContentLoaded', function() { + setDeleteUpdateOnClick(); +}); diff --git a/akvo/rsr/static/styles-src/main.css b/akvo/rsr/static/styles-src/main.css index 0433f2d526..95fae83187 100755 --- a/akvo/rsr/static/styles-src/main.css +++ b/akvo/rsr/static/styles-src/main.css @@ -625,6 +625,32 @@ nav.navbar-fixed-top { position: relative; right: auto; } } +div.updateListMenu ul { + margin-top: -10px; } + div.updateListMenu ul li { + display: inline-block; } + @media only screen and (max-width: 992px) { + div.updateListMenu ul li { + display: block; } } + div.updateListMenu ul li a:last-child { + margin-right: 0; } + div.updateListMenu ul li a.disabled { + background-color: #e6e6e6; + color: #cccccc; + pointer-events: initial; + cursor: not-allowed; + border: none; + -moz-border-radius: 5px; + -o-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; } + div.updateListMenu ul li a.disabled:hover { + text-decoration: none; } + +div.update-confirm-container { + margin-top: 5px; + font-size: 85%; } + .navbar-form { padding-left: 0; margin-bottom: 25px; } diff --git a/akvo/rsr/static/styles-src/main.scss b/akvo/rsr/static/styles-src/main.scss index e0eeb8ef02..60d718dd2b 100755 --- a/akvo/rsr/static/styles-src/main.scss +++ b/akvo/rsr/static/styles-src/main.scss @@ -688,6 +688,42 @@ nav.navbar-fixed-top { } } +div.updateListMenu { + ul { + margin-top: -10px; + li { + display: inline-block; + @include responsive (mediums-max-screens) { + display: block; + } + @include responsive (small-max-screens) {} + a { + &:last-child { + margin-right: 0; + } + &.disabled { + background-color: lighten($primary4, 30%); + color: lighten($primary4, 20%); + pointer-events: initial; + cursor: not-allowed; + border: none; + @include border-radius(5px); + &:hover { + text-decoration: none; + } + } + } + @include responsive (wide-screens) {} + > a {} + } + } +} + +div.update-confirm-container { + margin-top: 5px; + font-size: 85%; +} + .navbar-form { padding-left: 0; margin-bottom: 25px; diff --git a/akvo/rsr/views/project.py b/akvo/rsr/views/project.py index bf18092ded..72bd31734f 100644 --- a/akvo/rsr/views/project.py +++ b/akvo/rsr/views/project.py @@ -420,10 +420,6 @@ def set_update(request, project_id, edit_mode=False, form_class=ProjectUpdateFor request.error_message = u'You can only edit your own updates.' raise PermissionDenied - if update.edit_window_has_expired(): - request.error_message = u'You cannot edit this update anymore, the 30 minutes time limit has passed.' - raise PermissionDenied - if request.method == 'POST': updateform = form_class(request.POST, request.FILES, instance=update) if updateform.is_valid(): diff --git a/akvo/settings/40-pipeline.conf b/akvo/settings/40-pipeline.conf index 9cd4c78106..00a69d21f6 100644 --- a/akvo/settings/40-pipeline.conf +++ b/akvo/settings/40-pipeline.conf @@ -197,6 +197,12 @@ PIPELINE_JS = { ), 'output_filename': 'scripts/my-results-select.min.js', }, + 'my_updates': { + 'source_filenames': ( + 'scripts-src/my-updates.js', + ), + 'output_filename': 'scripts/my-updates.min.js', + }, 'more_partners': { 'source_filenames': ( 'scripts-src/more-partners.js', diff --git a/akvo/templates/myrsr/my_updates.html b/akvo/templates/myrsr/my_updates.html index fb281d63ed..f6f3ad493f 100644 --- a/akvo/templates/myrsr/my_updates.html +++ b/akvo/templates/myrsr/my_updates.html @@ -1,12 +1,13 @@ {% extends "myrsr/myrsr_base.html" %} -{% load i18n bootstrap3 rsr_utils %} +{% load i18n bootstrap3 rsr_utils compressed %} {% block title %}{% trans 'MyRSR - My updates' %}{% endblock %} {% block myrsr_main %}

{% trans 'My updates' %}

+