|
@@ -1,6 +1,5 @@
|
|
|
import logging
|
|
import logging
|
|
|
import re
|
|
import re
|
|
|
-from collections import defaultdict
|
|
|
|
|
from copy import deepcopy
|
|
from copy import deepcopy
|
|
|
|
|
|
|
|
from django.contrib import messages
|
|
from django.contrib import messages
|
|
@@ -12,11 +11,12 @@ from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
|
|
from django.http import HttpResponse
|
|
from django.http import HttpResponse
|
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
|
from django_tables2.export import TableExport
|
|
from django_tables2.export import TableExport
|
|
|
|
|
+from django.utils.safestring import mark_safe
|
|
|
|
|
|
|
|
from extras.models import ExportTemplate
|
|
from extras.models import ExportTemplate
|
|
|
from extras.signals import clear_webhooks
|
|
from extras.signals import clear_webhooks
|
|
|
from utilities.error_handlers import handle_protectederror
|
|
from utilities.error_handlers import handle_protectederror
|
|
|
-from utilities.exceptions import PermissionsViolation
|
|
|
|
|
|
|
+from utilities.exceptions import AbortRequest, PermissionsViolation
|
|
|
from utilities.forms import (
|
|
from utilities.forms import (
|
|
|
BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, CSVFileField, restrict_form_fields,
|
|
BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, CSVFileField, restrict_form_fields,
|
|
|
)
|
|
)
|
|
@@ -24,6 +24,7 @@ from utilities.htmx import is_htmx
|
|
|
from utilities.permissions import get_permission_for_model
|
|
from utilities.permissions import get_permission_for_model
|
|
|
from utilities.views import GetReturnURLMixin
|
|
from utilities.views import GetReturnURLMixin
|
|
|
from .base import BaseMultiObjectView
|
|
from .base import BaseMultiObjectView
|
|
|
|
|
+from .mixins import ActionsMixin, TableMixin
|
|
|
|
|
|
|
|
__all__ = (
|
|
__all__ = (
|
|
|
'BulkComponentCreateView',
|
|
'BulkComponentCreateView',
|
|
@@ -36,9 +37,9 @@ __all__ = (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
-class ObjectListView(BaseMultiObjectView):
|
|
|
|
|
|
|
+class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
|
|
"""
|
|
"""
|
|
|
- Display multiple objects, all of the same type, as a table.
|
|
|
|
|
|
|
+ Display multiple objects, all the same type, as a table.
|
|
|
|
|
|
|
|
Attributes:
|
|
Attributes:
|
|
|
filterset: A django-filter FilterSet that is applied to the queryset
|
|
filterset: A django-filter FilterSet that is applied to the queryset
|
|
@@ -50,31 +51,10 @@ class ObjectListView(BaseMultiObjectView):
|
|
|
template_name = 'generic/object_list.html'
|
|
template_name = 'generic/object_list.html'
|
|
|
filterset = None
|
|
filterset = None
|
|
|
filterset_form = None
|
|
filterset_form = None
|
|
|
- actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete')
|
|
|
|
|
- action_perms = defaultdict(set, **{
|
|
|
|
|
- 'add': {'add'},
|
|
|
|
|
- 'import': {'add'},
|
|
|
|
|
- 'bulk_edit': {'change'},
|
|
|
|
|
- 'bulk_delete': {'delete'},
|
|
|
|
|
- })
|
|
|
|
|
|
|
|
|
|
def get_required_permission(self):
|
|
def get_required_permission(self):
|
|
|
return get_permission_for_model(self.queryset.model, 'view')
|
|
return get_permission_for_model(self.queryset.model, 'view')
|
|
|
|
|
|
|
|
- def get_table(self, request, bulk_actions=True):
|
|
|
|
|
- """
|
|
|
|
|
- Return the django-tables2 Table instance to be used for rendering the objects list.
|
|
|
|
|
-
|
|
|
|
|
- Args:
|
|
|
|
|
- request: The current request
|
|
|
|
|
- bulk_actions: Show checkboxes for object selection
|
|
|
|
|
- """
|
|
|
|
|
- table = self.table(self.queryset, user=request.user)
|
|
|
|
|
- if 'pk' in table.base_columns and bulk_actions:
|
|
|
|
|
- table.columns.show('pk')
|
|
|
|
|
-
|
|
|
|
|
- return table
|
|
|
|
|
-
|
|
|
|
|
#
|
|
#
|
|
|
# Export methods
|
|
# Export methods
|
|
|
#
|
|
#
|
|
@@ -147,19 +127,14 @@ class ObjectListView(BaseMultiObjectView):
|
|
|
self.queryset = self.filterset(request.GET, self.queryset).qs
|
|
self.queryset = self.filterset(request.GET, self.queryset).qs
|
|
|
|
|
|
|
|
# Determine the available actions
|
|
# Determine the available actions
|
|
|
- actions = []
|
|
|
|
|
- for action in self.actions:
|
|
|
|
|
- if request.user.has_perms([
|
|
|
|
|
- get_permission_for_model(model, name) for name in self.action_perms[action]
|
|
|
|
|
- ]):
|
|
|
|
|
- actions.append(action)
|
|
|
|
|
|
|
+ actions = self.get_permitted_actions(request.user)
|
|
|
has_bulk_actions = any([a.startswith('bulk_') for a in actions])
|
|
has_bulk_actions = any([a.startswith('bulk_') for a in actions])
|
|
|
|
|
|
|
|
if 'export' in request.GET:
|
|
if 'export' in request.GET:
|
|
|
|
|
|
|
|
# Export the current table view
|
|
# Export the current table view
|
|
|
if request.GET['export'] == 'table':
|
|
if request.GET['export'] == 'table':
|
|
|
- table = self.get_table(request, has_bulk_actions)
|
|
|
|
|
|
|
+ table = self.get_table(self.queryset, request, has_bulk_actions)
|
|
|
columns = [name for name, _ in table.selected_columns]
|
|
columns = [name for name, _ in table.selected_columns]
|
|
|
return self.export_table(table, columns)
|
|
return self.export_table(table, columns)
|
|
|
|
|
|
|
@@ -177,12 +152,11 @@ class ObjectListView(BaseMultiObjectView):
|
|
|
|
|
|
|
|
# Fall back to default table/YAML export
|
|
# Fall back to default table/YAML export
|
|
|
else:
|
|
else:
|
|
|
- table = self.get_table(request, has_bulk_actions)
|
|
|
|
|
|
|
+ table = self.get_table(self.queryset, request, has_bulk_actions)
|
|
|
return self.export_table(table)
|
|
return self.export_table(table)
|
|
|
|
|
|
|
|
# Render the objects table
|
|
# Render the objects table
|
|
|
- table = self.get_table(request, has_bulk_actions)
|
|
|
|
|
- table.configure(request)
|
|
|
|
|
|
|
+ table = self.get_table(self.queryset, request, has_bulk_actions)
|
|
|
|
|
|
|
|
# If this is an HTMX request, return only the rendered table HTML
|
|
# If this is an HTMX request, return only the rendered table HTML
|
|
|
if is_htmx(request):
|
|
if is_htmx(request):
|
|
@@ -190,15 +164,13 @@ class ObjectListView(BaseMultiObjectView):
|
|
|
'table': table,
|
|
'table': table,
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- context = {
|
|
|
|
|
|
|
+ return render(request, self.template_name, {
|
|
|
'model': model,
|
|
'model': model,
|
|
|
'table': table,
|
|
'table': table,
|
|
|
'actions': actions,
|
|
'actions': actions,
|
|
|
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
|
|
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
|
|
|
**self.get_extra_context(request),
|
|
**self.get_extra_context(request),
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return render(request, self.template_name, context)
|
|
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
|
|
|
|
|
class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|
class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|
@@ -292,10 +264,10 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|
|
except IntegrityError:
|
|
except IntegrityError:
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- except PermissionsViolation:
|
|
|
|
|
- msg = "Object creation failed due to object-level permissions violation"
|
|
|
|
|
- logger.debug(msg)
|
|
|
|
|
- form.add_error(None, msg)
|
|
|
|
|
|
|
+ except (AbortRequest, PermissionsViolation) as e:
|
|
|
|
|
+ logger.debug(e.message)
|
|
|
|
|
+ form.add_error(None, e.message)
|
|
|
|
|
+ clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
else:
|
|
else:
|
|
|
logger.debug("Form validation failed")
|
|
logger.debug("Form validation failed")
|
|
@@ -420,10 +392,9 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
|
|
except ValidationError:
|
|
except ValidationError:
|
|
|
clear_webhooks.send(sender=self)
|
|
clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
- except PermissionsViolation:
|
|
|
|
|
- msg = "Object import failed due to object-level permissions violation"
|
|
|
|
|
- logger.debug(msg)
|
|
|
|
|
- form.add_error(None, msg)
|
|
|
|
|
|
|
+ except (AbortRequest, PermissionsViolation) as e:
|
|
|
|
|
+ logger.debug(e.message)
|
|
|
|
|
+ form.add_error(None, e.message)
|
|
|
clear_webhooks.send(sender=self)
|
|
clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
else:
|
|
else:
|
|
@@ -570,10 +541,9 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
|
|
|
messages.error(self.request, ", ".join(e.messages))
|
|
messages.error(self.request, ", ".join(e.messages))
|
|
|
clear_webhooks.send(sender=self)
|
|
clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
- except PermissionsViolation:
|
|
|
|
|
- msg = "Object update failed due to object-level permissions violation"
|
|
|
|
|
- logger.debug(msg)
|
|
|
|
|
- form.add_error(None, msg)
|
|
|
|
|
|
|
+ except (AbortRequest, PermissionsViolation) as e:
|
|
|
|
|
+ logger.debug(e.message)
|
|
|
|
|
+ form.add_error(None, e.message)
|
|
|
clear_webhooks.send(sender=self)
|
|
clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
else:
|
|
else:
|
|
@@ -667,10 +637,9 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
|
|
|
messages.success(request, f"Renamed {len(selected_objects)} {model_name}")
|
|
messages.success(request, f"Renamed {len(selected_objects)} {model_name}")
|
|
|
return redirect(self.get_return_url(request))
|
|
return redirect(self.get_return_url(request))
|
|
|
|
|
|
|
|
- except PermissionsViolation:
|
|
|
|
|
- msg = "Object update failed due to object-level permissions violation"
|
|
|
|
|
- logger.debug(msg)
|
|
|
|
|
- form.add_error(None, msg)
|
|
|
|
|
|
|
+ except (AbortRequest, PermissionsViolation) as e:
|
|
|
|
|
+ logger.debug(e.message)
|
|
|
|
|
+ form.add_error(None, e.message)
|
|
|
clear_webhooks.send(sender=self)
|
|
clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
else:
|
|
else:
|
|
@@ -745,11 +714,17 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|
|
if hasattr(obj, 'snapshot'):
|
|
if hasattr(obj, 'snapshot'):
|
|
|
obj.snapshot()
|
|
obj.snapshot()
|
|
|
obj.delete()
|
|
obj.delete()
|
|
|
|
|
+
|
|
|
except ProtectedError as e:
|
|
except ProtectedError as e:
|
|
|
logger.info("Caught ProtectedError while attempting to delete objects")
|
|
logger.info("Caught ProtectedError while attempting to delete objects")
|
|
|
handle_protectederror(queryset, request, e)
|
|
handle_protectederror(queryset, request, e)
|
|
|
return redirect(self.get_return_url(request))
|
|
return redirect(self.get_return_url(request))
|
|
|
|
|
|
|
|
|
|
+ except AbortRequest as e:
|
|
|
|
|
+ logger.debug(e.message)
|
|
|
|
|
+ messages.error(request, mark_safe(e.message))
|
|
|
|
|
+ return redirect(self.get_return_url(request))
|
|
|
|
|
+
|
|
|
msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}"
|
|
msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}"
|
|
|
logger.info(msg)
|
|
logger.info(msg)
|
|
|
messages.success(request, msg)
|
|
messages.success(request, msg)
|
|
@@ -857,10 +832,9 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|
|
except IntegrityError:
|
|
except IntegrityError:
|
|
|
clear_webhooks.send(sender=self)
|
|
clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
- except PermissionsViolation:
|
|
|
|
|
- msg = "Component creation failed due to object-level permissions violation"
|
|
|
|
|
- logger.debug(msg)
|
|
|
|
|
- form.add_error(None, msg)
|
|
|
|
|
|
|
+ except (AbortRequest, PermissionsViolation) as e:
|
|
|
|
|
+ logger.debug(e.message)
|
|
|
|
|
+ form.add_error(None, e.message)
|
|
|
clear_webhooks.send(sender=self)
|
|
clear_webhooks.send(sender=self)
|
|
|
|
|
|
|
|
if not form.errors:
|
|
if not form.errors:
|