diff --git a/plinth/templates/uninstall.html b/plinth/templates/uninstall.html new file mode 100644 index 000000000..a4589f74d --- /dev/null +++ b/plinth/templates/uninstall.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% comment %} +# SPDX-License-Identifier: AGPL-3.0-or-later +{% endcomment %} + +{% load bootstrap %} +{% load i18n %} + +{% block content %} +

+ {% blocktrans trimmed with app_name=app_info.name %} + Uninstall App {{ app_name }}? + {% endblocktrans %} +

+ + + +

+ {% blocktrans trimmed %} + All app data and configuration will be permanently lost. App may be + installed freshly again. + {% endblocktrans %} +

+ +

+

+ {% csrf_token %} + + {{ form|bootstrap }} + + +
+

+{% endblock %} diff --git a/plinth/urls.py b/plinth/urls.py index 912983869..5d550ffab 100644 --- a/plinth/urls.py +++ b/plinth/urls.py @@ -15,6 +15,8 @@ urlpatterns = [ name='language-selection'), re_path(r'^apps/$', views.AppsIndexView.as_view(), name='apps'), re_path(r'^sys/$', views.system_index, name='system'), + re_path(r'^uninstall/(?P[1-9a-z\-_]+)/$', + views.UninstallView.as_view(), name='uninstall'), # captcha urls are public re_path(r'^captcha/image/(?P\w+)/$', public(cviews.captcha_image), diff --git a/plinth/views.py b/plinth/views.py index e95990401..3464dc850 100644 --- a/plinth/views.py +++ b/plinth/views.py @@ -3,6 +3,7 @@ Main FreedomBox views. """ +import datetime import time import urllib.parse @@ -349,6 +350,57 @@ class SetupView(TemplateView): if component.has_unavailable_packages()) +class UninstallView(FormView): + """View to uninstall apps.""" + + form_class = forms.UninstallForm + template_name = 'uninstall.html' + + def dispatch(self, request, *args, **kwargs): + """Don't allow the view to be used on essential apps.""" + app_id = self.kwargs['app_id'] + app = app_module.App.get(app_id) + if app.info.is_essential: + raise Http404 + + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, *args, **kwargs): + """Add app information to the context data.""" + context = super().get_context_data(*args, **kwargs) + app_id = self.kwargs['app_id'] + app = app_module.App.get(app_id) + context['app_info'] = app.info + return context + + def get_success_url(self): + """Return the URL to redirect to after uninstall.""" + return reverse(self.kwargs['app_id'] + ':index') + + def form_valid(self, form): + """Uninstall the app.""" + app_id = self.kwargs['app_id'] + + # Backup the app + if form.cleaned_data['should_backup']: + repository_id = form.cleaned_data['repository'] + + import plinth.modules.backups.repository as repository_module + repository = repository_module.get_instance(repository_id) + if repository.flags.get('mountable'): + repository.mount() + + name = datetime.datetime.now().strftime( + '%Y-%m-%d:%H:%M:%S') + ' ' + str( + _('before uninstall of {app_id}')).format(app_id=app_id) + repository.create_archive(name, [app_id]) + + # Uninstall + setup.run_uninstall_on_app(app_id) + + return super().form_valid(form) + + def notification_dismiss(request, id): """Dismiss a notification.""" from .notification import Notification