def delete_rule(request, tid): rule = get_object_or_404(Rule, pk=tid) form = DeleteRuleForm(instance=rule) if request.method == 'POST': print >> sys.stderr, 'id' in request.POST print >> sys.stderr, str(rule.id) == request.POST['id'] print >> sys.stderr, str(rule.id) == request.POST['id'] # if 'id' in request.POST and str(rule.id) == request.POST['id']: form = DeleteRuleForm(request.POST, instance=rule) print >> sys.stderr, form.is_valid() print >> sys.stderr, form.errors print >> sys.stderr, form.non_field_errors() print >> sys.stderr, 'id' in request.POST if form.is_valid(): rule.delete() messages.add_message(request, messages.SUCCESS, 'Rule deleted.', extra_tags='alert-success') return HttpResponseRedirect(reverse('rules')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([rule]) rels = collector.nested() add_breadcrumb(parent=rule, title="Delete", top_level=False, request=request) system_settings = System_Settings.objects.get() return render(request, 'dojo/delete_rule.html', {'rule': rule, 'form': form, 'active_tab': 'findings', 'system_settings': system_settings, 'rels': rels, })
def _update_related_nested(self, account_type, from_instances, to_instance, to_merge): ignored = [] updated = collections.defaultdict(int) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect(from_instances) # FIXME: could handle multiple sets but will be good to test format returned by nested() try: (from_instance, related_instances) = collector.nested() except ValueError: raise CommandError("related item look-up error") ignored.append(from_instance) for related_instance in related_instances: if isinstance(related_instance, list): # NOTE: This should be a collection of related-related-objects, # NOTE: and as such only a concern if their link is deleted # # FIXME: worth bothering? pass elif related_instance._meta.model in to_merge: setattr(related_instance, account_type, to_instance) related_instance.save(update_fields=(account_type,)) updated[related_instance._meta.model] += 1 else: ignored.append(related_instance) return updated, ignored
class NestedObjectsTests(TestCase): """ Tests for ``NestedObject`` utility collection. """ def setUp(self): self.n = NestedObjects(using=DEFAULT_DB_ALIAS) self.objs = [Count.objects.create(num=i) for i in range(5)] def _check(self, target): self.assertEqual(self.n.nested(lambda obj: obj.num), target) def _connect(self, i, j): self.objs[i].parent = self.objs[j] self.objs[i].save() def _collect(self, *indices): self.n.collect([self.objs[i] for i in indices]) def test_unrelated_roots(self): self._connect(2, 1) self._collect(0) self._collect(1) self._check([0, 1, [2]]) def test_siblings(self): self._connect(1, 0) self._connect(2, 0) self._collect(0) self._check([0, [1, 2]]) def test_non_added_parent(self): self._connect(0, 1) self._collect(0) self._check([0]) def test_cyclic(self): self._connect(0, 2) self._connect(1, 0) self._connect(2, 1) self._collect(0) self._check([0, [1, [2]]]) def test_queries(self): self._connect(1, 0) self._connect(2, 0) # 1 query to fetch all children of 0 (1 and 2) # 1 query to fetch all children of 1 and 2 (none) # Should not require additional queries to populate the nested graph. self.assertNumQueries(2, self._collect, 0) def test_on_delete_do_nothing(self): """ Check that the nested collector doesn't query for DO_NOTHING objects. """ n = NestedObjects(using=DEFAULT_DB_ALIAS) objs = [Event.objects.create()] EventGuide.objects.create(event=objs[0]) with self.assertNumQueries(2): # One for Location, one for Guest, and no query for EventGuide n.collect(objs)
def delete_jira(request, tid): inst = get_object_or_404(JIRA_Conf, pk=tid) #eng = test.engagement #TODO Make Form form = DeleteJIRAConfForm(instance=inst) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([inst]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(inst.id) == request.POST['id']: form = DeleteJIRAConfForm(request.POST, instance=inst) if form.is_valid(): inst.delete() messages.add_message(request, messages.SUCCESS, 'JIRA Conf and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('jira')) add_breadcrumb(title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_jira.html', { 'inst': inst, 'form': form, 'rels': rels, 'deletable_objects': rels, })
def get_deleted_objects(self, objs, request): # we override here to allow soft_delete, modified from # https://github.com/django/django/blob/master/django/contrib/admin/utils.py """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogeneous iterable of objects (e.g. a QuerySet). Return a nested list of strings suitable for display in the template with the ``unordered_list`` filter. """ try: obj = objs[0] except IndexError: return [], {}, set(), [] else: using = router.db_for_write(obj._meta.model) collector = NestedObjects(using=using) collector.collect(objs) def format_callback(obj): return '%s: %s' % (capfirst(obj._meta.verbose_name), obj) to_delete = collector.nested(format_callback) model_count = {model._meta.verbose_name_plural: len(objs) for model, objs in collector.model_objs.items()} # we need to display count by model of the protected items too protected = [format_callback(obj) for obj in collector.protected] protected_model = {obj._meta.verbose_name_plural for obj in collector.protected} protected_model_count = dict(Counter(protected_model)) # since we are only performing soft delete, we may soft delete the protected objects later return to_delete + protected, {**model_count, **protected_model_count}, set(), []
def get_nested_objects(cls, obj): using = router.db_for_write(cls.model) collector = NestedObjects(using=using) collector.collect([obj]) nested = collector.nested(cls._format_obj) collector.dependencies return nested, list(map(cls._format_obj, collector.protected))
def delete_test(request, tid): test = get_object_or_404(Test, pk=tid) eng = test.engagement form = DeleteTestForm(instance=test) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([test]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(test.id) == request.POST['id']: form = DeleteTestForm(request.POST, instance=test) if form.is_valid(): del test.tags test.delete() messages.add_message(request, messages.SUCCESS, 'Test and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('view_engagement', args=(eng.id,))) add_breadcrumb(parent=test, title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_test.html', {'test': test, 'form': form, 'rels': rels, 'deletable_objects': rels, })
def process_preview(self, request, form, context): if "_delete" in request.POST: def format_callback(obj): return '%s: %s' % (capfirst(obj._meta.verbose_name), obj) ps = form.cleaned_data['teams'] collector = NestedObjects(using='default') # or specific database collector.collect(ps) to_delete = collector.nested(format_callback) context['objs'] = to_delete elif "_competing" in request.POST: self.preview_template = "plan/teamCompetePreview.html" teams = [] nt = form.cleaned_data['teams'] for t in nt: teams.append({ "name": t.origin.name, "competing": not t.is_competing }) context['teams'] = teams context['action'] = "_competing"
def delete_engagement(request, eid): engagement = get_object_or_404(Engagement, pk=eid) product = engagement.product form = DeleteEngagementForm(instance=engagement) if request.method == 'POST': if 'id' in request.POST and str(engagement.id) == request.POST['id']: form = DeleteEngagementForm(request.POST, instance=engagement) if form.is_valid(): del engagement.tags engagement.delete() messages.add_message( request, messages.SUCCESS, 'Engagement and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse("view_engagements", args=(product.id, ))) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([engagement]) rels = collector.nested() product_tab = Product_Tab(product.id, title="Delete Engagement", tab="engagements") product_tab.setEngagement(engagement) return render(request, 'dojo/delete_engagement.html', { 'product_tab': product_tab, 'engagement': engagement, 'form': form, 'rels': rels, })
def delete_survey(request, sid): survey = get_object_or_404(Engagement_Survey, id=sid) form = Delete_Eng_Survey_Form(instance=survey) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([survey]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(survey.id) == request.POST['id']: form = Delete_Eng_Survey_Form(request.POST, instance=survey) if form.is_valid(): survey.delete() messages.add_message(request, messages.SUCCESS, 'Questionnaire and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('survey')) add_breadcrumb(title="Delete", top_level=False, request=request) return render(request, 'defectDojo-engagement-survey/delete_survey.html', {'survey': survey, 'form': form, 'rels': rels, })
def delete_endpoint(request, eid): endpoint = get_object_or_404(Endpoint, pk=eid) product = endpoint.product form = DeleteEndpointForm(instance=endpoint) if request.method == 'POST': if 'id' in request.POST and str(endpoint.id) == request.POST['id']: form = DeleteEndpointForm(request.POST, instance=endpoint) if form.is_valid(): del endpoint.tags endpoint.delete() messages.add_message(request, messages.SUCCESS, 'Endpoint and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect( reverse('view_endpoint', args=(product.id, ))) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([endpoint]) rels = collector.nested() product_tab = Product_Tab(endpoint.product.id, "Delete Endpoint", tab="endpoints") return render( request, 'dojo/delete_endpoint.html', { 'endpoint': endpoint, 'product_tab': product_tab, 'form': form, 'rels': rels, })
def merge_users(user, user_uuid): existing_user, created = get_user_model().objects.get_or_create( uuid=user_uuid, defaults={'is_active': True}) collector = NestedObjects(using='default') collector.collect([user]) to_change_user = collector.nested() try: related_objects = to_change_user[1] except IndexError: related_objects = [] for related_object in related_objects: if getattr(related_object, 'user', None) == user: related_object.user = existing_user related_object.save() if getattr(related_object, 'created_by', None) == user: related_object.created_by = existing_user related_object.save() for group in user.communication_groups.all(): group.users.add(existing_user) if hasattr(existing_user, 'participant'): participant = existing_user.participant participant.delete() user.delete()
def delete_user(request, uid): user = get_object_or_404(Dojo_User, id=uid) form = DeleteUserForm(instance=user) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([user]) rels = collector.nested() if user.id == request.user.id: messages.add_message(request, messages.ERROR, 'You may not delete yourself.', extra_tags='alert-danger') return HttpResponseRedirect(reverse('edit_user', args=(user.id,))) if request.method == 'POST': if 'id' in request.POST and str(user.id) == request.POST['id']: form = DeleteUserForm(request.POST, instance=user) if form.is_valid(): user.delete() messages.add_message(request, messages.SUCCESS, 'User and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('users')) add_breadcrumb(title="Delete User", top_level=False, request=request) return render(request, 'dojo/delete_user.html', {'to_delete': user, 'form': form, 'rels': rels, })
class DeleteViewWithDependencies(FlagMixin, RulesRequiredMixin, AjaxTemplateMixin, DeleteView): success_message = "The objects are been deleted successfully" protected_template = None collector = None def get(self, request, *args, **kwargs): self.collector = NestedObjects(using="default") self.get_collect() self.post_collect() # If there is some protected objects, change the template if self.collector.protected: self.template_name = self.protected_template return super().get(request, *args, **kwargs) def get_collect(self): # Collect objects how will be deleted self.collector.collect([self.get_object()]) def post_collect(self): pass def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["deletable_objects"] = self.collector.nested(format_callback) context["protected_objects"] = self.collector.protected return context def delete(self, request, *args, **kwargs): result = super().delete(request, *args, **kwargs) display_success_messages(request, _(self.success_message)) return result
def delete_github(request, tid): github_instance = get_object_or_404(GITHUB_Conf, pk=tid) # eng = test.engagement # TODO Make Form form = DeleteGITHUBConfForm(instance=github_instance) if request.method == 'POST': if 'id' in request.POST and str( github_instance.id) == request.POST['id']: form = DeleteGITHUBConfForm(request.POST, instance=github_instance) if form.is_valid(): github_instance.delete() messages.add_message(request, messages.SUCCESS, 'Github Conf and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('github')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([github_instance]) rels = collector.nested() add_breadcrumb(title="Delete", top_level=False, request=request) return render( request, 'dojo/delete_github.html', { 'inst': github_instance, 'form': form, 'rels': rels, 'deletable_objects': rels, })
def delete_user(request, uid): user = get_object_or_404(Dojo_User, id=uid) form = DeleteUserForm(instance=user) if user.id == request.user.id: messages.add_message(request, messages.ERROR, 'You may not delete yourself.', extra_tags='alert-danger') return HttpResponseRedirect(reverse('edit_user', args=(user.id,))) if request.method == 'POST': if 'id' in request.POST and str(user.id) == request.POST['id']: form = DeleteUserForm(request.POST, instance=user) if form.is_valid(): user.delete() messages.add_message(request, messages.SUCCESS, 'User and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('users')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([user]) rels = collector.nested() add_breadcrumb(title="Delete User", top_level=False, request=request) return render(request, 'dojo/delete_user.html', {'to_delete': user, 'form': form, 'rels': rels, })
def delete_product(request, pid): product = get_object_or_404(Product, pk=pid) form = DeleteProductForm(instance=product) if request.method == 'POST': if 'id' in request.POST and str(product.id) == request.POST['id']: form = DeleteProductForm(request.POST, instance=product) if form.is_valid(): if product.tags: del product.tags product.delete() messages.add_message(request, messages.SUCCESS, 'Product and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('product')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([product]) rels = collector.nested() product_tab = Product_Tab(pid, title="Product", tab="settings") return render(request, 'dojo/delete_product.html', {'product': product, 'form': form, 'product_tab': product_tab, 'rels': rels, })
def _related_objects(self, using): """ Method to get all objects with foreign key the relation with self instance Args: self Returns: List(): all related objects """ using = using or router.db_for_write(self.__class__, instance=self) collector = NestedObjects(using=using) collector.collect([self]) def parse_list(obj): """ Parse to nested objects Args: obj: list instance or models object instance Returns: list(): list with objects """ if isinstance(obj, list): array = [] for item in obj: array += parse_list(item) return array return [obj] colletion = parse_list(collector.nested()) colletion.remove(self) return colletion
def get_deleted_objects(objs, request): try: obj = objs[0] except IndexError: return [], {}, set(), [] else: using = router.db_for_write(obj._meta.model) collector = NestedObjects(using=using) collector.collect(objs) perms_needed = set() def format_callback(obj): opts = obj._meta no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), obj) perms_needed.add(opts.verbose_name) try: return format_html('{}: <a href="{}">{}</a>', capfirst(opts.verbose_name), obj.get_absolute_url(), obj) except AttributeError: return no_edit_link to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] model_count = { model._meta.verbose_name_plural: len(objs) for model, objs in collector.model_objs.items() } return to_delete, model_count, perms_needed, protected
def delete_endpoint(request, eid): endpoint = get_object_or_404(Endpoint, pk=eid) product = endpoint.product form = DeleteEndpointForm(instance=endpoint) if request.method == 'POST': if 'id' in request.POST and str(endpoint.id) == request.POST['id']: form = DeleteEndpointForm(request.POST, instance=endpoint) if form.is_valid(): product = endpoint.product endpoint.delete() messages.add_message(request, messages.SUCCESS, 'Endpoint and relationships removed.', extra_tags='alert-success') create_notification(event='other', title='Deletion of %s' % endpoint, product=product, description='The endpoint "%s" was deleted by %s' % (endpoint, request.user), url=request.build_absolute_uri(reverse('endpoint')), icon="exclamation-triangle") return HttpResponseRedirect(reverse('view_product', args=(product.id,))) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([endpoint]) rels = collector.nested() product_tab = Product_Tab(endpoint.product.id, "Delete Endpoint", tab="endpoints") return render(request, 'dojo/delete_endpoint.html', {'endpoint': endpoint, 'product_tab': product_tab, 'form': form, 'rels': rels, })
def delete_jira(request, tid): jira_instance = get_object_or_404(JIRA_Conf, pk=tid) # eng = test.engagement # TODO Make Form form = DeleteJIRAConfForm(instance=jira_instance) if request.method == 'POST': if 'id' in request.POST and str(jira_instance.id) == request.POST['id']: form = DeleteJIRAConfForm(request.POST, instance=jira_instance) if form.is_valid(): jira_instance.delete() messages.add_message(request, messages.SUCCESS, 'JIRA Conf and relationships removed.', extra_tags='alert-success') create_notification(event='other', title='Deletion of JIRA URL %s' % jira_instance.url, description='JIRA url "%s" was deleted by %s' % (jira_instance.url, request.user), url=request.build_absolute_uri(reverse('jira')), ) return HttpResponseRedirect(reverse('jira')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([jira_instance]) rels = collector.nested() add_breadcrumb(title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_jira.html', {'inst': jira_instance, 'form': form, 'rels': rels, 'deletable_objects': rels, })
def delete_engagement_presets(request, pid, eid): prod = get_object_or_404(Product, id=pid) preset = get_object_or_404(Engagement_Presets, id=eid) form = DeleteEngagementPresetsForm(instance=preset) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([preset]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST: form = DeleteEngagementPresetsForm(request.POST, instance=preset) if form.is_valid(): preset.delete() messages.add_message( request, messages.SUCCESS, 'Engagement presets and engagement relationships removed.', extra_tags='alert-success') return HttpResponseRedirect( reverse('engagement_presets', args=(pid, ))) product_tab = Product_Tab(pid, title="Delete Engagement Preset", tab="settings") return render( request, 'dojo/delete_presets.html', { 'product': product, 'form': form, 'product_tab': product_tab, 'rels': rels, })
def get_deleted_objects(objs): """Based on `django/contrib/admin/utils.py`""" collector = NestedObjects(using="default") collector.collect(objs) def format_callback(obj): opts = obj._meta # Display a link to the admin page. try: return format_html( '{}: <a href="{}">{}</a>', capfirst(opts.verbose_name), reverse(app_urlname(opts, "update"), kwargs={"pk": obj.pk}), obj, ) except NoReverseMatch: pass no_edit_link = "%s: %s" % (capfirst( opts.verbose_name), force_text(obj)) return no_edit_link to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] model_count = { model._meta.verbose_name_plural: len(objs) for model, objs in collector.model_objs.items() } return to_delete, model_count, protected
def delete_product(request, pid): product = get_object_or_404(Product, pk=pid) form = DeleteProductForm(instance=product) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([product]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(product.id) == request.POST['id']: form = DeleteProductForm(request.POST, instance=product) if form.is_valid(): if product.tags: del product.tags product.delete() messages.add_message(request, messages.SUCCESS, 'Product and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('product')) add_breadcrumb(parent=product, title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_product.html', {'product': product, 'form': form, 'rels': rels, })
def delete_group(request, gid): group = get_object_or_404(Dojo_Group, id=gid) form = DeleteGroupForm(instance=group) if request.method == 'POST': if 'id' in request.POST and str(group.id) == request.POST['id']: form = DeleteGroupForm(request.POST, instance=group) if form.is_valid(): try: group.delete() messages.add_message( request, messages.SUCCESS, 'Group and relationships successfully removed.', extra_tags='alert-success') except RestrictedError as err: messages.add_message( request, messages.WARNING, 'Group cannot be deleted: {}'.format(err), extra_tags='alert-warning') return HttpResponseRedirect(reverse('groups')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([group]) rels = collector.nested() add_breadcrumb(title="Delete Group", top_level=False, request=request) return render(request, 'dojo/delete_group.html', { 'to_delete': group, 'form': form, 'rels': rels })
def delete_jira(request, tid): jira_instance = get_object_or_404(JIRA_Conf, pk=tid) # eng = test.engagement # TODO Make Form form = DeleteJIRAConfForm(instance=jira_instance) if request.method == 'POST': if 'id' in request.POST and str(jira_instance.id) == request.POST['id']: form = DeleteJIRAConfForm(request.POST, instance=jira_instance) if form.is_valid(): jira_instance.delete() messages.add_message(request, messages.SUCCESS, 'JIRA Conf and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('jira')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([jira_instance]) rels = collector.nested() add_breadcrumb(title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_jira.html', {'inst': jira_instance, 'form': form, 'rels': rels, 'deletable_objects': rels, })
def delete_product(request, pid): product = get_object_or_404(Product, pk=pid) form = DeleteProductForm(instance=product) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([product]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(product.id) == request.POST['id']: form = DeleteProductForm(request.POST, instance=product) if form.is_valid(): if product.tags: del product.tags product.delete() messages.add_message(request, messages.SUCCESS, 'Product and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('product')) product_tab = Product_Tab(pid, title="Product", tab="settings") return render( request, 'dojo/delete_product.html', { 'product': product, 'form': form, 'product_tab': product_tab, 'rels': rels, })
def get_context_data(self, **kwargs): context = super(Delete, self).get_context_data(**kwargs) context['list_reversed_url'] = reverse_lazy(ADDRESS_LIST_URL_NAME) collector = NestedObjects(using='default') collector.collect([self.get_object()]) context['deleted_objects'] = collector.nested() return context
def delete_engagement(request, eid): engagement = get_object_or_404(Engagement, pk=eid) product = engagement.product form = DeleteEngagementForm(instance=engagement) from django.contrib.admin.utils import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([engagement]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(engagement.id) == request.POST['id']: form = DeleteEngagementForm(request.POST, instance=engagement) if form.is_valid(): del engagement.tags engagement.delete() messages.add_message(request, messages.SUCCESS, 'Engagement and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect( reverse('view_product', args=(product.id, ))) add_breadcrumb(parent=engagement, title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_engagement.html', { 'engagement': engagement, 'form': form, 'rels': rels, })
def delete_engagement_presets(request, pid, eid): prod = get_object_or_404(Product, id=pid) preset = get_object_or_404(Engagement_Presets, id=eid) form = DeleteEngagementPresetsForm(instance=preset) if request.method == 'POST': if 'id' in request.POST: form = DeleteEngagementPresetsForm(request.POST, instance=preset) if form.is_valid(): preset.delete() messages.add_message(request, messages.SUCCESS, 'Engagement presets and engagement relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('engagement_presets', args=(pid,))) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([preset]) rels = collector.nested() product_tab = Product_Tab(pid, title="Delete Engagement Preset", tab="settings") return render(request, 'dojo/delete_presets.html', {'product': product, 'form': form, 'product_tab': product_tab, 'rels': rels, })
def delete_test(request, tid): test = get_object_or_404(Test, pk=tid) eng = test.engagement form = DeleteTestForm(instance=test) if request.method == 'POST': if 'id' in request.POST and str(test.id) == request.POST['id']: form = DeleteTestForm(request.POST, instance=test) if form.is_valid(): del test.tags test.delete() messages.add_message(request, messages.SUCCESS, 'Test and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('view_engagement', args=(eng.id,))) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([test]) rels = collector.nested() product_tab = Product_Tab(test.engagement.product.id, title="Delete Test", tab="engagements") product_tab.setEngagement(test.engagement) return render(request, 'dojo/delete_test.html', {'test': test, 'product_tab': product_tab, 'form': form, 'rels': rels, 'deletable_objects': rels, })
def delete_product_type(request, ptid): product_type = get_object_or_404(Product_Type, pk=ptid) form = Delete_Product_TypeForm(instance=product_type) if request.method == 'POST': if 'id' in request.POST and str(product_type.id) == request.POST['id']: form = Delete_Product_TypeForm(request.POST, instance=product_type) if form.is_valid(): product_type.delete() messages.add_message(request, messages.SUCCESS, 'Product Type and relationships removed.', extra_tags='alert-success') create_notification( event='other', title='Deletion of %s' % product_type.name, description='The product type "%s" was deleted by %s' % (product_type.name, request.user), url=request.build_absolute_uri(reverse('product_type')), icon="exclamation-triangle") return HttpResponseRedirect(reverse('product_type')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([product_type]) rels = collector.nested() add_breadcrumb(title="Delete Product Type", top_level=False, request=request) return render(request, 'dojo/delete_product_type.html', { 'product_type': product_type, 'form': form, 'rels': rels, })
def delete_product(request, pid): product = get_object_or_404(Product, pk=pid) form = DeleteProductForm(instance=product) if request.method == 'POST': if 'id' in request.POST and str(product.id) == request.POST['id']: form = DeleteProductForm(request.POST, instance=product) if form.is_valid(): if product.tags: del product.tags product.delete() messages.add_message(request, messages.SUCCESS, 'Product and relationships removed.', extra_tags='alert-success') create_notification(event='other', title='Deletion of %s' % product.name, description='The product "%s" was deleted by %s' % (product.name, request.user), url=request.build_absolute_uri(reverse('product')), icon="exclamation-triangle") return HttpResponseRedirect(reverse('product')) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([product]) rels = collector.nested() product_tab = Product_Tab(pid, title="Product", tab="settings") return render(request, 'dojo/delete_product.html', {'product': product, 'form': form, 'product_tab': product_tab, 'rels': rels, })
def xls_export(self, _models, root=None, root_qs=None): if (root and root_qs) or ((root or root_qs) is None): raise RuntimeError( _("Either a root object or a root queryset must be provided")) workbook = None try: workbookfile = self.dest or NamedTemporaryFile(dir=self.tmpdir, delete=False) workbook = Workbook() del workbook['Sheet'] sheets = {} lmodels = {} for k, v in _models.items(): lname = k.lower() model_name = lname.rsplit('.')[1] lmodels[lname] = v sheets[model_name] = workbook.create_sheet(title=model_name) sheets[model_name].append(v) if root: root_qs = root._meta.model.objects.filter(pk=root.pk) using = router.db_for_write(root_qs.first()._meta.model) collector = NestedObjects(using=using) collector.collect(root_qs) def callback(obj): fields = lmodels.get(obj._meta.label_lower, None) if fields: sheets[obj._meta.model_name].append( [getattr(obj, x) for x in fields]) collector.nested(callback) workbook.save(workbookfile) return workbookfile.name except Exception as e: if workbook: if not workbookfile.closed: workbookfile.close() if os.path.exists(workbookfile.name): os.remove(workbookfile.name) raise e
def get_deleted_objects(object): """ List the related objects before delete an object """ collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([object]) return collector.nested()
def model_has_related_objects(model): collector = NestedObjects(using="default") collector.collect([model]) collected = collector.nested() if len(collected) >= 2: return collected[1] assert collected[0] == model return False
def cascade_activate(obj, using=None): if using is None: using = DEFAULT_DB_ALIAS collector = NestedObjects(using) collector.collect([obj]) activate(collector.nested())
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) collector = NestedObjects(using='default') collector.collect([self.get_object()]) context['deleted_objects'] = collector.nested() return context
def get_deleted_objects(self): collector = NestedObjects(using=router.db_for_write(self.model)) collector.collect([self.object]) perms_needed = set() def format_callback(obj): p = '%s.%s' % ( obj._meta.app_label, get_permission_codename('delete', obj._meta) ) if not self.request.user.has_perm(p): perms_needed.add(obj._meta.verbose_name) registered = obj.__class__ in self.request.djangobmf_site.modules # only show bmf modules if not registered: return None if hasattr(obj, '_bmfmeta') and obj._bmfmeta.only_related: return format_html( '{0}: {1}', obj._meta.verbose_name, obj ) else: return format_html( '{0}: <a href="{1}">{2}</a>', obj._meta.verbose_name, obj.bmfmodule_detail(), obj ) def format_protected_callback(obj): if obj.__class__ in self.request.djangobmf_site.modules and not obj._bmfmeta.only_related: return format_html( '{0}: <a href="{1}">{2}</a>', obj._meta.verbose_name, obj.bmfmodule_detail(), obj ) else: return format_html( '{0}: {1}', obj._meta.verbose_name, obj ) to_delete = collector.nested(format_callback) protected = [ format_protected_callback(obj) for obj in collector.protected ] return to_delete, perms_needed, protected
def _nested_objs(obj): c = NestedObjects("default") c.collect([obj]) def format_callback(obj): return mark_safe('<a href="/keys/{}/{}/">{}</a>'.format(obj._meta.model_name, obj.pk, escape(obj))) result = unordered_list(c.nested(format_callback)) return mark_safe("<ul>{}</ul>".format(result))
def list_deleted_objects(obj): model = obj.__class__ def format_callback(item): return u'%s: %s' % (item._meta.verbose_name.capitalize(), force_unicode(item)) collector = NestedObjects(using=router.db_for_write(model)) collector.collect(model.objects.filter(id=obj.id)) to_delete = collector.nested(format_callback) return {'to_delete': to_delete}
def get_related_objects(obj, using=DEFAULT_DB_ALIAS): # This code is based on https://github.com/makinacorpus/django-safedelete collector = NestedObjects(using=using) collector.collect([obj]) def flatten(elem): if isinstance(elem, list): return itertools.chain.from_iterable(map(flatten, elem)) elif obj != elem: return (elem,) return () return flatten(collector.nested())
def get_merged_objects(objs, opts, user, admin_site, using): """ Find all objects related to ``objs`` that should also be merged. ``objs`` must be a homogenous iterable of objects (e.g. a QuerySet). Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. """ collector = NestedObjects(using=using) collector.collect(objs) perms_needed = set() def format_callback(obj): has_admin = obj.__class__ in admin_site._registry opts = obj._meta if has_admin: admin_url = reverse('%s:%s_%s_change' % (admin_site.name, opts.app_label, opts.object_name.lower()), None, (quote(obj._get_pk_val()),)) p = '%s.%s' % (opts.app_label, opts.get_delete_permission()) if not user.has_perm(p): perms_needed.add(opts.verbose_name) p = '%s.%s' % (opts.app_label, opts.get_change_permission()) if not user.has_perm(p): perms_needed.add(opts.verbose_name) p = '%s.%s' % (opts.app_label, opts.get_add_permission()) if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. return mark_safe(u'%s: <a href="%s">%s</a>' % (escape(capfirst(opts.verbose_name)), admin_url, escape(obj))) else: # Don't display link to edit, because it either has no # admin or is edited inline. return u'%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) to_merge = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_merge, perms_needed, protected
def related_objects(obj): """ Return a generator to the objects that would be deleted if we delete "obj" (excluding obj) """ collector = NestedObjects(using=router.db_for_write(obj)) collector.collect([obj]) def flatten(elem): if isinstance(elem, list): return itertools.chain.from_iterable(map(flatten, elem)) elif obj != elem: return (elem,) return () return flatten(collector.nested())
def get_delete_cascade(request, title): document = get_object_or_error(Document, request.user, ['change_document'], url_title=title) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([document]) delete_cascade = collector.nested() # remove all subclasses of current document from the list because that does not add much helpful information simplified_delete_cascade = [] for cascade_item in delete_cascade: if issubclass(type(document), type(cascade_item)) and not type(document) == type(cascade_item): continue simplified_delete_cascade.append(cascade_item) return HttpResponse(json.dumps(delete_cascade_to_json(simplified_delete_cascade)))
def get_deleted_objects(objs, using): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogeneous iterable of objects (e.g. a QuerySet). Returns a nested list of objects suitable for display in the template with the ``unordered_list`` filter. This is simplified from a method by the same name that the Django admin uses. "using" means the key in the DATABASES setting. """ collector = NestedObjects(using=using) collector.collect(objs) # nested() can take a formatting callback if we want it later to_delete = collector.nested() return to_delete
def collect(self): def collect_objects(obj): try: iter(obj) for o in obj: collect_objects(o) except TypeError: flattened.append(obj) collector = NestedObjects(using=self.using) collector.collect(self.qs) objs = collector.nested() flattened = [] for obj in objs: collect_objects(obj) return flattened
def get_deleted_objects(objs, user): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogeneous iterable of objects (e.g. a QuerySet). Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. Copied and updated from django.contrib.admin.utils for front end display. """ using = router.db_for_write(objs[0].__class__) collector = NestedObjects(using=using) collector.collect(objs) perms_needed = set() def format_callback(obj): opts = obj._meta no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) p = '%s.%s' % (opts.app_label, get_permission_codename('delete', opts)) if not user.has_perm(p): perms_needed.add(opts.verbose_name) if hasattr(obj, 'get_absolute_url'): url = obj.get_absolute_url() # Display a link to the admin page. return format_html('{}: <a href="{}">{}</a>', capfirst(opts.verbose_name), url, obj) else: # Don't display link to edit, because it either has no # admin or is edited inline. return no_edit_link to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, collector.model_count, perms_needed, protected
def collect_backlinks(model_instance): """:param model_instance: Django Model Instance :return: A dict of Models that reference the current Useful for determining if an instance can be deleted. Includes hyperlinks to the related models """ from django.contrib.admin.utils import NestedObjects collector = NestedObjects(using='scenario_db') # or specific database collector.collect([model_instance]) # https://docs.djangoproject.com/en/1.7/releases/1.7/#remove-and-clear-methods-of-related-managers dependants = collector.nested() # fun fact: spelling differs between America and Brittain #print("Found related models:", dependants) links = {} if len(dependants[1:]): for direct_reference in dependants[1:][0]: # only iterates over the top level if not isinstance(direct_reference, list) and not isinstance(direct_reference, RelationalPoint): # Points are obvious, don't include them name = direct_reference.__class__.__name__ try: # not everything has a name attr links[str(direct_reference)] = '/setup/%s/%i/' % (name, direct_reference.pk) except: links['%s:%i' % (name, direct_reference.pk)] = '/setup/%s/%i/' % (name, direct_reference.pk) #print(links) return links
def collect_deleted_objects(modeladmin, request, queryset): """Collects objects that are related to queryset items and checks their permissions. This method checks if the user has permissions to delete items that are anyhow related to theese in the queryset (regardless of the depth). ``modeladmin`` is expected to be a ModelAdmin instance corresponding to the class of items contained in the ``queryset``. """ db_backend = router.db_for_write(queryset.first().__class__) # NestedObjects is undocumented API, can blow up at any time collector = NestedObjects(using=db_backend) collector.collect(queryset) # Check permissions and return a human-readable string representation perms_needed = set() def format_callback(obj): admin_site = modeladmin.admin_site has_admin = obj.__class__ in admin_site._registry opts = obj._meta if has_admin: model_admin = admin_site._registry[obj.__class__] if not request.user.is_superuser and \ not model_admin.has_delete_permission(request, obj): perms_needed.add(opts.verbose_name) return '%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) # Get a nested list of dependent objects to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, perms_needed, protected
def delete_related_objects(modeladmin, request, queryset): """ Action that deletes related objects for the selected items. This action first displays a confirmation page whichs shows all the deleteable objects, or, if the user has no permission one of the related childs (foreignkeys), a "permission denied" message. Next, it deletes all related objects and redirects back to the change list. """ opts = modeladmin.model._meta app_label = opts.app_label # Check that the user has delete permission for the actual model if not modeladmin.has_delete_permission(request): raise PermissionDenied using = router.db_for_write(modeladmin.model) first_level_related_objects = [] collector = NestedObjects(using=using) collector.collect(queryset) for base_object_or_related_list in collector.nested(): if type(base_object_or_related_list) is not list: # If it's not a list, it's a base object. Skip it. continue for obj in base_object_or_related_list: if type(obj) is list: # A list here contains related objects for the previous # element. We can skip it since delete() on the first # level of related objects will cascade. continue elif not isinstance(obj, _ReadOnlyModel): first_level_related_objects.append(obj) # Populate deletable_objects, a data structure of (string representations # of) all related objects that will also be deleted. deletable_objects, model_count, perms_needed, protected = get_deleted_objects( first_level_related_objects, opts, request.user, modeladmin.admin_site, using ) # The user has already confirmed the deletion. # Do the deletion and return a None to display the change list view again. if request.POST.get('post'): if perms_needed: raise PermissionDenied n = 0 with transaction.atomic(using): for obj in first_level_related_objects: obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) obj.delete() n += 1 modeladmin.message_user( request, _("Successfully deleted %(count)d related objects.") % { "count": n, "items": model_ngettext(modeladmin.opts, n)}, messages.SUCCESS ) # Return None to display the change list page again. return None if len(queryset) == 1: objects_name = force_text(opts.verbose_name) else: objects_name = force_text(opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} else: title = _("Are you sure?") context = dict( modeladmin.admin_site.each_context(request), title=title, objects_name=objects_name, deletable_objects=[deletable_objects], model_count=dict(model_count).items(), queryset=queryset, perms_lacking=perms_needed, protected=protected, opts=opts, action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, ) request.current_app = modeladmin.admin_site.name # Display the confirmation page return TemplateResponse( request, "delete_related_for_selected_confirmation.html", context, current_app=modeladmin.admin_site.name)
def _collect_related(self, obj): collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([obj]) collected = collector.nested() return u''.join(self._collect_related_format(collected))
def get_dep_objects(instance, using=DEFAULT_DB_ALIAS): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogeneous iterable of objects (e.g. a QuerySet). Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. """ collector = NestedObjects(using=using) collector.collect([instance]) def format_callback(obj): no_edit_link = '%s: %s' % (capfirst(force_text(obj._meta.verbose_name)), force_text(obj)) return no_edit_link def format_callback2(obj): no_edit_link = '%s' % (capfirst(force_text(obj._meta.verbose_name))) return no_edit_link #ver_objs = collector.nested(format_callback) objects = collector.nested() # print objects deps = [] try: for x in objects[1]: if type(x) is not list: deps.append(x) except: pass # obteniendo mensaje para eliminar msg_del = '' if deps: objs = [] for p in deps: if not 'relationship' in force_text(p._meta.verbose_name): objs.append( _(u'<br>%(class_name)s: "%(instance)s"') % { 'class_name': capfirst(force_text(p._meta.verbose_name)), 'instance': force_text(p) + ' (' + force_text(p.pk) + ')'} ) params = { 'class_name': capfirst(force_text(instance._meta.verbose_name)), 'instance': force_text(instance), 'related_objects': get_text_list(objs, _('and'))} msg = _("Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s") msgx = _("Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:") #raise ValidationError(force_text(msg), code='deleting_protected', params=params) #raise Exception(msg) # messages.success(self.request, (', ').join(deps)# ) msg_del = force_text(msg % params) msg_delx = msgx % { 'object_name': capfirst(force_text(instance._meta.verbose_name)), 'escaped_object': get_text_list(objs, _('and'))} return deps, msg_del
def delete_related_services(modeladmin, request, queryset): opts = modeladmin.model._meta app_label = opts.app_label using = router.db_for_write(modeladmin.model) collector = NestedObjects(using=using) collector.collect(queryset) registered_services = services.get() related_services = [] to_delete = [] admin_site = modeladmin.admin_site def format(obj, account=False): has_admin = obj.__class__ in admin_site._registry opts = obj._meta no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) if has_admin: try: admin_url = reverse( 'admin:%s_%s_change' % (opts.app_label, opts.model_name), None, (quote(obj._get_pk_val()),) ) except NoReverseMatch: # Change url doesn't exist -- don't display link to edit return no_edit_link # Display a link to the admin page. context = (capfirst(opts.verbose_name), admin_url, obj) if account: context += (_("services to delete:"),) return format_html('{} <a href="{}">{}</a> {}', *context) return format_html('{}: <a href="{}">{}</a>', *context) else: # Don't display link to edit, because it either has no # admin or is edited inline. return no_edit_link def format_nested(objs, result): if isinstance(objs, list): current = [] for obj in objs: format_nested(obj, current) result.append(current) else: result.append(format(objs)) for nested in collector.nested(): if isinstance(nested, list): # Is lists of objects current = [] is_service = False for service in nested: if type(service) in registered_services: if service == main_systemuser: continue current.append(format(service)) to_delete.append(service) is_service = True elif is_service and isinstance(service, list): nested = [] format_nested(service, nested) current.append(nested) is_service = False else: is_service = False related_services.append(current) elif isinstance(nested, modeladmin.model): # Is account # Prevent the deletion of the main system user, which will delete the account main_systemuser = nested.main_systemuser related_services.append(format(nested, account=True)) # The user has already confirmed the deletion. # Do the deletion and return a None to display the change list view again. if request.POST.get('post'): accounts = len(queryset) msg = _("Related services deleted and account disabled.") for account in queryset: account.is_active = False account.save(update_fields=('is_active',)) modeladmin.log_change(request, account, msg) if accounts: relateds = len(to_delete) for obj in to_delete: obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) obj.delete() context = { 'accounts': accounts, 'relateds': relateds, } msg = _("Successfully disabled %(accounts)d account and deleted %(relateds)d related services.") % context modeladmin.message_user(request, msg, messages.SUCCESS) # Return None to display the change list page again. return None if len(queryset) == 1: objects_name = force_text(opts.verbose_name) else: objects_name = force_text(opts.verbose_name_plural) context = dict( admin_site.each_context(request), title=_("Are you sure?"), objects_name=objects_name, deletable_objects=[related_services], model_count=dict(collector.model_count).items(), queryset=queryset, opts=opts, action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, ) request.current_app = admin_site.name # Display the confirmation page template = 'admin/%s/%s/delete_related_services_confirmation.html' % (app_label, opts.model_name) return TemplateResponse(request, template, context)
def delete_related_services(modeladmin, request, queryset): opts = modeladmin.model._meta app_label = opts.app_label using = router.db_for_write(modeladmin.model) collector = NestedObjects(using=using) collector.collect(queryset) registered_services = services.get() related_services = [] to_delete = [] user = request.user admin_site = modeladmin.admin_site def format(obj, account=False): has_admin = obj.__class__ in admin_site._registry opts = obj._meta no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) if has_admin: try: admin_url = reverse('admin:%s_%s_change' % (opts.app_label, opts.model_name), None, (quote(obj._get_pk_val()),) ) except NoReverseMatch: # Change url doesn't exist -- don't display link to edit return no_edit_link p = '%s.%s' % (opts.app_label, get_permission_codename('delete', opts)) if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. context = (capfirst(opts.verbose_name), admin_url, obj) if account: context += (_("services to delete:"),) return format_html('{} <a href="{}">{}</a> {}', *context) return format_html('{}: <a href="{}">{}</a>', *context) else: # Don't display link to edit, because it either has no # admin or is edited inline. return no_edit_link def format_nested(objs, result): if isinstance(objs, list): current = [] for obj in objs: format_nested(obj, current) result.append(current) else: result.append(format(objs)) for nested in collector.nested(): if isinstance(nested, list): # Is lists of objects current = [] is_service = False for service in nested: if type(service) in registered_services: if service == main_systemuser: continue current.append(format(service)) to_delete.append(service) is_service = True elif is_service and isinstance(service, list): nested = [] format_nested(service, nested) current.append(nested) is_service = False else: is_service = False related_services.append(current) elif isinstance(nested, modeladmin.model): # Is account # Prevent the deletion of the main system user, which will delete the account main_systemuser = nested.main_systemuser related_services.append(format(nested, account=True)) # The user has already confirmed the deletion. # Do the deletion and return a None to display the change list view again. if request.POST.get('post'): n = queryset.count() if n: for obj in to_delete: obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) # TODO This probably will fail in certain conditions, just capture exception obj.delete() modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { "count": n, "items": model_ngettext(modeladmin.opts, n) }, messages.SUCCESS) # Return None to display the change list page again. return None if len(queryset) == 1: objects_name = force_text(opts.verbose_name) else: objects_name = force_text(opts.verbose_name_plural) context = dict( modeladmin.admin_site.each_context(request), title=_("Are you sure?"), objects_name=objects_name, deletable_objects=[related_services], model_count=dict(collector.model_count).items(), queryset=queryset, opts=opts, action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, ) request.current_app = modeladmin.admin_site.name # Display the confirmation page return TemplateResponse(request, modeladmin.delete_selected_confirmation_template or [ "admin/%s/%s/delete_selected_confirmation.html" % (app_label, opts.model_name), "admin/%s/delete_selected_confirmation.html" % app_label, "admin/delete_selected_confirmation.html" ], context)