mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-25 09:21:10 +00:00
featherwiki: Add new app
- Uninstall deletes wikis & extensions - Use Skylark (v1.8.0) - Add option to upload existing wiki - Open wiki links in new tab. Since Feather Wiki modifies browser history, it takes several clicks to go back and reach the FreedomBox app for Feather Wiki if the user wants to switch to another wiki file. Opening in a new tab also makes it easy for the user to move text between wikis (i.e. the Refile use case). - Improve HTML file path handling. Extract only the HTML file name from the URL. Return a 404 status if the file cannot be found - Place featherwiki_nest.cgi file in /usr/lib/cgi-bin. The file is installed as part of the FreedomBox package, rather than a step in the installation of Feather Wiki. [sunil] - Reorganized description to complete the introduction before talking about FreedomBox implementation. - Update description to say that only users of 'wiki' group can access. - Update description to talk about where the wiki is downloaded from how to upgrade it. - Update short description to 'Personal Notebooks'. - Add UsersAndGroups component and to reuse 'wiki' group properly. - Reorder component to resemble other apps (could prove useful in future). - Restrict frontpage shortcut to 'wiki' group users. - Minor styling updates. Run isort. - Use pathlib.Path object where possible instead of os.path. - Perform sanitization in privileged methods instead of callers. This leads better security if the service is compromised. - Perform duplicate checking in privileged methods instead of callers. - Check in privileged action that uploaded file originates from temporary directory. Otherwise, arbitrary files can moved into DAV directory. - Switch storage path to /var/lib/ which is an application data folder from /var/www which is a user data folder. - Add extra security to the DAV folder by explicitly rejecting .htaccess directives, forcing mime type and removing all options. - Update SVG/PNG logo icons to adhere to our guidelines. - Minor template updates. Add required attributes. Improve i18n. Avoid <p> inside <p>. - Refactor tests for more code reuse and fewer globals. Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net> Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
35bfe86bda
commit
b7c3a06e85
6
debian/copyright
vendored
6
debian/copyright
vendored
@ -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 <dev@alamantus.com>
|
||||
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
|
||||
|
||||
120
plinth/modules/featherwiki/__init__.py
Normal file
120
plinth/modules/featherwiki/__init__.py
Normal file
@ -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 <a href="{users_url}">'
|
||||
'any user</a> 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'
|
||||
])
|
||||
@ -0,0 +1,25 @@
|
||||
##
|
||||
## On all sites, provide Feather Wiki files on a path: /featherwiki
|
||||
##
|
||||
|
||||
Alias /featherwiki /var/lib/featherwiki
|
||||
|
||||
<Location /featherwiki>
|
||||
Include includes/freedombox-single-sign-on.conf
|
||||
<IfModule mod_auth_pubtkt.c>
|
||||
TKTAuthToken "admin" "wiki"
|
||||
</IfModule>
|
||||
</Location>
|
||||
|
||||
<Directory /var/lib/featherwiki>
|
||||
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
|
||||
</Directory>
|
||||
@ -0,0 +1 @@
|
||||
plinth.modules.featherwiki
|
||||
37
plinth/modules/featherwiki/forms.py
Normal file
37
plinth/modules/featherwiki/forms.py
Normal file
@ -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.'))
|
||||
16
plinth/modules/featherwiki/manifest.py
Normal file
16
plinth/modules/featherwiki/manifest.py
Normal file
@ -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)]}}
|
||||
93
plinth/modules/featherwiki/privileged.py
Normal file
93
plinth/modules/featherwiki/privileged.py
Normal file
@ -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)
|
||||
BIN
plinth/modules/featherwiki/static/icons/featherwiki.png
Normal file
BIN
plinth/modules/featherwiki/static/icons/featherwiki.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
159
plinth/modules/featherwiki/static/icons/featherwiki.svg
Normal file
159
plinth/modules/featherwiki/static/icons/featherwiki.svg
Normal file
@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg2"
|
||||
sodipodi:docname="featherwiki.svg"
|
||||
viewBox="0 0 511.99998 511.99999"
|
||||
version="1.1"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
width="512"
|
||||
height="512"
|
||||
inkscape:export-filename="featherwiki.png"
|
||||
inkscape:export-xdpi="48"
|
||||
inkscape:export-ydpi="48"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs11" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
bordercolor="#666666"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-y="23"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:window-height="1429"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:zoom="1.4283557"
|
||||
inkscape:window-x="26"
|
||||
showgrid="false"
|
||||
borderopacity="1.0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:cx="175.37648"
|
||||
inkscape:cy="344.45202"
|
||||
inkscape:window-width="1789"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:document-units="px"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Vrstva 1"
|
||||
inkscape:groupmode="layer">
|
||||
<g
|
||||
id="g907"
|
||||
transform="matrix(0.51715286,0,0,0.51715286,-2.4633634,-2.5670809)">
|
||||
<g
|
||||
id="g850"
|
||||
transform="matrix(-2.53053,-0.03341354,-0.03341354,-2.53053,1038.0811,2209.6311)">
|
||||
<path
|
||||
id="path844"
|
||||
sodipodi:nodetypes="cccccccsccccccccccsscscsscscaaacsscccccccccccccccccccccc"
|
||||
style="color:#000000;opacity:1;fill:#19457c;fill-opacity:1;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 205.22,478.53 c -23.15688,16.34454 -41.83323,53.12276 -43.6252,79.5 l 18.34,27.97 -18.6,-19.53 c -0.0333,8.4167 0.51776,17.394 1.125,27.094 l 12,14.844 -11.531,-6.375 c 0.34608,5.9871 0.66846,12.155 0.8125,18.688 0.53129,24.095 3.2621,61.668 7.8125,98.219 l 15.16,31.78 -14.03,-23.22 c 1.2784,9.6063 2.6675,19.06 4.1875,28.094 l 14.156,17 -13.5,-13.312 c 1.3937,8.0034 2.8954,15.596 4.4688,22.656 l 14,9.1562 -18.741,-5.1097 c 2.2337,8.2287 16.005,10.247 20.225,13.24 0,0 -18.352,-1.4767 -16.481,0.0653 5.136,4.2312 12.223,5.0179 14.951,6.2104 5.2176,2.2806 3.3255,2.0318 3.3255,2.0318 -1.7183,-0.31873 -8.3403,4.3107 -12.856,4.275 -3.2433,-0.0256 2.6433,0.16019 6.3662,-0.25953 14.31,-4.1628 7.819,-1.0595 8.9684,-1.9324 1.8048,-1.3706 -10.876,5.7242 -13.106,5.8769 -3.1577,0.21605 3.0489,1.4336 5.3547,0.35605 6.3347,-3.0329 12.819,-5.5291 19.655,-1.5848 4.907,2.8316 9.6512,-0.21607 7.8019,-1.3297 -1.7484,-1.8359 0.41271,0.30444 0.2971,0.43753 -1.4859,1.7106 -6.0522,-0.7293 -6.1023,-2.9946 -0.0714,-3.2319 9.9407,-1.7546 8.5089,-4.6529 -0.29476,-0.59665 4.1858,1.4365 -1.8251,0.80916 -2.1331,-0.31119 -2.9558,-0.90497 -4.4048,1.2467 0,0 1.8069,-3.3305 0.92239,-3.0232 -11.553,4.0142 6.8807,-4.0658 9.8015,-11.357 l -13.494,2.5441 10.051,-11.724 c 0.76355,-2.9062 1.512,-5.9353 2.25,-9.0312 l -13.54,7.78 14.06,-10.06 c 0.92178,-3.9543 1.8354,-8.0372 2.7188,-12.25 l -18.188,12.875 18.906,-16.406 c 1.3869,-6.8048 2.7087,-13.9 3.9688,-21.125 l -19.875,22 20.96,-28.38 c 7.2214,-43.29 11.924,-90.896 11.906,-120.09 l -13,8.4375 c 12.83559,-7.69668 13.00208,-13.67041 12.59405,-26.343 l -13.281,12.094 13.188,-15.031 c -0.5639,-15.697 -1.5394,-30.203 -3.5,-42.625 l -18.11,15.38 14.19,-32.69 c -6.41339,-14.04841 -22.03415,-48.83827 -35.28095,-50.219 z" />
|
||||
<path
|
||||
id="path846"
|
||||
style="fill:#f0e2e2;fill-rule:evenodd;stroke:#000000;stroke-width:0.30448;stroke-linecap:round"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 205.03,478.51 c 0.98759,-6.7e-4 1.7961,80.659 1.8047,180.04 0.009,99.385 -0.7859,180.05 -1.7735,180.05 -0.98759,6.7e-4 -1.7961,-80.659 -1.8047,-180.04 -0.009,-99.385 0.78591,-180.05 1.7735,-180.05 z" />
|
||||
<path
|
||||
id="path848"
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:#000000;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 202.28,842.66 c 2.174,-4.381 4.3103,-2.3783 5.4014,0.47766 l -2.514,-28.929 z" />
|
||||
</g>
|
||||
<g
|
||||
id="g842"
|
||||
transform="matrix(-2.7355499,-0.95049478,-1.0223921,2.7094995,1991.4013,-1095.9208)">
|
||||
<path
|
||||
id="path836"
|
||||
sodipodi:nodetypes="cccccccccccccccccscccccscacssccccccccccccccccccccccc"
|
||||
style="color:#000000;opacity:1;fill:#1f5598;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 205.22,478.53 c -23.15688,16.34454 -41.83323,53.12276 -43.6252,79.5 l 18.34,27.97 -18.6,-19.53 c -0.0333,8.4167 0.51776,17.394 1.125,27.094 l 12,14.844 -11.531,-6.375 c 1.61117,39.57325 4.38737,82.31761 8.625,116.907 l 15.16,31.78 -14.03,-23.22 c 1.2784,9.6063 2.6675,19.06 4.1875,28.094 l 14.156,17 -13.5,-13.312 c 1.3937,8.0034 2.8954,15.596 4.4688,22.656 l 14,9.1562 -18.741,-5.1097 c 2.2337,8.2287 16.005,10.247 20.225,13.24 0,0 -18.352,-1.4767 -16.481,0.0653 5.136,4.2312 12.223,5.0179 14.951,6.2104 0.58151,3.17031 -8.46009,4.5849 -9.5305,6.3068 -3.2433,-0.0256 2.6433,0.16019 6.3662,-0.25953 0,0 4.65117,2.59746 -4.1376,3.9445 -3.1577,0.21605 3.0489,1.4336 5.3547,0.35605 6.3347,-3.0329 12.819,-5.5291 19.655,-1.5848 4.907,2.8316 9.6512,-0.21607 7.8019,-1.3297 -1.7484,-1.8359 0.41271,0.30444 0.2971,0.43753 -1.4859,1.7106 -6.0522,-0.7293 -6.1023,-2.9946 0,0 1.71899,-1.59312 2.279,-2.59704 0.51529,-0.92375 1.8069,-3.3305 0.92239,-3.0232 -11.553,4.0142 6.8807,-4.0658 9.8015,-11.357 l -13.494,2.5441 10.051,-11.724 c 0.76355,-2.9062 1.512,-5.9353 2.25,-9.0312 l -13.54,7.78 14.06,-10.06 c 0.92178,-3.9543 1.8354,-8.0372 2.7188,-12.25 l -18.188,12.875 18.906,-16.406 c 1.3869,-6.8048 2.7087,-13.9 3.9688,-21.125 l -19.875,22 20.96,-28.38 c 7.2214,-43.29 11.924,-90.896 11.906,-120.09 l -13,8.4375 12.969,-10.656 c -0.0506,-5.26738 -0.22316,-10.99106 -0.37495,-15.687 l -13.281,12.094 13.188,-15.031 c -0.5639,-15.697 -1.5394,-30.203 -3.5,-42.625 l -18.11,15.38 14.19,-32.69 c -6.41339,-14.04841 -22.03415,-48.83827 -35.28095,-50.219 z" />
|
||||
<path
|
||||
id="path838"
|
||||
style="fill:#f0e2e2;fill-rule:evenodd;stroke:#000000;stroke-width:0.30448;stroke-linecap:round"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 205.03,478.51 c 0.98759,-6.7e-4 1.7961,80.659 1.8047,180.04 0.009,99.385 -0.7859,180.05 -1.7735,180.05 -0.98759,6.7e-4 -1.7961,-80.659 -1.8047,-180.04 -0.009,-99.385 0.78591,-180.05 1.7735,-180.05 z" />
|
||||
<path
|
||||
id="path840"
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:#000000;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 202.28,842.66 c 2.174,-4.381 4.3103,-2.3783 5.4014,0.47766 l -2.514,-28.929 z" />
|
||||
</g>
|
||||
<g
|
||||
id="g858"
|
||||
transform="matrix(2.7355499,-0.95049478,1.0223921,2.7094995,-991.40128,-1095.9208)">
|
||||
<path
|
||||
id="path852"
|
||||
sodipodi:nodetypes="ssccccccccccccccccsscscssccssccccccccccccccccccccccs"
|
||||
style="color:#000000;opacity:1;fill:#1f5598;fill-opacity:0.993151;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 205.22,478.53 c -9.3228,0.20557 -32.409,40.138 -38.719,55.875 -2.9559,7.373 -4.3693,15.194 -4.9062,23.625 l 18.34,27.97 -18.6,-19.53 c -0.0333,8.4167 0.51776,17.394 1.125,27.094 l 12,14.844 -11.531,-6.375 c 1.61117,39.57325 4.38737,82.31761 8.625,116.907 l 15.16,31.78 -14.03,-23.22 c 1.2784,9.6063 2.6675,19.06 4.1875,28.094 l 14.156,17 -13.5,-13.312 c 1.3937,8.0034 2.8954,15.596 4.4688,22.656 l 14,9.1562 -18.741,-5.1097 c 2.2337,8.2287 16.005,10.247 20.225,13.24 0,0 -18.352,-1.4767 -16.481,0.0653 5.136,4.2312 12.223,5.0179 14.951,6.2104 5.2176,2.2806 3.3255,2.0318 3.3255,2.0318 -1.7183,-0.31873 -8.3403,4.3107 -12.856,4.275 -3.2433,-0.0256 2.6433,0.16019 6.3662,-0.25953 14.31,-4.1628 7.819,-1.0595 8.9684,-1.9324 1.8048,-1.3706 -10.876,5.7242 -13.106,5.8769 -3.1577,0.21605 3.0489,1.4336 5.3547,0.35605 6.3347,-3.0329 12.819,-5.5291 19.655,-1.5848 0.96206,-2.55384 0.78043,-5.49588 4.2757,-6.48381 2.47806,-0.70042 4.60156,-3.1899 3.71705,-2.8826 -11.553,4.0142 4.08604,-4.2064 7.00684,-11.4976 l -13.494,2.5441 10.051,-11.724 c 0.76355,-2.9062 1.512,-5.9353 2.25,-9.0312 l -13.54,7.78 14.06,-10.06 c 0.92178,-3.9543 1.8354,-8.0372 2.7188,-12.25 l -18.188,12.875 18.906,-16.406 c 1.3869,-6.8048 2.7087,-13.9 3.9688,-21.125 l -19.875,22 20.96,-28.38 c 7.2214,-43.29 11.924,-90.896 11.906,-120.09 l -13,8.4375 12.969,-10.656 c -0.0506,-5.26738 -0.22316,-10.99106 -0.37495,-15.687 l -13.281,12.094 13.188,-15.031 c -0.5639,-15.697 -1.5394,-30.203 -3.5,-42.625 l -18.11,15.38 14.19,-32.69 c -6.41339,-14.04841 -22.03415,-48.83827 -35.28095,-50.219 z" />
|
||||
<path
|
||||
id="path854"
|
||||
style="fill:#f0e2e2;fill-rule:evenodd;stroke:#000000;stroke-width:0.30448;stroke-linecap:round"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 205.03,478.51 c 0.98759,-6.7e-4 1.7961,80.659 1.8047,180.04 0.009,99.385 -0.7859,180.05 -1.7735,180.05 -0.98759,6.7e-4 -1.7961,-80.659 -1.8047,-180.04 -0.009,-99.385 0.78591,-180.05 1.7735,-180.05 z" />
|
||||
<path
|
||||
id="path856"
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:#000000;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 202.28,842.66 c 2.174,-4.381 4.3103,-2.3783 5.4014,0.47766 l -2.514,-28.929 z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<metadata
|
||||
id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work>
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
|
||||
<dc:publisher>
|
||||
<cc:Agent
|
||||
rdf:about="http://openclipart.org/">
|
||||
<dc:title>Openclipart</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:date>2012-09-02T06:27:26</dc:date>
|
||||
<dc:description>a blue feather</dc:description>
|
||||
<dc:source>https://openclipart.org/detail/172047/feather-by-t-i-n-a-172047</dc:source>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>T-i-n-a</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>bird</rdf:li>
|
||||
<rdf:li>blue</rdf:li>
|
||||
<rdf:li>feather</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@ -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 }}
|
||||
|
||||
<h3>{% trans "Manage Wikis" %}</h3>
|
||||
|
||||
<div class="btn-toolbar">
|
||||
<a href="{% url 'featherwiki:create' %}" class="btn btn-default"
|
||||
role="button" title="{% trans 'Create Wiki' %}">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
{% trans 'Create Wiki' %}
|
||||
</a>
|
||||
<a href="{% url 'featherwiki:upload' %}" class="btn btn-default"
|
||||
role="button" title="{% trans 'Upload Wiki' %}">
|
||||
<span class="fa fa-upload" aria-hidden="true"></span>
|
||||
{% trans 'Upload Wiki' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{% if not wikis %}
|
||||
<p>{% trans 'No wikis available.' %}</p>
|
||||
{% else %}
|
||||
<div id="featherwiki-wiki-list" class="list-group list-group-two-column">
|
||||
{% for wiki in wikis %}
|
||||
<div class="list-group-item">
|
||||
<a class="wiki-label" href="/featherwiki/{{ wiki }}" target="_blank"
|
||||
title="{% blocktrans %}Go to wiki {{ wiki }}{% endblocktrans %}">
|
||||
{{ wiki }}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'featherwiki:rename' wiki %}"
|
||||
class="wiki-edit btn btn-default btn-sm secondary"
|
||||
role="button"
|
||||
title="{% blocktrans %}Rename wiki {{ wiki }}{% endblocktrans %}">
|
||||
<span class="fa fa-pencil-square-o" aria-hidden="true"></span>
|
||||
</a>
|
||||
|
||||
<a href="{% url 'featherwiki:delete' wiki %}"
|
||||
class="wiki-delete btn btn-default btn-sm secondary"
|
||||
role="button"
|
||||
title="{% blocktrans %}Delete wiki {{ wiki }}{% endblocktrans %}">
|
||||
<span class="fa fa-trash-o" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
37
plinth/modules/featherwiki/templates/featherwiki_delete.html
Normal file
37
plinth/modules/featherwiki/templates/featherwiki_delete.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>
|
||||
{% blocktrans trimmed %}
|
||||
Delete wiki <em>{{ name }}</em>
|
||||
{% endblocktrans %}
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
<strong>Hint</strong>: You can download a copy of this wiki from within
|
||||
Feather Wiki before deleting it.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Delete this wiki file permanently?</p>
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<form class="form form-delete" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<input type="submit" class="btn btn-md btn-danger"
|
||||
value="{% blocktrans %}Delete{% endblocktrans %}"/>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<form class="form form-featherwiki" method="post"
|
||||
enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<input type="submit" class="btn btn-primary"
|
||||
value="{% trans "Upload" %}"/>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
0
plinth/modules/featherwiki/tests/__init__.py
Normal file
0
plinth/modules/featherwiki/tests/__init__.py
Normal file
9
plinth/modules/featherwiki/tests/data/dummy_wiki.html
Normal file
9
plinth/modules/featherwiki/tests/data/dummy_wiki.html
Normal file
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Dummy Feather Wiki File</title>
|
||||
</head>
|
||||
<body>
|
||||
<p> This is a not a real Feather Wiki file. </p>
|
||||
</body>
|
||||
</html>
|
||||
105
plinth/modules/featherwiki/tests/test_functional.py
Normal file
105
plinth/modules/featherwiki/tests/test_functional.py
Normal file
@ -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)
|
||||
20
plinth/modules/featherwiki/urls.py
Normal file
20
plinth/modules/featherwiki/urls.py
Normal file
@ -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<old_name>.+\.html)/rename/$',
|
||||
RenameWikiView.as_view(), name='rename'),
|
||||
re_path(r'^apps/featherwiki/(?P<name>.+\.html)/delete/$', delete,
|
||||
name='delete'),
|
||||
]
|
||||
157
plinth/modules/featherwiki/views.py
Normal file
157
plinth/modules/featherwiki/views.py
Normal file
@ -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
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user