mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-05 19:41:41 +00:00
Adding bulk deletion endpoint for notifications (#3154)
* Catch DoesNotExist error * Move notificationtable function to js file * Fix for custom metadata class - Previously only worked if a POST or PUT action was available on the endpoint - So, a ListAPIView endpoint would not actually work! - Adding in a BulkDelete mixin to a ListAPIView caused failure * Add unit test to ensure new OPTIONS metadata updates are checked * Expand functionality of the existing BulkDelete mixin - Allow deletion by custom filters - Allow each implementing class to implement custom filters - Adds more unit testing for BulkDelete mixin class * Add bulk delete operation for Notification API - Ensure users can only delete their *own* notifications * Improve notification tables / buttons / etc * Adds unit testing for bulk delete of notifications - Fixed API permissions for notifications list endpoint * Update BulkDelete operations for the StockItemTestResult table * Use filters parameter in attachments table to ensure that only correct attachments are deleted * JS linting * Fixes for unit tests
This commit is contained in:
@@ -10,15 +10,22 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
<div class='btn btn-secondary' type='button' id='history-refresh' title='{% trans "Refresh Notification History" %}'>
|
||||
<span class='fa fa-sync'></span> {% trans "Refresh Notification History" %}
|
||||
<div class='btn btn-danger' type='button' id='history-delete' title='{% trans "Delete Notifications" %}'>
|
||||
<span class='fas fa-trash-alt'></span> {% trans "Delete Notifications" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id='history-buttons'>
|
||||
<div class='btn-group' role='group'>
|
||||
{% include "filter_list.html" with id="notifications-history" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<table class='table table-striped table-condensed' id='history-table'>
|
||||
|
||||
<table class='table table-striped table-condensed' id='history-table' data-toolbar='#history-buttons'>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
@@ -13,15 +13,18 @@
|
||||
<div class='btn btn-outline-secondary' type='button' id='mark-all' title='{% trans "Mark all as read" %}'>
|
||||
<span class='fa fa-bookmark'></span> {% trans "Mark all as read" %}
|
||||
</div>
|
||||
<div class='btn btn-secondary' type='button' id='inbox-refresh' title='{% trans "Refresh Pending Notifications" %}'>
|
||||
<span class='fa fa-sync'></span> {% trans "Refresh Pending Notifications" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id='inbox-buttons'>
|
||||
<div class='btn-group' role='group'>
|
||||
{% include "filter_list.html" with id="notifications-inbox" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<table class='table table-striped table-condensed' id='inbox-table'>
|
||||
<table class='table table-striped table-condensed' id='inbox-table' data-toolbar='#inbox-buttons'>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
@@ -29,83 +29,6 @@ function updateNotificationTables() {
|
||||
// this allows the global notification panel to update the tables
|
||||
window.updateNotifications = updateNotificationTables
|
||||
|
||||
function loadNotificationTable(table, options={}, enableDelete=false) {
|
||||
|
||||
var params = options.params || {};
|
||||
var read = typeof(params.read) === 'undefined' ? true : params.read;
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: options.url,
|
||||
name: options.name,
|
||||
groupBy: false,
|
||||
search: true,
|
||||
queryParams: {
|
||||
ordering: 'age',
|
||||
read: read,
|
||||
},
|
||||
paginationVAlign: 'bottom',
|
||||
formatNoMatches: options.no_matches,
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
title: '{% trans "ID" %}',
|
||||
visible: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'age',
|
||||
title: '{% trans "Age" %}',
|
||||
sortable: 'true',
|
||||
formatter: function(value, row) {
|
||||
return row.age_human
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
title: '{% trans "Category" %}',
|
||||
sortable: 'true',
|
||||
},
|
||||
{
|
||||
field: 'target',
|
||||
title: '{% trans "Item" %}',
|
||||
sortable: 'true',
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var html = `${value.model}: ${value.name}`;
|
||||
if (value.link ) {html = `<a href='${value.link}'>${html}</a>`;}
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '{% trans "Name" %}',
|
||||
},
|
||||
{
|
||||
field: 'message',
|
||||
title: '{% trans "Message" %}',
|
||||
},
|
||||
{
|
||||
formatter: function(value, row, index, field) {
|
||||
var bRead = getReadEditButton(row.pk, row.read)
|
||||
if (enableDelete) {
|
||||
var bDel = "<button title='{% trans "Delete Notification" %}' class='notification-delete btn btn-outline-secondary' type='button' pk='" + row.pk + "'><span class='fas fa-trash-alt icon-red'></span></button>";
|
||||
} else {
|
||||
var bDel = '';
|
||||
}
|
||||
var html = "<div class='btn-group float-right' role='group'>" + bRead + bDel + "</div>";
|
||||
return html;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
$(table).on('click', '.notification-read', function() {
|
||||
updateNotificationReadState($(this));
|
||||
});
|
||||
}
|
||||
|
||||
loadNotificationTable("#inbox-table", {
|
||||
name: 'inbox',
|
||||
@@ -116,10 +39,6 @@ loadNotificationTable("#inbox-table", {
|
||||
no_matches: function() { return '{% trans "No unread notifications found" %}'; },
|
||||
});
|
||||
|
||||
$("#inbox-refresh").on('click', function() {
|
||||
$("#inbox-table").bootstrapTable('refresh');
|
||||
});
|
||||
|
||||
$("#mark-all").on('click', function() {
|
||||
inventreeGet(
|
||||
'{% url "api-notifications-readall" %}',
|
||||
@@ -140,8 +59,31 @@ loadNotificationTable("#history-table", {
|
||||
no_matches: function() { return '{% trans "No notification history found" %}'; },
|
||||
}, true);
|
||||
|
||||
$("#history-refresh").on('click', function() {
|
||||
$("#history-table").bootstrapTable('refresh');
|
||||
|
||||
$('#history-delete').click(function() {
|
||||
|
||||
var html = `
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "Delete all read notifications" %}
|
||||
</div>`;
|
||||
|
||||
// Perform a bulk delete of all 'read' notifications for this user
|
||||
constructForm(
|
||||
'{% url "api-notifications-list" %}',
|
||||
{
|
||||
method: 'DELETE',
|
||||
preFormContent: html,
|
||||
title: '{% trans "Delete Notifications" %}',
|
||||
onSuccess: function() {
|
||||
$('#history-table').bootstrapTable('refresh');
|
||||
},
|
||||
form_data: {
|
||||
filters: {
|
||||
read: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#history-table").on('click', '.notification-delete', function() {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<div id='attachment-buttons'>
|
||||
<div class='btn-group' role='group'>
|
||||
<div class='btn-group'>
|
||||
<button class='btn btn-primary dropdown-toggle' type='buton' data-bs-toggle='dropdown' title='{% trans "Actions" %}'>
|
||||
<button class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle='dropdown' title='{% trans "Actions" %}'>
|
||||
<span class='fas fa-tools'></span> <span class='caret'></span>
|
||||
</button>
|
||||
<ul class='dropdown-menu'>
|
||||
|
@@ -113,6 +113,7 @@ function deleteAttachments(attachments, url, options={}) {
|
||||
preFormContent: html,
|
||||
form_data: {
|
||||
items: ids,
|
||||
filters: options.filters,
|
||||
},
|
||||
onSuccess: function() {
|
||||
// Refresh the table once all attachments are deleted
|
||||
@@ -128,6 +129,9 @@ function reloadAttachmentTable() {
|
||||
}
|
||||
|
||||
|
||||
/* Load a table of attachments against a specific model.
|
||||
* Note that this is a 'generic' table which is used for multiple attachment model classes
|
||||
*/
|
||||
function loadAttachmentTable(url, options) {
|
||||
|
||||
var table = options.table || '#attachment-table';
|
||||
@@ -141,7 +145,7 @@ function loadAttachmentTable(url, options) {
|
||||
var attachments = getTableData(table);
|
||||
|
||||
if (attachments.length > 0) {
|
||||
deleteAttachments(attachments, url);
|
||||
deleteAttachments(attachments, url, options);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -182,7 +186,7 @@ function loadAttachmentTable(url, options) {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var attachment = $(table).bootstrapTable('getRowByUniqueId', pk);
|
||||
deleteAttachments([attachment], url);
|
||||
deleteAttachments([attachment], url, options);
|
||||
});
|
||||
},
|
||||
columns: [
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* exported
|
||||
loadNotificationTable,
|
||||
showAlertOrCache,
|
||||
showCachedAlerts,
|
||||
startNotificationWatcher,
|
||||
@@ -9,6 +10,96 @@
|
||||
closeNotificationPanel,
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Load notification table
|
||||
*/
|
||||
function loadNotificationTable(table, options={}, enableDelete=false) {
|
||||
|
||||
var params = options.params || {};
|
||||
var read = typeof(params.read) === 'undefined' ? true : params.read;
|
||||
|
||||
setupFilterList(`notifications-${options.name}`, table);
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: options.url,
|
||||
name: options.name,
|
||||
groupBy: false,
|
||||
search: true,
|
||||
queryParams: {
|
||||
ordering: 'age',
|
||||
read: read,
|
||||
},
|
||||
paginationVAlign: 'bottom',
|
||||
formatNoMatches: options.no_matches,
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
title: '{% trans "ID" %}',
|
||||
visible: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'age',
|
||||
title: '{% trans "Age" %}',
|
||||
sortable: 'true',
|
||||
formatter: function(value, row) {
|
||||
return row.age_human;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
title: '{% trans "Category" %}',
|
||||
sortable: 'true',
|
||||
},
|
||||
{
|
||||
field: 'target',
|
||||
title: '{% trans "Item" %}',
|
||||
sortable: 'true',
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var html = `${value.model}: ${value.name}`;
|
||||
if (value.link ) {
|
||||
html = `<a href='${value.link}'>${html}</a>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '{% trans "Name" %}',
|
||||
},
|
||||
{
|
||||
field: 'message',
|
||||
title: '{% trans "Message" %}',
|
||||
},
|
||||
{
|
||||
formatter: function(value, row, index, field) {
|
||||
var bRead = getReadEditButton(row.pk, row.read);
|
||||
|
||||
if (enableDelete) {
|
||||
var bDel = `<button title='{% trans "Delete Notification" %}' class='notification-delete btn btn-outline-secondary' type='button' pk='${row.pk}'><span class='fas fa-trash-alt icon-red'></span></button>`;
|
||||
} else {
|
||||
var bDel = '';
|
||||
}
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>${bRead}${bDel}</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
$(table).on('click', '.notification-read', function() {
|
||||
updateNotificationReadState($(this));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add a cached alert message to sesion storage
|
||||
*/
|
||||
|
Reference in New Issue
Block a user