From a1e8c69eefcce5f0fe31462e320dd83e22228619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Mon, 20 Feb 2023 16:34:04 +0100 Subject: [PATCH] applications: unlink app in services on deletion (#74659) --- hobo/applications/models.py | 22 ++++++++++++++++++++++ hobo/applications/views.py | 25 ++++++++++++++++++++++++- tests/test_application.py | 19 ++++++++++++++++--- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/hobo/applications/models.py b/hobo/applications/models.py index 2007071..f5c6a1b 100644 --- a/hobo/applications/models.py +++ b/hobo/applications/models.py @@ -71,6 +71,10 @@ class DeploymentError(ApplicationError): pass +class UnlinkError(ApplicationError): + pass + + class Application(models.Model): SUPPORTED_MODULES = ('wcs',) @@ -180,6 +184,24 @@ class Application(models.Model): elements[(element.type, element.slug)] = (element, relation) return elements + def unlink(self): + for service_id, services in getattr(settings, 'KNOWN_SERVICES', {}).items(): + if service_id not in Application.SUPPORTED_MODULES: + continue + service_objects = {x.get_base_url_path(): x for x in get_installed_services(types=[service_id])} + for service in services.values(): + if service['url'] not in service_objects: + continue + if service_objects[service['url']].secondary: + continue + url = urllib.parse.urljoin(service['url'], 'api/export-import/unlink/') + response = requests.post(url, data={'application': self.slug}) + if not response.ok: + raise UnlinkError( + _('Failed to unlink application in module %s (%s)') + % (service_id, response.status_code) + ) + class Element(models.Model): type = models.CharField(max_length=100, verbose_name=_('Type')) diff --git a/hobo/applications/views.py b/hobo/applications/views.py index 0bcd6b1..6a4fbb9 100644 --- a/hobo/applications/views.py +++ b/hobo/applications/views.py @@ -19,6 +19,7 @@ import io import json import tarfile +from django.contrib import messages from django.core.files.base import ContentFile from django.db.models import Prefetch from django.http import HttpResponse, HttpResponseRedirect @@ -31,7 +32,16 @@ from django.views.generic import DetailView, FormView, ListView, TemplateView from django.views.generic.edit import CreateView, DeleteView, UpdateView from .forms import GenerateForm, InstallForm, MetadataForm -from .models import STATUS_CHOICES, Application, AsyncJob, Element, Relation, Version, get_object_types +from .models import ( + STATUS_CHOICES, + Application, + AsyncJob, + Element, + Relation, + UnlinkError, + Version, + get_object_types, +) from .utils import Requests requests = Requests() @@ -383,6 +393,19 @@ class AppDeleteView(DeleteView): model = Application template_name = 'hobo/applications/app_confirm_delete.html' + def delete(self, request, *args, **kwargs): + self.object = self.get_object() + success_url = self.get_success_url() + try: + self.object.unlink() + except UnlinkError as e: + messages.error(self.request, str(e)) + return HttpResponseRedirect( + reverse('application-manifest', kwargs={'app_slug': self.kwargs['slug']}) + ) + self.object.delete() + return HttpResponseRedirect(success_url) + def get_success_url(self): return reverse('applications-home') diff --git a/tests/test_application.py b/tests/test_application.py index 1267795..9913cbc 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -754,9 +754,8 @@ def test_delete_application(app, admin_user, settings): with HTTMock(mocked_http): resp = app.get('/applications/manifest/app_to_delete/') - resp = resp.click(re.compile('^Delete$')) - - resp = resp.forms[0].submit() + resp = resp.click(re.compile('^Delete$')) + resp = resp.forms[0].submit() resp = resp.follow() assert '/applications/' in resp @@ -765,6 +764,20 @@ def test_delete_application(app, admin_user, settings): assert Application.objects.count() == 1 assert Application.objects.first().name == 'OtherApp' + def response_content(url, request): + if url.path == '/api/export-import/unlink/': + return {'status_code': 500} + return mocked_http(url, request) + + with HTTMock(response_content): + resp = app.get('/applications/manifest/other_app/') + resp = resp.click(re.compile('^Delete$')) + resp = resp.forms[0].submit() + resp = resp.follow() + assert 'Failed to unlink application in module wcs (500)' in resp + assert Application.objects.count() == 1 + assert Application.objects.first().name == 'OtherApp' + def test_404_unknown_app(app, admin_user, settings): login(app)