diff --git a/debian/copyright b/debian/copyright index bb756b6ff..64b25d866 100644 --- a/debian/copyright +++ b/debian/copyright @@ -101,6 +101,12 @@ Copyright: 2012 William Theaker Comment: https://gitlab.com/fdroid/artwork/blob/master/fdroid-logo-2015/fdroid-logo.svg License: CC-BY-SA-3.0 or GPL-3+ +Files: plinth/modules/featherwiki/static/icons/featherwiki.png + plinth/modules/featherwiki/static/icons/featherwiki.svg +Copyright: 2022 Robbie Antenesse +Comment: https://codeberg.org/Alamantus/FeatherWiki/src/branch/main/logo.svg +License: AGPL-3+ + Files: plinth/modules/gitweb/static/icons/gitweb.png plinth/modules/gitweb/static/icons/gitweb.svg Copyright: 2010 Git Authors diff --git a/plinth/modules/featherwiki/__init__.py b/plinth/modules/featherwiki/__init__.py new file mode 100644 index 000000000..0a7120438 --- /dev/null +++ b/plinth/modules/featherwiki/__init__.py @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +FreedomBox app for Feather Wiki. + +This is a FreedomBox-native implementation of a Feather Wiki Nest. +This app doesn't install any Debian packages. +""" + +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ + +from plinth import app as app_module +from plinth import cfg, frontpage, menu +from plinth.config import DropinConfigs +from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore +from plinth.modules.firewall.components import Firewall +from plinth.modules.users.components import UsersAndGroups +from plinth.utils import format_lazy + +from . import manifest, privileged + +_description = [ + format_lazy( + _('Feather Wiki is a tool to create simple self-contained wikis, each ' + 'stored in a single HTML file on your {box_name}. You can use it as ' + 'a personal wiki, as a web notebook, or for project documentation.'), + box_name=_(cfg.box_name)), + _('Each wiki is a small file. Create as many wikis as you like, such as ' + 'one wiki per topic. Customize each wiki to your liking with extensions ' + 'and other customization options.'), + _('Feather Wiki is downloaded from upstream website and not from Debian. ' + 'Wikis need to be upgraded to newer version manually.'), + format_lazy( + _('Wikis are not public by default, but they can be downloaded for ' + 'sharing or publishing. They can be edited by ' + 'any user on {box_name} belonging to the wiki group. ' + 'Simultaneous editing is not supported.'), box_name=_(cfg.box_name), + users_url=reverse_lazy('users:index')) +] + + +class FeatherWikiApp(app_module.App): + """FreedomBox app for Feather Wiki.""" + + app_id = 'featherwiki' + + _version = 1 + + def __init__(self): + """Create components for the app.""" + super().__init__() + + groups = {'wiki': _('View and edit wiki applications')} + + info = app_module.Info(self.app_id, self._version, + name=_('Feather Wiki'), + icon_filename='featherwiki', + short_description=_('Personal Notebooks'), + description=_description, + manual_page='FeatherWiki', + clients=manifest.clients) + self.add(info) + + menu_item = menu.Menu('menu-featherwiki', info.name, + info.short_description, info.icon_filename, + 'featherwiki:index', parent_url_name='apps') + self.add(menu_item) + + # The shortcut is a simple directory listing provided by Apache server. + # Expecting a large number of wiki files, so creating a shortcut for + # each file (like in ikiwiki's case) will crowd the front page. + shortcut = frontpage.Shortcut( + 'shortcut-featherwiki', info.name, + short_description=info.short_description, icon=info.icon_filename, + description=info.description, manual_page=info.manual_page, + url='/featherwiki/', clients=info.clients, login_required=True, + allowed_groups=list(groups)) + self.add(shortcut) + + dropin_configs = DropinConfigs('dropin-configs-featherwiki', [ + '/etc/apache2/conf-available/featherwiki-freedombox.conf', + ]) + self.add(dropin_configs) + + firewall = Firewall('firewall-featherwiki', info.name, + ports=['http', 'https'], is_external=True) + self.add(firewall) + + webserver = Webserver('webserver-featherwiki', + 'featherwiki-freedombox') + self.add(webserver) + + users_and_groups = UsersAndGroups('users-and-groups-featherwiki', + groups=groups) + self.add(users_and_groups) + + backup_restore = BackupRestore('backup-restore-featherwiki', + **manifest.backup) + self.add(backup_restore) + + def setup(self, old_version=None): + """Install and configure the app.""" + super().setup(old_version) + privileged.setup() + if not old_version: + self.enable() + + def uninstall(self): + """Purge directory with all the wikis.""" + super().uninstall() + privileged.uninstall() + + +def get_wiki_list(): + """List all the Feather Wiki files.""" + return sorted([ + path.name for path in privileged.wiki_dir.iterdir() + if path.suffix == '.html' + ]) diff --git a/plinth/modules/featherwiki/data/usr/share/freedombox/etc/apache2/conf-available/featherwiki-freedombox.conf b/plinth/modules/featherwiki/data/usr/share/freedombox/etc/apache2/conf-available/featherwiki-freedombox.conf new file mode 100644 index 000000000..3781dedb8 --- /dev/null +++ b/plinth/modules/featherwiki/data/usr/share/freedombox/etc/apache2/conf-available/featherwiki-freedombox.conf @@ -0,0 +1,25 @@ +## +## On all sites, provide Feather Wiki files on a path: /featherwiki +## + +Alias /featherwiki /var/lib/featherwiki + + + Include includes/freedombox-single-sign-on.conf + + TKTAuthToken "admin" "wiki" + + + + + Dav On + + # Don't accept overrides in .htaccess + AllowOverride None + + # Disable following symlinks, show an index page + Options Indexes + + # Accept and serve only HTML files + ForceType text/html + diff --git a/plinth/modules/featherwiki/data/usr/share/freedombox/modules-enabled/featherwiki b/plinth/modules/featherwiki/data/usr/share/freedombox/modules-enabled/featherwiki new file mode 100644 index 000000000..9e2d74f56 --- /dev/null +++ b/plinth/modules/featherwiki/data/usr/share/freedombox/modules-enabled/featherwiki @@ -0,0 +1 @@ +plinth.modules.featherwiki diff --git a/plinth/modules/featherwiki/forms.py b/plinth/modules/featherwiki/forms.py new file mode 100644 index 000000000..7f2687264 --- /dev/null +++ b/plinth/modules/featherwiki/forms.py @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Django forms for configuring Feather Wiki.""" + +from django import forms +from django.core import validators +from django.utils.translation import gettext_lazy as _ + + +class CreateWikiForm(forms.Form): + """Form to create a new wiki file.""" + + name = forms.CharField( + label=_('Name of the wiki file, with file extension ".html"'), + strip=True, help_text=_( + 'Wiki title and description can be set from within the wiki. ' + 'This file name is independent of the wiki title.')) + + +class RenameWikiForm(forms.Form): + """Form to rename a wiki file.""" + + new_name = forms.CharField( + label=_('New name for the wiki file, with file extension ".html"'), + strip=True, help_text=_( + 'Renaming the file has no effect on the title of the wiki.')) + + +class UploadWikiForm(forms.Form): + """Form to upload a wiki file.""" + + file = forms.FileField( + label=_('A Feather Wiki file with .html file extension'), + required=True, validators=[ + validators.FileExtensionValidator( + ['html'], _('Feather Wiki files must be in HTML format')) + ], help_text=_( + 'Upload an existing Feather Wiki file from this computer.')) diff --git a/plinth/modules/featherwiki/manifest.py b/plinth/modules/featherwiki/manifest.py new file mode 100644 index 000000000..0469011c0 --- /dev/null +++ b/plinth/modules/featherwiki/manifest.py @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Application manifest for Feather Wiki.""" + +from django.utils.translation import gettext_lazy as _ + +from .privileged import wiki_dir + +clients = [{ + 'name': _('Feather Wiki'), + 'platforms': [{ + 'type': 'web', + 'url': '/featherwiki/' + }] +}] + +backup = {'data': {'directories': [str(wiki_dir)]}} diff --git a/plinth/modules/featherwiki/privileged.py b/plinth/modules/featherwiki/privileged.py new file mode 100644 index 000000000..d5066d17c --- /dev/null +++ b/plinth/modules/featherwiki/privileged.py @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Configure Feather Wiki.""" + +import pathlib +import re +import shutil +import tempfile +import urllib.request + +from plinth.actions import privileged + +# Needs to be changed on a new release +EMPTY_WIKI_FILE = 'https://feather.wiki/builds/v1.8.x/FeatherWiki_Skylark.html' + +wiki_dir = pathlib.Path('/var/lib/featherwiki') + + +def _set_ownership(path: pathlib.Path): + """Makes www-data:www-data the owner of the give path.""" + shutil.chown(path, user='www-data', group='www-data') + + +@privileged +def setup(): + """Setup wiki dir and CGI script.""" + wiki_dir.mkdir(parents=True, exist_ok=True) + _set_ownership(wiki_dir) + + +def _normalize_wiki_file_name(name): + """Return a normalized file name from a wiki name.""" + file_name = name.replace(' ', '_') + invalid_characters = r'[\/\\\:\*\?\"\'\<\>\|]' + file_name = re.sub(invalid_characters, '', file_name) + if not file_name.endswith('.html'): + return file_name + '.html' + + return file_name + + +@privileged +def create_wiki(file_name: str): + """Initialize wiki with the latest version of Feather Wiki.""" + file_name = _normalize_wiki_file_name(file_name) + response = urllib.request.urlopen(EMPTY_WIKI_FILE) + file_path = wiki_dir / file_name + if file_path.exists(): + raise ValueError('Wiki exists') + + file_path.write_bytes(response.read()) + _set_ownership(file_path) + + +@privileged +def add_wiki_file(upload_file_path: str): + """Add an uploaded wiki file.""" + upload_file_path = pathlib.Path(upload_file_path) + temp_dir = tempfile.gettempdir() + if not upload_file_path.is_relative_to(temp_dir): + raise Exception('Uploaded file is not in expected temp directory.') + + file_name = _normalize_wiki_file_name(upload_file_path.name) + file_path = wiki_dir / file_name + if file_path.exists(): + raise ValueError('Wiki exists') + + shutil.move(upload_file_path, file_path) + _set_ownership(file_path) + + +@privileged +def rename_wiki(old_name: str, new_name: str): + """Rename wiki file.""" + old_name = _normalize_wiki_file_name(old_name) + new_name = _normalize_wiki_file_name(new_name) + file_path = wiki_dir / new_name + if file_path.exists(): + raise ValueError('Wiki exists') + + (wiki_dir / old_name).rename(file_path) + + +@privileged +def delete_wiki(file_name: str): + """Delete one wiki file by name.""" + file_name = _normalize_wiki_file_name(file_name) + (wiki_dir / file_name).unlink(missing_ok=True) + + +@privileged +def uninstall(): + """Delete all the wiki content.""" + shutil.rmtree(wiki_dir) diff --git a/plinth/modules/featherwiki/static/icons/featherwiki.png b/plinth/modules/featherwiki/static/icons/featherwiki.png new file mode 100644 index 000000000..35660077e Binary files /dev/null and b/plinth/modules/featherwiki/static/icons/featherwiki.png differ diff --git a/plinth/modules/featherwiki/static/icons/featherwiki.svg b/plinth/modules/featherwiki/static/icons/featherwiki.svg new file mode 100644 index 000000000..fa4f2d7e2 --- /dev/null +++ b/plinth/modules/featherwiki/static/icons/featherwiki.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + 2012-09-02T06:27:26 + a blue feather + https://openclipart.org/detail/172047/feather-by-t-i-n-a-172047 + + + T-i-n-a + + + + + bird + blue + feather + + + + + + + + + + + diff --git a/plinth/modules/featherwiki/templates/featherwiki_configure.html b/plinth/modules/featherwiki/templates/featherwiki_configure.html new file mode 100644 index 000000000..416d42f8f --- /dev/null +++ b/plinth/modules/featherwiki/templates/featherwiki_configure.html @@ -0,0 +1,60 @@ +{% extends "app.html" %} +{% comment %} +# SPDX-License-Identifier: AGPL-3.0-or-later +{% endcomment %} + +{% load bootstrap %} +{% load i18n %} + +{% block configuration %} + {{ block.super }} + +

{% trans "Manage Wikis" %}

+ +
+ + + {% trans 'Create Wiki' %} + + + + {% trans 'Upload Wiki' %} + +
+ +
+
+ {% if not wikis %} +

{% trans 'No wikis available.' %}

+ {% else %} +
+ {% for wiki in wikis %} + + {% endfor %} +
+ {% endif %} +
+
+ +{% endblock %} diff --git a/plinth/modules/featherwiki/templates/featherwiki_delete.html b/plinth/modules/featherwiki/templates/featherwiki_delete.html new file mode 100644 index 000000000..714bff5bc --- /dev/null +++ b/plinth/modules/featherwiki/templates/featherwiki_delete.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} +{% comment %} +# SPDX-License-Identifier: AGPL-3.0-or-later +{% endcomment %} + +{% load bootstrap %} +{% load i18n %} + +{% block content %} + +

+ {% blocktrans trimmed %} + Delete wiki {{ name }} + {% endblocktrans %} +

+ +

+ {% blocktrans trimmed %} + Hint: You can download a copy of this wiki from within + Feather Wiki before deleting it. + {% endblocktrans %} +

+ +

+ {% blocktrans trimmed %} + Delete this wiki file permanently?

+ {% endblocktrans %} +

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

{{ title }}

+ +
+ {% csrf_token %} + + {{ form|bootstrap }} + + +
+ +{% endblock %} diff --git a/plinth/modules/featherwiki/tests/__init__.py b/plinth/modules/featherwiki/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plinth/modules/featherwiki/tests/data/dummy_wiki.html b/plinth/modules/featherwiki/tests/data/dummy_wiki.html new file mode 100644 index 000000000..fd092d709 --- /dev/null +++ b/plinth/modules/featherwiki/tests/data/dummy_wiki.html @@ -0,0 +1,9 @@ + + + + Dummy Feather Wiki File + + +

This is a not a real Feather Wiki file.

+ + diff --git a/plinth/modules/featherwiki/tests/test_functional.py b/plinth/modules/featherwiki/tests/test_functional.py new file mode 100644 index 000000000..812c54e97 --- /dev/null +++ b/plinth/modules/featherwiki/tests/test_functional.py @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Functional, browser based tests for Feather Wiki app.""" + +import pathlib + +import pytest + +from plinth.tests import functional + +pytestmark = [pytest.mark.apps, pytest.mark.featherwiki] + +course_1 = 'Computer Organization and Architecture' +file_name_1 = 'Computer_Organization_and_Architecture.html' + + +class TestFeatherWikiApp(functional.BaseAppTests): + app_name = 'featherwiki' + has_service = False + has_web = True + + def _create_wiki_file(self, session_browser): + """Add a wiki to using the 'Create' functionality.""" + functional.nav_to_module(session_browser, 'featherwiki') + + wiki_link_1 = f'/featherwiki/{file_name_1}' + if self._get_links_in_app_page(session_browser, wiki_link_1): + return + + session_browser.links.find_by_href( + '/plinth/apps/featherwiki/create/').first.click() + session_browser.find_by_id('id_featherwiki-name').fill(course_1) + functional.submit(session_browser, form_class='form-featherwiki') + + def _get_links_in_app_page(self, session_browser, link): + """Return the links matching a href in the app page.""" + functional.nav_to_module(session_browser, 'featherwiki') + return session_browser.links.find_by_href(link) + + def _get_links_in_apache_listing(self, session_browser, link): + """Return the links matching a href in the index page.""" + default_url = functional.config['DEFAULT']['url'] + session_browser.visit(f'{default_url}/featherwiki') + return session_browser.links.find_by_href(link) + + def _assert_wiki_present(self, session_browser, file_name, present=True): + """Assert that a wiki is present.""" + wiki_link = f'/featherwiki/{file_name}' + assert bool(self._get_links_in_app_page(session_browser, + wiki_link)) == present + assert bool( + self._get_links_in_apache_listing(session_browser, + file_name)) == present + + def _assert_wiki_works(self, session_browser, file_name): + """Assert that wiki loads and run as expected.""" + wiki_link = f'/featherwiki/{file_name}' + default_url = functional.config['DEFAULT']['url'] + session_browser.visit(f'{default_url}{wiki_link}') + links = session_browser.links.find_by_href('https://feather.wiki') + assert len(links) == 1 + + def test_wiki_file_access(self, session_browser): + """Test creating a new wiki file.""" + self._create_wiki_file(session_browser) + + self._assert_wiki_present(session_browser, file_name_1) + self._assert_wiki_works(session_browser, file_name_1) + + def test_rename_wiki_file(self, session_browser): + """Test changing the name of a wiki file.""" + self._create_wiki_file(session_browser) + + new_course = 'A Midsummer Night\'s Dream' + new_file_name = 'A_Midsummer_Nights_Dream.html' + self._get_links_in_app_page( + session_browser, '/plinth/apps/featherwiki/' + file_name_1 + + '/rename/').first.click() + session_browser.find_by_id('id_featherwiki-new_name').fill(new_course) + functional.submit(session_browser, form_class='form-featherwiki') + + self._assert_wiki_present(session_browser, new_file_name) + self._assert_wiki_works(session_browser, new_file_name) + + def test_upload_wiki_file(self, session_browser): + """Test uploading an existing wiki file.""" + _test_data_dir = pathlib.Path(__file__).parent / 'data' + test_wiki_file = str(_test_data_dir / 'dummy_wiki.html') + + session_browser.links.find_by_href( + '/plinth/apps/featherwiki/upload/').first.click() + session_browser.attach_file('featherwiki-file', test_wiki_file) + functional.submit(session_browser, form_class='form-featherwiki') + + self._assert_wiki_present(session_browser, 'dummy_wiki.html') + + def test_delete_wiki_file(self, session_browser): + """Test deleting an existing wiki file""" + self._create_wiki_file(session_browser) + + self._get_links_in_app_page( + session_browser, '/plinth/apps/featherwiki/' + file_name_1 + + '/delete/').first.click() + functional.submit(session_browser, form_class='form-delete') + + self._assert_wiki_present(session_browser, file_name_1, present=False) diff --git a/plinth/modules/featherwiki/urls.py b/plinth/modules/featherwiki/urls.py new file mode 100644 index 000000000..87efd38a6 --- /dev/null +++ b/plinth/modules/featherwiki/urls.py @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""URLs for the Feather Wiki app.""" + +from django.urls import re_path + +from .views import (CreateWikiView, FeatherWikiAppView, RenameWikiView, + UploadWikiView, delete) + +urlpatterns = [ + re_path(r'^apps/featherwiki/$', FeatherWikiAppView.as_view(), + name='index'), + re_path(r'^apps/featherwiki/create/$', CreateWikiView.as_view(), + name='create'), + re_path(r'^apps/featherwiki/upload/$', UploadWikiView.as_view(), + name='upload'), + re_path(r'^apps/featherwiki/(?P.+\.html)/rename/$', + RenameWikiView.as_view(), name='rename'), + re_path(r'^apps/featherwiki/(?P.+\.html)/delete/$', delete, + name='delete'), +] diff --git a/plinth/modules/featherwiki/views.py b/plinth/modules/featherwiki/views.py new file mode 100644 index 000000000..7692028c0 --- /dev/null +++ b/plinth/modules/featherwiki/views.py @@ -0,0 +1,157 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Django views for Feather Wiki.""" + +import tempfile + +from django.contrib import messages +from django.contrib.messages.views import SuccessMessageMixin +from django.shortcuts import redirect +from django.template.response import TemplateResponse +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic import FormView + +from plinth import app as app_module +from plinth import views +from plinth.modules import featherwiki + +from . import privileged +from .forms import CreateWikiForm, RenameWikiForm, UploadWikiForm + +DUPLICATE_FILE_ERROR = _('A wiki file with the given name already exists.') + + +class FeatherWikiAppView(views.AppView): + """Serve configuration page.""" + + app_id = 'featherwiki' + template_name = 'featherwiki_configure.html' + + def get_context_data(self, *args, **kwargs): + """Add wikis to the context data.""" + context = super().get_context_data(*args, **kwargs) + context['wikis'] = featherwiki.get_wiki_list() + return context + + +class CreateWikiView(SuccessMessageMixin, FormView): + """View to create a new repository.""" + + form_class = CreateWikiForm + prefix = 'featherwiki' + template_name = 'form.html' + success_url = reverse_lazy('featherwiki:index') + + def get_context_data(self, **kwargs): + """Return additional context for rendering the template.""" + context = super().get_context_data(**kwargs) + context['title'] = _('Create Wiki') + return context + + def form_valid(self, form): + """Create the repository on valid form submission.""" + try: + privileged.create_wiki(form.cleaned_data['name']) + self.success_message = _('Wiki created.') + except ValueError: + messages.error(self.request, DUPLICATE_FILE_ERROR) + except Exception as error: + messages.error( + self.request, "{0} {1}".format( + _('An error occurred while creating the wiki.'), error)) + + return super().form_valid(form) + + +class RenameWikiView(SuccessMessageMixin, FormView): + """View to edit an existing repository.""" + + form_class = RenameWikiForm + prefix = 'featherwiki' + template_name = 'form.html' + success_url = reverse_lazy('featherwiki:index') + + def get_context_data(self, **kwargs): + """Return additional context for rendering the template.""" + context = super().get_context_data(**kwargs) + context['title'] = _('Rename Wiki') + return context + + def form_valid(self, form): + """Rename the wiki on valid form submission.""" + try: + privileged.rename_wiki(self.kwargs['old_name'], + form.cleaned_data['new_name']) + self.success_message = _('Wiki renamed.') + except ValueError: + messages.error(self.request, DUPLICATE_FILE_ERROR) + except Exception as error: + messages.error( + self.request, "{0} {1}".format( + _('An error occurred while renaming the wiki.'), error)) + + return super().form_valid(form) + + +class UploadWikiView(SuccessMessageMixin, FormView): + """View to upload an existing wiki file.""" + + form_class = UploadWikiForm + prefix = 'featherwiki' + template_name = 'featherwiki_upload_file.html' + success_url = reverse_lazy('featherwiki:index') + + def get_context_data(self, **kwargs): + """Return additional context for rendering the template.""" + context = super().get_context_data(**kwargs) + context['title'] = _('Upload Wiki File') + return context + + def form_valid(self, form): + """Add the wiki file on valid form submission.""" + multipart_file = self.request.FILES['featherwiki-file'] + + try: + with tempfile.TemporaryDirectory() as temp_dir: + wiki_file_name = temp_dir + '/' + multipart_file.name + with open(wiki_file_name, 'wb+') as wiki_file: + for chunk in multipart_file.chunks(): + wiki_file.write(chunk) + + privileged.add_wiki_file(wiki_file_name) + + self.success_message = _('Wiki file added.') + except ValueError: + messages.error(self.request, DUPLICATE_FILE_ERROR) + except Exception as error: + messages.error( + self.request, "{0} {1}".format(_('Failed to add wiki file.'), + error)) + return redirect(reverse_lazy('featherwiki:index')) + + return super().form_valid(form) + + +def delete(request, name): + """Handle deleting wikis, showing a confirmation dialog first. + + On GET, display a confirmation page. + On POST, delete the wiki. + """ + app = app_module.App.get('featherwiki') + if request.method == 'POST': + try: + privileged.delete_wiki(name) + messages.success(request, _('{name} deleted.').format(name=name)) + except Exception as error: + messages.error( + request, + _('Could not delete {name}: {error}').format( + name=name, error=error)) + + return redirect(reverse_lazy('featherwiki:index')) + + return TemplateResponse(request, 'featherwiki_delete.html', { + 'title': app.info.name, + 'name': name + })