mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-08-26 12:46:08 +00:00
kiwix: Fix various issues after review
- Fix icon paths in copyright file. - Minor refactoring. - Add Kiwix library link to app page as well as users may want to see the content available before installing the app. - Consolidate terminology to 'content package' for UI and just 'package' internally. - Drop unused SYSTEM_USER constant. - Simplify the ExecStart= in systemd service file. - Fix incorrect i18n caused by non-lazy formatting of strings. - Confirm that xml parsing is not vulnerable as expat library of required version is used in Debian bookworm. - Don't start the kiwix daemon when managing library if app is disabled. - Ignore errors when removing files during uninstallation. - Handle failures more gracefully when library XML file does not have required attributes. - Update SVG/PNG icons to adhere to FreedomBox guidelines. - Trim block translations in templates. - Drop comments/deadcode inside translation strings. - Drop a comment inside add content page that only makes sense with multiple methods for adding content. - tests: Don't use pkg_resources library as it is deprecated. We can use importlib.resources library in future if we run tests on zip installations. - Fix potential security issues while writing file to tmp directory. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
34976ac4b0
commit
cfdf92cf0d
4
debian/copyright
vendored
4
debian/copyright
vendored
@ -132,8 +132,8 @@ Files: plinth/modules/janus/static/icons/janus.png
|
|||||||
Copyright: 2014-2022 Meetecho
|
Copyright: 2014-2022 Meetecho
|
||||||
License: GPL-3 with OpenSSL exception
|
License: GPL-3 with OpenSSL exception
|
||||||
|
|
||||||
Files: static/themes/default/icons/kiwix.png
|
Files: plinth/modules/kiwix/static/icons/kiwix.png
|
||||||
static/themes/default/icons/kiwix.svg
|
plinth/modules/kiwix/static/icons/kiwix.svg
|
||||||
Copyright: 2020 The other Kiwix guy
|
Copyright: 2020 The other Kiwix guy
|
||||||
Comment: https://commons.wikimedia.org/wiki/File:Kiwix_logo_v3.svg
|
Comment: https://commons.wikimedia.org/wiki/File:Kiwix_logo_v3.svg
|
||||||
License: CC-BY-SA-4.0
|
License: CC-BY-SA-4.0
|
||||||
|
|||||||
@ -5,13 +5,14 @@ FreedomBox app for Kiwix content server.
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from plinth import app as app_module, frontpage, menu, package
|
from plinth import app as app_module
|
||||||
|
from plinth import frontpage, menu, package
|
||||||
from plinth.config import DropinConfigs
|
from plinth.config import DropinConfigs
|
||||||
from plinth.daemon import Daemon
|
from plinth.daemon import Daemon
|
||||||
from plinth.modules.apache.components import Webserver
|
from plinth.modules.apache.components import Webserver
|
||||||
from plinth.modules.backups.components import BackupRestore
|
from plinth.modules.backups.components import BackupRestore
|
||||||
from plinth.modules.kiwix import manifest
|
from plinth.modules.firewall.components import (Firewall,
|
||||||
from plinth.modules.firewall.components import Firewall, FirewallLocalProtection
|
FirewallLocalProtection)
|
||||||
from plinth.modules.users.components import UsersAndGroups
|
from plinth.modules.users.components import UsersAndGroups
|
||||||
|
|
||||||
from . import manifest, privileged
|
from . import manifest, privileged
|
||||||
@ -28,11 +29,13 @@ _description = [
|
|||||||
<li>Educational materials: PHET, TED Ed, Vikidia</li>
|
<li>Educational materials: PHET, TED Ed, Vikidia</li>
|
||||||
<li>eBooks: Project Gutenberg</li>
|
<li>eBooks: Project Gutenberg</li>
|
||||||
<li>Magazines: Low-tech Magazine</li>
|
<li>Magazines: Low-tech Magazine</li>
|
||||||
</ul>''')
|
</ul>'''),
|
||||||
|
_('You can <a href="https://library.kiwix.org" target="_blank" '
|
||||||
|
'rel="noopener noreferrer">download</a> content packages from the Kiwix '
|
||||||
|
'project or <a href="https://openzim.org/wiki/Build_your_ZIM_file" '
|
||||||
|
'target="_blank" rel="noopener noreferrer">create</a> your own.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
SYSTEM_USER = 'kiwix'
|
|
||||||
|
|
||||||
|
|
||||||
class KiwixApp(app_module.App):
|
class KiwixApp(app_module.App):
|
||||||
"""FreedomBox app for Kiwix."""
|
"""FreedomBox app for Kiwix."""
|
||||||
@ -116,6 +119,6 @@ class KiwixApp(app_module.App):
|
|||||||
|
|
||||||
|
|
||||||
def validate_file_name(file_name: str):
|
def validate_file_name(file_name: str):
|
||||||
"""Check if the content archive file has a valid extension."""
|
"""Check if the content package file has a valid extension."""
|
||||||
if not file_name.endswith(".zim"):
|
if not file_name.endswith('.zim'):
|
||||||
raise ValueError(f"Expected a ZIM file. Found {file_name}")
|
raise ValueError(f'Expected a ZIM file. Found {file_name}')
|
||||||
|
|||||||
@ -13,7 +13,7 @@ Environment=HOME="/var/lib/kiwix-server-freedombox"
|
|||||||
Environment=LIBRARY_PATH="/var/lib/kiwix-server-freedombox/library_zim.xml"
|
Environment=LIBRARY_PATH="/var/lib/kiwix-server-freedombox/library_zim.xml"
|
||||||
Environment=ARGS="--library --port=4201 --urlRootLocation=kiwix"
|
Environment=ARGS="--library --port=4201 --urlRootLocation=kiwix"
|
||||||
ExecStartPre=sh -e -c "mkdir -p $HOME/content; library=$$(ls ${LIBRARY_PATH} 2>/dev/null || true); [ \"x$${library}\" = \"x\" ] && (mkdir -p \"${HOME}\" && echo '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<library version=\"20110515\">\n</library>' > \"${LIBRARY_PATH}\") || true"
|
ExecStartPre=sh -e -c "mkdir -p $HOME/content; library=$$(ls ${LIBRARY_PATH} 2>/dev/null || true); [ \"x$${library}\" = \"x\" ] && (mkdir -p \"${HOME}\" && echo '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<library version=\"20110515\">\n</library>' > \"${LIBRARY_PATH}\") || true"
|
||||||
ExecStart=sh -e -c "exec /usr/bin/kiwix-serve $ARGS $LIBRARY_PATH"
|
ExecStart=/usr/bin/kiwix-serve $ARGS $LIBRARY_PATH
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
ExecReload=/bin/kill -HUP $MAINPID
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
|
|||||||
@ -8,18 +8,21 @@ from django.core import validators
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
|
from plinth.utils import format_lazy
|
||||||
|
|
||||||
from .privileged import KIWIX_HOME
|
from .privileged import KIWIX_HOME
|
||||||
|
|
||||||
|
|
||||||
class AddContentForm(forms.Form):
|
class AddPackageForm(forms.Form):
|
||||||
"""Form to create an empty library."""
|
"""Form to upload a content package to a library."""
|
||||||
|
|
||||||
# Would be nice to have a progress bar when uploading large files.
|
# Would be nice to have a progress bar when uploading large files.
|
||||||
file = forms.FileField(
|
file = forms.FileField(
|
||||||
label=_('Upload File'), required=True, validators=[
|
label=_('Upload File'), required=True, validators=[
|
||||||
validators.FileExtensionValidator(
|
validators.FileExtensionValidator(
|
||||||
['zim'], _('Content packages have to be in .zim format'))
|
['zim'], _('Content packages have to be in .zim format'))
|
||||||
], help_text=_(f'''Uploaded ZIM files will be stored under
|
], help_text=format_lazy(
|
||||||
{KIWIX_HOME}/content on your {cfg.box_name}. If Kiwix fails to add the file,
|
_('Uploaded ZIM files will be stored under {kiwix_home}/content '
|
||||||
it will be deleted immediately to save disk space.'''))
|
'on your {box_name}. If Kiwix fails to add the file, it will be '
|
||||||
|
'deleted immediately to save disk space.'),
|
||||||
|
box_name=_(cfg.box_name), kiwix_home=KIWIX_HOME))
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from plinth.clients import validate
|
from plinth.clients import validate
|
||||||
|
|
||||||
clients = validate([{
|
clients = validate([{
|
||||||
'name': _('kiwix'),
|
'name': _('Kiwix'),
|
||||||
'platforms': [{
|
'platforms': [{
|
||||||
'type': 'web',
|
'type': 'web',
|
||||||
'url': '/kiwix'
|
'url': '/kiwix'
|
||||||
|
|||||||
@ -3,10 +3,11 @@
|
|||||||
Privileged actions for Kiwix content server.
|
Privileged actions for Kiwix content server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import shutil
|
import shutil
|
||||||
import xml.etree.ElementTree as ET
|
import subprocess
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
from plinth import action_utils
|
from plinth import action_utils
|
||||||
from plinth.actions import privileged
|
from plinth.actions import privileged
|
||||||
@ -19,19 +20,19 @@ CONTENT_DIR = KIWIX_HOME / 'content'
|
|||||||
|
|
||||||
|
|
||||||
@privileged
|
@privileged
|
||||||
def add_content(file_name: str):
|
def add_package(file_name: str):
|
||||||
"""Adds a content package to Kiwix.
|
"""Adds a content package to Kiwix.
|
||||||
|
|
||||||
Adding packages is idempotent.
|
Adding packages is idempotent.
|
||||||
|
|
||||||
Users can add content to Kiwix in multiple ways:
|
Users can add content to Kiwix in multiple ways:
|
||||||
- Upload a ZIM file
|
- Upload a ZIM file
|
||||||
- Provide a link to the ZIM file
|
- Provide a link to the ZIM file
|
||||||
- Provide a magnet link to the ZIM file
|
- Provide a magnet link to the ZIM file
|
||||||
|
|
||||||
The commandline download manager aria2c is a dependency of kiwix-tools.
|
The commandline download manager aria2c is a dependency of kiwix-tools.
|
||||||
aria2c is used for both HTTP and Magnet downloads.
|
aria2c is used for both HTTP and Magnet downloads.
|
||||||
"""
|
"""
|
||||||
kiwix.validate_file_name(file_name)
|
kiwix.validate_file_name(file_name)
|
||||||
|
|
||||||
# Moving files to the Kiwix library path ensures that
|
# Moving files to the Kiwix library path ensures that
|
||||||
@ -39,6 +40,8 @@ def add_content(file_name: str):
|
|||||||
zim_file_name = pathlib.Path(file_name).name
|
zim_file_name = pathlib.Path(file_name).name
|
||||||
CONTENT_DIR.mkdir(exist_ok=True)
|
CONTENT_DIR.mkdir(exist_ok=True)
|
||||||
zim_file_dest = str(CONTENT_DIR / zim_file_name)
|
zim_file_dest = str(CONTENT_DIR / zim_file_name)
|
||||||
|
shutil.chown(file_name, 'root', 'root')
|
||||||
|
os.chmod(file_name, 0o644)
|
||||||
shutil.move(file_name, zim_file_dest)
|
shutil.move(file_name, zim_file_dest)
|
||||||
|
|
||||||
_kiwix_manage_add(zim_file_dest)
|
_kiwix_manage_add(zim_file_dest)
|
||||||
@ -48,40 +51,51 @@ def _kiwix_manage_add(zim_file: str):
|
|||||||
subprocess.check_call(['kiwix-manage', LIBRARY_FILE, 'add', zim_file])
|
subprocess.check_call(['kiwix-manage', LIBRARY_FILE, 'add', zim_file])
|
||||||
|
|
||||||
# kiwix-serve doesn't read the library file unless it is restarted.
|
# kiwix-serve doesn't read the library file unless it is restarted.
|
||||||
action_utils.service_restart('kiwix-server-freedombox')
|
action_utils.service_try_restart('kiwix-server-freedombox')
|
||||||
|
|
||||||
|
|
||||||
@privileged
|
@privileged
|
||||||
def uninstall():
|
def uninstall() -> None:
|
||||||
"""Remove all content during uninstall."""
|
"""Remove all content during uninstall."""
|
||||||
shutil.rmtree(str(CONTENT_DIR))
|
shutil.rmtree(str(CONTENT_DIR), ignore_errors=True)
|
||||||
LIBRARY_FILE.unlink()
|
LIBRARY_FILE.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
@privileged
|
@privileged
|
||||||
def list_content_packages() -> dict[str, dict]:
|
def list_packages() -> dict[str, dict[str, str]]:
|
||||||
library = ET.parse(LIBRARY_FILE).getroot()
|
"""Return the list of content packages configured in library file."""
|
||||||
|
library = ElementTree.parse(LIBRARY_FILE).getroot()
|
||||||
|
|
||||||
# Relying on the fact that Python dictionaries maintain order of insertion.
|
books = {}
|
||||||
return {
|
for book in library:
|
||||||
book.attrib['id']: {
|
path = book.attrib['path'].split('/')[-1]
|
||||||
'title': book.attrib['title'],
|
path = path.removesuffix('.zim').lower() # Strip '.zim' from the path
|
||||||
'description': book.attrib['description'],
|
try:
|
||||||
# strip '.zim' from the path
|
books[book.attrib['id']] = {
|
||||||
'path': book.attrib['path'].split('/')[-1][:-4].lower()
|
'title': book.attrib['title'],
|
||||||
}
|
'description': book.attrib['description'],
|
||||||
for book in library
|
'path': path
|
||||||
}
|
}
|
||||||
|
except KeyError:
|
||||||
|
pass # Ignore entries that don't have expected properties
|
||||||
|
|
||||||
|
return books
|
||||||
|
|
||||||
|
|
||||||
@privileged
|
@privileged
|
||||||
def delete_content_package(zim_id: str):
|
def delete_package(zim_id: str):
|
||||||
library = ET.parse(LIBRARY_FILE).getroot()
|
"""Remove a content package from the library file."""
|
||||||
|
library = ElementTree.parse(LIBRARY_FILE).getroot()
|
||||||
|
|
||||||
for book in library:
|
for book in library:
|
||||||
if book.attrib['id'] == zim_id:
|
try:
|
||||||
|
if book.attrib['id'] != zim_id:
|
||||||
|
continue
|
||||||
|
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
['kiwix-manage', LIBRARY_FILE, 'remove', zim_id])
|
['kiwix-manage', LIBRARY_FILE, 'remove', zim_id])
|
||||||
(KIWIX_HOME / book.attrib['path']).unlink()
|
(KIWIX_HOME / book.attrib['path']).unlink()
|
||||||
action_utils.service_restart('kiwix-server-freedombox')
|
action_utils.service_try_restart('kiwix-server-freedombox')
|
||||||
return
|
return
|
||||||
|
except KeyError: # Expected properties not found on elements
|
||||||
|
pass
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 18 KiB |
@ -1,20 +1,48 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<svg version="1.1"
|
|
||||||
id="Layer_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1256 1256"
|
version="1.1"
|
||||||
style="enable-background:new 0 0 1256 1256;" xml:space="preserve">
|
id="Layer_1"
|
||||||
<style type="text/css">
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="kiwix.svg"
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
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"><defs
|
||||||
|
id="defs9" /><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.59282121"
|
||||||
|
inkscape:cx="448.70189"
|
||||||
|
inkscape:cy="609.79599"
|
||||||
|
inkscape:window-width="1504"
|
||||||
|
inkscape:window-height="1282"
|
||||||
|
inkscape:window-x="26"
|
||||||
|
inkscape:window-y="23"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="Layer_1" />
|
||||||
|
<style
|
||||||
|
type="text/css"
|
||||||
|
id="style2">
|
||||||
.st0{fill:#010101;}
|
.st0{fill:#010101;}
|
||||||
</style>
|
</style>
|
||||||
<path class="st0" d="M1165,764.1c-8.3-36.4-68.5-141.3-191.6-234.4c-22.5-17.1-42.8-31.3-59.7-42.6
|
<path
|
||||||
c24.6-105.3-103.3-232.3-228.1-172.5C596,230.3,496.1,195.9,404.2,197.3c-243.3,3.4-431,256.9-229.1,498.8c0.1,0.1,0.2,0.2,0.4,0.4
|
class="st0"
|
||||||
c3.1,3.7,6.3,7.4,9.5,11.1c13.1,15.7,21.8,29.6,29.2,54.1L274.4,959h-21.3c-19.6,0-35.6,15.9-35.6,35.6h80.8l135.8,64.2
|
d="m 511.31939,320.69463 c -3.94623,-17.30636 -32.56828,-67.181 -91.09611,-111.44534 -10.69761,-8.13018 -20.34923,-14.88156 -28.38433,-20.25414 11.69606,-50.06482 -49.11392,-110.446904 -108.45,-82.01502 C 240.78868,66.899736 193.29129,50.544276 149.59749,51.209906 33.920647,52.826436 -55.321204,173.35286 40.672028,288.36407 c 0.04754,0.0476 0.09509,0.0951 0.19018,0.19018 1.473893,1.75916 2.995331,3.51833 4.516769,5.27749 6.228389,7.46456 10.364799,14.0733 13.883124,25.72181 l 28.622054,93.80617 H 77.757083 c -9.318809,0 -16.925999,7.55964 -16.925999,16.926 h 38.416311 l 64.566025,30.52385 c 3.99378,-8.463 0.38036,-18.54253 -8.03509,-22.48876 l -16.926,-7.98755 h 48.68602 c 0,-9.31881 -7.55965,-16.926 -16.926,-16.926 h -44.1217 c -7.70228,0 -14.54876,-5.03976 -16.78337,-12.40923 L 87.028346,326.49511 c -5.657848,-19.58851 7.321921,-32.3781 19.540974,-33.89954 11.12551,-1.3788 16.73581,5.80048 21.96576,23.20193 l 20.15905,66.08747 h -10.12707 c -9.31881,0 -16.926,7.55964 -16.926,16.926 h 38.41631 l 64.56603,30.52385 c 3.99378,-8.463 0.38036,-18.54253 -8.03509,-22.48876 l -16.926,-7.98755 h 35.70625 c 3.61341,6.1333 8.03509,11.93378 13.31258,17.16373 33.28146,33.28145 87.34006,33.28145 120.62151,0 33.28146,-33.28146 33.28146,-87.34006 0,-120.62152 -33.28145,-33.28145 -87.34005,-33.28145 -120.62151,0 -23.39211,23.39211 -30.38122,57.05393 -20.87223,86.53179 h -40.4132 c -7.70228,0 -14.54875,-5.03976 -16.78336,-12.40923 l -33.47164,-110.01899 5.7054,-3.04287 c 79.44759,-33.329 164.4104,26.19726 223.5563,-30.9993 0.14264,-0.14264 0.28527,-0.28527 0.38036,-0.38036 7.32192,-6.65629 14.64384,-13.45522 36.27679,0.0951 23.29702,14.59629 74.69311,52.67979 97.99013,117.81636 0.0475,0 14.16839,-5.03977 10.2697,-22.29858 z M 347.85989,176.39574 c -0.19018,-8.8909 -7.41702,-16.02265 -16.40301,-16.02265 -9.03354,0 -16.403,7.32193 -16.403,16.40301 0,4.94467 2.18706,9.31881 5.6103,12.31414 -11.88623,-2.28216 -20.82468,-12.64696 -20.82468,-25.15127 0,-14.1684 11.45833,-25.62673 25.62672,-25.62673 14.16839,0 25.62672,11.45833 25.62672,25.62673 0,4.51676 -1.18862,8.74826 -3.23305,12.45677 z"
|
||||||
c8.4-17.8,0.8-39-16.9-47.3l-35.6-16.8H484c0-19.6-15.9-35.6-35.6-35.6h-92.8c-16.2,0-30.6-10.6-35.3-26.1l-47.7-156.7
|
id="path4"
|
||||||
c-11.9-41.2,15.4-68.1,41.1-71.3c23.4-2.9,35.2,12.2,46.2,48.8l42.4,139h-21.3c-19.6,0-35.6,15.9-35.6,35.6h80.8l135.8,64.2
|
style="stroke-width:0.475449" />
|
||||||
c8.4-17.8,0.8-39-16.9-47.3l-35.6-16.8h75.1c7.6,12.9,16.9,25.1,28,36.1c70,70,183.7,70,253.7,0s70-183.7,0-253.7s-183.7-70-253.7,0
|
|
||||||
c-49.2,49.2-63.9,120-43.9,182h-85c-16.2,0-30.6-10.6-35.3-26.1L378,635.4l12-6.4c167.1-70.1,345.8,55.1,470.2-65.2
|
|
||||||
c0.3-0.3,0.6-0.6,0.8-0.8c15.4-14,30.8-28.3,76.3,0.2c49,30.7,157.1,110.8,206.1,247.8C1143.5,811,1173.2,800.4,1165,764.1z
|
|
||||||
M821.2,460.6c-0.4-18.7-15.6-33.7-34.5-33.7c-19,0-34.5,15.4-34.5,34.5c0,10.4,4.6,19.6,11.8,25.9c-25-4.8-43.8-26.6-43.8-52.9
|
|
||||||
c0-29.8,24.1-53.9,53.9-53.9c29.8,0,53.9,24.1,53.9,53.9C828,443.9,825.5,452.8,821.2,460.6z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.3 KiB |
@ -1,58 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% comment %}
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% load bootstrap %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h3>{{ title }}</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% blocktrans %}
|
|
||||||
You can <a href="https://library.kiwix.org"
|
|
||||||
target="_blank" rel="noopener noreferrer">download</a>
|
|
||||||
content packages from the Kiwix project or
|
|
||||||
<a href="https://openzim.org/wiki/Build_your_ZIM_file"
|
|
||||||
target="_blank" rel="noopener noreferrer">create</a> your own.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% blocktrans %}
|
|
||||||
Content packages can be added in the following ways:
|
|
||||||
<ul>
|
|
||||||
<li>upload a ZIM file</li>
|
|
||||||
<!-- <li>upload a BitTorrent file to download a ZIM file</li> -->
|
|
||||||
<!-- <li>provide a download link to a ZIM file</li> -->
|
|
||||||
<!-- <li>provide a magnet link to download a ZIM file</li> -->
|
|
||||||
<!-- <li>provide a file path to a ZIM file</li> -->
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<!-- TODO Add this somewhere -->
|
|
||||||
<!-- The Kiwix project recommends using BitTorrent for downloads. -->
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if max_filesize %}
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<!-- <span class="fa fa-exclamation-triangle" aria-hidden="true"></span> -->
|
|
||||||
<!-- <span class="sr-only">{% trans "Caution:" %}</span> -->
|
|
||||||
{% blocktrans trimmed %}
|
|
||||||
You have {{ max_filesize }} of free disk space available.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form class="form form-kiwix" enctype="multipart/form-data" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{{ form|bootstrap }}
|
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary"
|
|
||||||
value="{% trans "Upload file" %}"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
39
plinth/modules/kiwix/templates/kiwix-add-package.html
Normal file
39
plinth/modules/kiwix/templates/kiwix-add-package.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load bootstrap %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You can <a href="https://library.kiwix.org" target="_blank"
|
||||||
|
rel="noopener noreferrer">download</a> content packages from the Kiwix
|
||||||
|
project or <a href="https://openzim.org/wiki/Build_your_ZIM_file"
|
||||||
|
target="_blank" rel="noopener noreferrer">create</a> your own.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if max_filesize %}
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You have {{ max_filesize }} of free disk space available.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form class="form form-kiwix" enctype="multipart/form-data" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form|bootstrap }}
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary"
|
||||||
|
value="{% trans "Upload ZIM file" %}"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
Delete this package permanently? You may add it back later if you have a copy of the ZIM file.
|
Delete this package permanently? You may add it back later if you have a
|
||||||
|
copy of the ZIM file.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -8,32 +8,31 @@
|
|||||||
{% block configuration %}
|
{% block configuration %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
<h3>{% trans "Manage Content" %}</h3>
|
<h3>{% trans "Manage Content Packages" %}</h3>
|
||||||
|
|
||||||
<div class="btn-toolbar">
|
<div class="btn-toolbar">
|
||||||
<a href="{% url 'kiwix:add-content' %}" class="btn btn-default"
|
<a href="{% url 'kiwix:add-package' %}" class="btn btn-default"
|
||||||
role="button" title="{% trans 'Add a content package' %}">
|
role="button" title="{% trans 'Add a content package' %}">
|
||||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
{% trans 'Add' %}
|
{% trans 'Add Package' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% if not packages %}
|
{% if not packages %}
|
||||||
<p>{% trans 'No content available.' %}</p>
|
<p>{% trans 'No content packages available.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div id="kiwix-packages" class="list-group list-group-two-column">
|
<div id="kiwix-packages" class="list-group list-group-two-column">
|
||||||
{% for id, package in packages.items %}
|
{% for id, package in packages.items %}
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<a id="{{ id }}"
|
<a id="{{ id }}" class="primary"
|
||||||
class="primary"
|
|
||||||
href="/kiwix/viewer#{{ package.path }}"
|
href="/kiwix/viewer#{{ package.path }}"
|
||||||
title="{{ package.description }}">
|
title="{{ package.description }}">
|
||||||
{{ package.title }}
|
{{ package.title }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'kiwix:delete-content' id %}"
|
<a href="{% url 'kiwix:delete-package' id %}"
|
||||||
class="btn btn-default btn-sm secondary" role="button"
|
class="btn btn-default btn-sm secondary" role="button"
|
||||||
title="{% blocktrans with title=package.title %}Delete package {{ title }}{% endblocktrans %}">
|
title="{% blocktrans with title=package.title %}Delete package {{ title }}{% endblocktrans %}">
|
||||||
<span class="fa fa-trash-o" aria-hidden="true"></span>
|
<span class="fa fa-trash-o" aria-hidden="true"></span>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Nothing to see here.
|
Nothing to see here.
|
||||||
|
|||||||
@ -3,94 +3,97 @@
|
|||||||
Functional, browser based tests for Kiwix app.
|
Functional, browser based tests for Kiwix app.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pkg_resources
|
import pathlib
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from time import sleep
|
|
||||||
from plinth.modules.kiwix.tests.test_privileged import ZIM_ID
|
|
||||||
|
|
||||||
from plinth.tests import functional
|
from plinth.tests import functional
|
||||||
|
|
||||||
|
from .test_privileged import ZIM_ID
|
||||||
|
|
||||||
pytestmark = [pytest.mark.apps, pytest.mark.sso, pytest.mark.kiwix]
|
pytestmark = [pytest.mark.apps, pytest.mark.sso, pytest.mark.kiwix]
|
||||||
|
|
||||||
_default_url = functional.config['DEFAULT']['url']
|
_default_url = functional.config['DEFAULT']['url']
|
||||||
|
|
||||||
ZIM_ID = 'bc4f8cdf-5626-2b13-3860-0033deddfbea'
|
_data_dir = pathlib.Path(__file__).parent / 'data'
|
||||||
|
|
||||||
|
|
||||||
class TestKiwixApp(functional.BaseAppTests):
|
class TestKiwixApp(functional.BaseAppTests):
|
||||||
|
"""Basic functional tests for Kiwix app."""
|
||||||
|
|
||||||
app_name = 'kiwix'
|
app_name = 'kiwix'
|
||||||
has_service = True
|
has_service = True
|
||||||
has_web = True
|
has_web = True
|
||||||
|
|
||||||
def test_add_delete_content_package(self, session_browser):
|
def test_add_delete_package(self, session_browser):
|
||||||
"""Test adding/deleting content package to the library."""
|
"""Test adding/deleting content package to the library."""
|
||||||
functional.app_enable(session_browser, 'kiwix')
|
functional.app_enable(session_browser, 'kiwix')
|
||||||
|
|
||||||
zim_file = pkg_resources.resource_filename(
|
zim_file = _data_dir / 'FreedomBox.zim'
|
||||||
'plinth.modules.kiwix.tests', 'data/FreedomBox.zim')
|
_add_package(session_browser, str(zim_file))
|
||||||
_add_content_package(session_browser, zim_file)
|
assert _is_package_listed(session_browser, 'freedombox')
|
||||||
assert _is_content_package_listed(session_browser, 'freedombox')
|
assert _is_package_available(session_browser, 'FreedomBox')
|
||||||
assert _is_content_package_available(session_browser, 'FreedomBox')
|
|
||||||
|
|
||||||
_delete_content_package(session_browser, ZIM_ID)
|
_delete_package(session_browser, ZIM_ID)
|
||||||
assert not _is_content_package_listed(session_browser, 'freedombox')
|
assert not _is_package_listed(session_browser, 'freedombox')
|
||||||
assert not _is_content_package_available(session_browser, 'FreedomBox')
|
assert not _is_package_available(session_browser, 'FreedomBox')
|
||||||
|
|
||||||
@pytest.mark.backups
|
@pytest.mark.backups
|
||||||
def test_backup_restore(self, session_browser):
|
def test_backup_restore(self, session_browser):
|
||||||
"""Test backing up and restoring."""
|
"""Test backing up and restoring."""
|
||||||
functional.app_enable(session_browser, 'kiwix')
|
functional.app_enable(session_browser, 'kiwix')
|
||||||
|
|
||||||
zim_file = pkg_resources.resource_filename(
|
zim_file = _data_dir / 'FreedomBox.zim'
|
||||||
'plinth.modules.kiwix.tests', 'data/FreedomBox.zim')
|
_add_package(session_browser, str(zim_file))
|
||||||
_add_content_package(session_browser, zim_file)
|
|
||||||
functional.backup_create(session_browser, 'kiwix', 'test_kiwix')
|
functional.backup_create(session_browser, 'kiwix', 'test_kiwix')
|
||||||
|
|
||||||
_delete_content_package(session_browser, ZIM_ID)
|
_delete_package(session_browser, ZIM_ID)
|
||||||
functional.backup_restore(session_browser, 'kiwix', 'test_kiwix')
|
functional.backup_restore(session_browser, 'kiwix', 'test_kiwix')
|
||||||
|
|
||||||
assert _is_content_package_listed(session_browser, 'freedombox')
|
assert _is_package_listed(session_browser, 'freedombox')
|
||||||
assert _is_content_package_available(session_browser, 'FreedomBox')
|
assert _is_package_available(session_browser, 'FreedomBox')
|
||||||
|
|
||||||
def test_add_invalid_zim_file(self, session_browser):
|
def test_add_invalid_zim_file(self, session_browser):
|
||||||
"""Test handling of invalid zim files."""
|
"""Test handling of invalid zim files."""
|
||||||
functional.app_enable(session_browser, 'kiwix')
|
functional.app_enable(session_browser, 'kiwix')
|
||||||
|
|
||||||
zim_file = pkg_resources.resource_filename(
|
zim_file = _data_dir / 'invalid.zim'
|
||||||
'plinth.modules.kiwix.tests', 'data/invalid.zim')
|
_add_package(session_browser, str(zim_file))
|
||||||
_add_content_package(session_browser, zim_file)
|
|
||||||
|
|
||||||
assert not _is_content_package_listed(session_browser, 'invalid')
|
assert not _is_package_listed(session_browser, 'invalid')
|
||||||
|
|
||||||
|
|
||||||
def _add_content_package(browser, file_name):
|
def _add_package(browser, file_name):
|
||||||
browser.links.find_by_href('/plinth/apps/kiwix/content/add/').first.click()
|
"""Add a package by uploading the ZIM file in kiwix app page."""
|
||||||
|
browser.links.find_by_href('/plinth/apps/kiwix/package/add/').first.click()
|
||||||
browser.attach_file('kiwix-file', file_name)
|
browser.attach_file('kiwix-file', file_name)
|
||||||
functional.submit(browser, form_class='form-kiwix')
|
functional.submit(browser, form_class='form-kiwix')
|
||||||
|
|
||||||
|
|
||||||
def _is_content_package_available(browser, title) -> bool:
|
def _is_package_available(browser, title) -> bool:
|
||||||
|
"""Check whether a ZIM file is available in Kiwix web interface."""
|
||||||
browser.visit(f'{_default_url}/kiwix')
|
browser.visit(f'{_default_url}/kiwix')
|
||||||
sleep(1) # Allow time for the books to appear
|
sleep(1) # Allow time for the books to appear
|
||||||
titles = browser.find_by_id('book__title')
|
titles = browser.find_by_id('book__title')
|
||||||
print(len(titles))
|
return any(element.value == title for element in titles)
|
||||||
print([title.value for title in titles])
|
|
||||||
return any(map(lambda e: e.value == title, titles))
|
|
||||||
|
|
||||||
|
|
||||||
def _is_content_package_listed(browser, name) -> bool:
|
def _is_package_listed(browser, name) -> bool:
|
||||||
|
"""Return whether a content package is list in kiwix app page."""
|
||||||
functional.nav_to_module(browser, 'kiwix')
|
functional.nav_to_module(browser, 'kiwix')
|
||||||
links_found = browser.links.find_by_partial_href(f'/kiwix/viewer#{name}')
|
links_found = browser.links.find_by_partial_href(f'/kiwix/viewer#{name}')
|
||||||
return len(links_found) == 1
|
return len(links_found) == 1
|
||||||
|
|
||||||
|
|
||||||
def _delete_content_package(browser, zim_id):
|
def _delete_package(browser, zim_id):
|
||||||
|
"""Delete a content package from the kiwix app page."""
|
||||||
functional.nav_to_module(browser, 'kiwix')
|
functional.nav_to_module(browser, 'kiwix')
|
||||||
link = browser.links.find_by_href(
|
link = browser.links.find_by_href(
|
||||||
f'/plinth/apps/kiwix/content/{zim_id}/delete/')
|
f'/plinth/apps/kiwix/package/{zim_id}/delete/')
|
||||||
if not link:
|
if not link:
|
||||||
raise ValueError('ZIM file missing!')
|
raise ValueError('ZIM file missing!')
|
||||||
|
|
||||||
link.first.click()
|
link.first.click()
|
||||||
functional.submit(browser, form_class='form-delete')
|
functional.submit(browser, form_class='form-delete')
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Test module for Kiwix actions.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import pkg_resources
|
import shutil
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -22,65 +22,57 @@ ZIM_ID = 'bc4f8cdf-5626-2b13-3860-0033deddfbea'
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def fixture_kiwix_home(tmpdir):
|
def fixture_kiwix_home(tmp_path):
|
||||||
"""Set Kiwix home to a new temporary directory
|
"""Create a new Kiwix home in a new temporary directory.
|
||||||
initialized with an empty library file."""
|
|
||||||
privileged.KIWIX_HOME = pathlib.Path(str(tmpdir / 'kiwix'))
|
Initialize with a sample, valid library file.
|
||||||
|
"""
|
||||||
|
privileged.KIWIX_HOME = tmp_path / 'kiwix'
|
||||||
privileged.KIWIX_HOME.mkdir()
|
privileged.KIWIX_HOME.mkdir()
|
||||||
privileged.CONTENT_DIR = privileged.KIWIX_HOME / 'content'
|
privileged.CONTENT_DIR = privileged.KIWIX_HOME / 'content'
|
||||||
privileged.CONTENT_DIR.mkdir()
|
privileged.CONTENT_DIR.mkdir()
|
||||||
privileged.LIBRARY_FILE = privileged.KIWIX_HOME / 'library_zim.xml'
|
privileged.LIBRARY_FILE = privileged.KIWIX_HOME / 'library_zim.xml'
|
||||||
with open(privileged.LIBRARY_FILE, 'w', encoding='utf_8') as library_file:
|
source_file = pathlib.Path(__file__).parent / 'data/sample_library_zim.xml'
|
||||||
library_file.write(EMPTY_LIBRARY_CONTENTS)
|
shutil.copy(source_file, privileged.LIBRARY_FILE)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def fixture_patch():
|
def fixture_patch():
|
||||||
"""Patch some underlying methods."""
|
"""Patch some underlying methods."""
|
||||||
with patch('subprocess.check_call'), patch('subprocess.run'):
|
with patch('subprocess.check_call'), patch('subprocess.run'), patch(
|
||||||
|
'os.chown'):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
def test_add_content(tmpdir):
|
def test_add_package(tmp_path):
|
||||||
"""Test adding a content package to Kiwix."""
|
"""Test adding a content package to Kiwix."""
|
||||||
some_dir = tmpdir / 'some' / 'dir'
|
some_dir = tmp_path / 'some' / 'dir'
|
||||||
pathlib.Path(some_dir).mkdir(parents=True, exist_ok=True)
|
some_dir.mkdir(parents=True, exist_ok=True)
|
||||||
zim_file_name = 'wikipedia_en_all_maxi_2022-05.zim'
|
zim_file_name = 'wikipedia_en_all_maxi_2022-05.zim'
|
||||||
orig_file = some_dir / zim_file_name
|
orig_file = some_dir / zim_file_name
|
||||||
pathlib.Path(orig_file).touch()
|
orig_file.touch()
|
||||||
|
|
||||||
privileged.add_content(str(orig_file))
|
privileged.add_package(str(orig_file))
|
||||||
assert (privileged.KIWIX_HOME / 'content' / zim_file_name).exists()
|
assert (privileged.KIWIX_HOME / 'content' / zim_file_name).exists()
|
||||||
assert not orig_file.exists()
|
assert not orig_file.exists()
|
||||||
|
|
||||||
|
|
||||||
def test_list_content_packages():
|
def test_list_packages():
|
||||||
"""Test listing the content packages from a library file."""
|
"""Test listing the content packages from a library file."""
|
||||||
privileged.LIBRARY_FILE = pkg_resources.resource_filename(
|
content = privileged.list_packages()
|
||||||
'plinth.modules.kiwix.tests', 'data/sample_library_zim.xml')
|
assert content[ZIM_ID] == {
|
||||||
content_packages = privileged.list_content_packages()
|
|
||||||
assert content_packages[ZIM_ID] == {
|
|
||||||
'title': 'FreedomBox',
|
'title': 'FreedomBox',
|
||||||
'description': 'A sample content archive',
|
'description': 'A sample content archive',
|
||||||
'path': 'freedombox'
|
'path': 'freedombox'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_delete_content_package():
|
def test_delete_package():
|
||||||
"""Test deleting one content package."""
|
"""Test deleting one content package."""
|
||||||
sample_library_file = pkg_resources.resource_filename(
|
|
||||||
'plinth.modules.kiwix.tests', 'data/sample_library_zim.xml')
|
|
||||||
|
|
||||||
with open(sample_library_file, 'r',
|
|
||||||
encoding='utf_8') as sample_library_file:
|
|
||||||
with open(privileged.LIBRARY_FILE, 'w',
|
|
||||||
encoding='utf_8') as library_file:
|
|
||||||
library_file.write(sample_library_file.read())
|
|
||||||
|
|
||||||
zim_file = privileged.CONTENT_DIR / 'FreedomBox.zim'
|
zim_file = privileged.CONTENT_DIR / 'FreedomBox.zim'
|
||||||
zim_file.touch()
|
zim_file.touch()
|
||||||
|
|
||||||
privileged.delete_content_package(ZIM_ID)
|
privileged.delete_package(ZIM_ID)
|
||||||
|
|
||||||
assert not zim_file.exists()
|
assert not zim_file.exists()
|
||||||
# Cannot check that the book is removed from library_zim.xml
|
# Cannot check that the book is removed from library_zim.xml
|
||||||
|
|||||||
@ -3,19 +3,18 @@
|
|||||||
Test module for Kiwix validations.
|
Test module for Kiwix validations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import unittest
|
import pytest
|
||||||
|
|
||||||
from plinth.modules import kiwix
|
from plinth.modules import kiwix
|
||||||
|
|
||||||
|
|
||||||
class TestValidations(unittest.TestCase):
|
def test_add_file_with_invalid_extension():
|
||||||
|
"""Test that adding a file with invalid fails as expected."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
kiwix.validate_file_name('wikipedia.zip')
|
||||||
|
|
||||||
def test_add_file_with_invalid_extension(self):
|
# We don't support the legacy format of split zim files.
|
||||||
self.assertRaises(ValueError,
|
with pytest.raises(ValueError):
|
||||||
lambda: kiwix.validate_file_name('wikipedia.zip'))
|
kiwix.validate_file_name('wikipedia_en_all_maxi_2022-05.zima')
|
||||||
|
|
||||||
# We don't support the legacy format of split zim files.
|
kiwix.validate_file_name('wikipedia_en_all_maxi_2022-05.zim')
|
||||||
self.assertRaises(
|
|
||||||
ValueError, lambda: kiwix.validate_file_name(
|
|
||||||
'wikipedia_en_all_maxi_2022-05.zima'))
|
|
||||||
|
|
||||||
kiwix.validate_file_name('wikipedia_en_all_maxi_2022-05.zim')
|
|
||||||
|
|||||||
@ -3,15 +3,15 @@
|
|||||||
Test module for Kiwix views.
|
Test module for Kiwix views.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from plinth import module_loader
|
import pathlib
|
||||||
from django import urls
|
|
||||||
from unittest.mock import call, patch
|
from unittest.mock import call, patch
|
||||||
from django.contrib.messages.storage.fallback import FallbackStorage
|
|
||||||
from django.http.response import Http404
|
|
||||||
from django.test.client import encode_multipart, RequestFactory
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from django import urls
|
||||||
|
from django.contrib.messages.storage.fallback import FallbackStorage
|
||||||
|
from django.http.response import Http404
|
||||||
|
|
||||||
|
from plinth import module_loader
|
||||||
from plinth.modules.kiwix import views
|
from plinth.modules.kiwix import views
|
||||||
|
|
||||||
# For all tests, use plinth.urls instead of urls configured for testing
|
# For all tests, use plinth.urls instead of urls configured for testing
|
||||||
@ -19,6 +19,8 @@ pytestmark = pytest.mark.urls('plinth.urls')
|
|||||||
|
|
||||||
ZIM_ID = 'bc4f8cdf-5626-2b13-3860-0033deddfbea'
|
ZIM_ID = 'bc4f8cdf-5626-2b13-3860-0033deddfbea'
|
||||||
|
|
||||||
|
_data_dir = pathlib.Path(__file__).parent / 'data'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope='module')
|
@pytest.fixture(autouse=True, scope='module')
|
||||||
def fixture_kiwix_urls():
|
def fixture_kiwix_urls():
|
||||||
@ -41,107 +43,88 @@ def make_request(request, view, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def kiwix_patch():
|
def fiture_kiwix_patch():
|
||||||
"""Patch kiwix methods."""
|
"""Patch kiwix methods."""
|
||||||
with patch('plinth.modules.kiwix.privileged.list_content_packages'
|
with patch(
|
||||||
) as list_libraries:
|
'plinth.modules.kiwix.privileged.list_packages') as list_libraries:
|
||||||
list_libraries.return_value = {
|
list_libraries.return_value = {
|
||||||
ZIM_ID: {
|
ZIM_ID: {
|
||||||
'title': 'TestExistingContentPackage',
|
'title': 'TestExistingPackage',
|
||||||
'description': 'A sample content package',
|
'description': 'A sample content package',
|
||||||
'path': 'test_existing_content_package'
|
'path': 'test_existing_package'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@patch('tempfile.TemporaryDirectory')
|
||||||
def storage_info_patch():
|
@patch('plinth.modules.kiwix.privileged.add_package')
|
||||||
"""Patch storage info method."""
|
def test_add_package(add_package, temp_dir_class, rf, tmp_path):
|
||||||
with patch('plinth.modules.storage.get_mount_info') as get_mount_info:
|
|
||||||
get_mount_info.return_value = {'free_bytes': 1000000000000}
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
@patch('plinth.modules.kiwix.privileged.add_content')
|
|
||||||
def test_add_content_package(add_content, rf):
|
|
||||||
"""Test that adding content view works."""
|
"""Test that adding content view works."""
|
||||||
with open('plinth/modules/kiwix/tests/data/FreedomBox.zim',
|
temp_dir_class.return_value.__enter__.return_value = str(tmp_path)
|
||||||
'rb') as zim_file:
|
with open(_data_dir / 'FreedomBox.zim', 'rb') as zim_file:
|
||||||
post_data = {
|
post_data = {'kiwix-file': zim_file}
|
||||||
'kiwix-file': zim_file,
|
request = rf.post('', data=post_data)
|
||||||
}
|
|
||||||
post_data = encode_multipart('BoUnDaRyStRiNg', post_data)
|
|
||||||
request = rf.post(
|
|
||||||
'', data=post_data, content_type='multipart/form-data; '
|
|
||||||
'boundary=BoUnDaRyStRiNg')
|
|
||||||
response, messages = make_request(request,
|
response, messages = make_request(request,
|
||||||
views.AddContentView.as_view())
|
views.AddPackageView.as_view())
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.url == urls.reverse('kiwix:index')
|
assert response.url == urls.reverse('kiwix:index')
|
||||||
assert list(messages)[0].message == 'Content package added.'
|
assert list(messages)[0].message == 'Content package added.'
|
||||||
add_content.assert_has_calls([call('/tmp/FreedomBox.zim')])
|
add_package.assert_has_calls([call(f'{tmp_path}/FreedomBox.zim')])
|
||||||
|
|
||||||
|
|
||||||
@patch('plinth.modules.kiwix.privileged.add_content')
|
@patch('plinth.modules.kiwix.privileged.add_package')
|
||||||
def test_add_content_package_failed(add_content, rf):
|
def test_add_package_failed(add_package, rf):
|
||||||
"""Test that adding content package fails in case of an error."""
|
"""Test that adding content package fails in case of an error."""
|
||||||
add_content.side_effect = RuntimeError('TestError')
|
add_package.side_effect = RuntimeError('TestError')
|
||||||
with open('plinth/modules/kiwix/tests/data/FreedomBox.zim',
|
with open(_data_dir / 'FreedomBox.zim', 'rb') as zim_file:
|
||||||
'rb') as zim_file:
|
post_data = {'kiwix-file': zim_file}
|
||||||
post_data = {
|
request = rf.post('', data=post_data)
|
||||||
'kiwix-file': zim_file,
|
|
||||||
}
|
|
||||||
post_data = encode_multipart('BoUnDaRyStRiNg', post_data)
|
|
||||||
request = rf.post(
|
|
||||||
'', data=post_data, content_type='multipart/form-data; '
|
|
||||||
'boundary=BoUnDaRyStRiNg')
|
|
||||||
response, messages = make_request(request,
|
response, messages = make_request(request,
|
||||||
views.AddContentView.as_view())
|
views.AddPackageView.as_view())
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.url == urls.reverse('kiwix:index')
|
assert response.url == urls.reverse('kiwix:index')
|
||||||
assert list(messages)[0].message == \
|
assert list(messages)[0].message == 'Failed to add content package.'
|
||||||
'Failed to add content package.'
|
|
||||||
|
|
||||||
|
|
||||||
@patch('plinth.app.App.get')
|
@patch('plinth.app.App.get')
|
||||||
def test_delete_package_confirmation_view(_app, rf):
|
def test_delete_package_confirmation_view(_app, rf):
|
||||||
"""Test that deleting package confirmation shows correct title."""
|
"""Test that deleting content confirmation shows correct title."""
|
||||||
response, _ = make_request(rf.get(''), views.delete_content, zim_id=ZIM_ID)
|
response, _ = make_request(rf.get(''), views.delete_package, zim_id=ZIM_ID)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.context_data['name'] == 'TestExistingContentPackage'
|
assert response.context_data['name'] == 'TestExistingPackage'
|
||||||
|
|
||||||
|
|
||||||
@patch('plinth.modules.kiwix.privileged.delete_content_package')
|
@patch('plinth.modules.kiwix.privileged.delete_package')
|
||||||
@patch('plinth.app.App.get')
|
@patch('plinth.app.App.get')
|
||||||
def test_delete_content_package(_app, delete_content_package, rf):
|
def test_delete_package(_app, delete_package, rf):
|
||||||
"""Test that deleting a content package works."""
|
"""Test that deleting a content package works."""
|
||||||
response, messages = make_request(rf.post(''), views.delete_content,
|
response, messages = make_request(rf.post(''), views.delete_package,
|
||||||
zim_id=ZIM_ID)
|
zim_id=ZIM_ID)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.url == urls.reverse('kiwix:index')
|
assert response.url == urls.reverse('kiwix:index')
|
||||||
assert list(messages)[0].message == 'TestExistingContentPackage deleted.'
|
assert list(messages)[0].message == 'TestExistingPackage deleted.'
|
||||||
delete_content_package.assert_has_calls([call(ZIM_ID)])
|
delete_package.assert_has_calls([call(ZIM_ID)])
|
||||||
|
|
||||||
|
|
||||||
@patch('plinth.modules.kiwix.privileged.delete_content_package')
|
@patch('plinth.modules.kiwix.privileged.delete_package')
|
||||||
def test_delete_content_package_error(delete_content_package, rf):
|
def test_delete_package_error(delete_package, rf):
|
||||||
"""Test that deleting a content package shows an error when operation fails."""
|
"""Test that deleting content shows an error when operation fails."""
|
||||||
delete_content_package.side_effect = ValueError('TestError')
|
delete_package.side_effect = ValueError('TestError')
|
||||||
response, messages = make_request(rf.post(''), views.delete_content,
|
response, messages = make_request(rf.post(''), views.delete_package,
|
||||||
zim_id=ZIM_ID)
|
zim_id=ZIM_ID)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.url == urls.reverse('kiwix:index')
|
assert response.url == urls.reverse('kiwix:index')
|
||||||
assert list(messages)[0].message == \
|
assert list(messages)[0].message == \
|
||||||
'Could not delete TestExistingContentPackage: TestError'
|
'Could not delete TestExistingPackage: TestError'
|
||||||
|
|
||||||
|
|
||||||
def test_delete_content_package_non_existing(rf):
|
def test_delete_package_non_existing(rf):
|
||||||
"""Test that deleting a content package shows error when operation fails."""
|
"""Test that deleting content shows error when operation fails."""
|
||||||
with pytest.raises(Http404):
|
with pytest.raises(Http404):
|
||||||
make_request(rf.post(''), views.delete_content,
|
make_request(rf.post(''), views.delete_package,
|
||||||
zim_id='NonExistentZimId')
|
zim_id='NonExistentZimId')
|
||||||
|
|
||||||
with pytest.raises(Http404):
|
with pytest.raises(Http404):
|
||||||
make_request(rf.get(''), views.delete_content,
|
make_request(rf.get(''), views.delete_package,
|
||||||
zim_id='NonExistentZimId')
|
zim_id='NonExistentZimId')
|
||||||
|
|||||||
@ -9,8 +9,8 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r'^apps/kiwix/$', views.KiwixAppView.as_view(), name='index'),
|
re_path(r'^apps/kiwix/$', views.KiwixAppView.as_view(), name='index'),
|
||||||
re_path(r'^apps/kiwix/content/add/$', views.AddContentView.as_view(),
|
re_path(r'^apps/kiwix/package/add/$', views.AddPackageView.as_view(),
|
||||||
name='add-content'),
|
name='add-package'),
|
||||||
re_path(r'^apps/kiwix/content/(?P<zim_id>[a-zA-Z0-9-]+)/delete/$',
|
re_path(r'^apps/kiwix/package/(?P<zim_id>[a-zA-Z0-9-]+)/delete/$',
|
||||||
views.delete_content, name='delete-content'),
|
views.delete_package, name='delete-package'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -4,6 +4,7 @@ Views for the Kiwix module.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
@ -27,21 +28,23 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class KiwixAppView(views.AppView):
|
class KiwixAppView(views.AppView):
|
||||||
"""Serve configuration form."""
|
"""Serve configuration form."""
|
||||||
|
|
||||||
app_id = 'kiwix'
|
app_id = 'kiwix'
|
||||||
template_name = 'kiwix.html'
|
template_name = 'kiwix.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Return additional context for rendering the template."""
|
"""Return additional context for rendering the template."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['packages'] = privileged.list_content_packages()
|
context['packages'] = privileged.list_packages()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class AddContentView(SuccessMessageMixin, FormView):
|
class AddPackageView(SuccessMessageMixin, FormView):
|
||||||
"""View to add content in the form of ZIM files."""
|
"""View to add content package in the form of ZIM files."""
|
||||||
form_class = forms.AddContentForm
|
|
||||||
|
form_class = forms.AddPackageForm
|
||||||
prefix = 'kiwix'
|
prefix = 'kiwix'
|
||||||
template_name = 'add-content-package.html'
|
template_name = 'kiwix-add-package.html'
|
||||||
success_url = reverse_lazy('kiwix:index')
|
success_url = reverse_lazy('kiwix:index')
|
||||||
success_message = _('Content package added.')
|
success_message = _('Content package added.')
|
||||||
|
|
||||||
@ -66,23 +69,26 @@ class AddContentView(SuccessMessageMixin, FormView):
|
|||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Store the uploaded file."""
|
"""Store the uploaded file."""
|
||||||
multipart_file = self.request.FILES['kiwix-file']
|
multipart_file = self.request.FILES['kiwix-file']
|
||||||
zim_file_name = '/tmp/' + multipart_file.name
|
|
||||||
with open(zim_file_name, 'wb+') as zim_file:
|
|
||||||
for chunk in multipart_file.chunks():
|
|
||||||
zim_file.write(chunk)
|
|
||||||
|
|
||||||
try:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
privileged.add_content(zim_file_name)
|
zim_file_name = temp_dir + '/' + multipart_file.name
|
||||||
except Exception:
|
with open(zim_file_name, 'wb+') as zim_file:
|
||||||
messages.error(self.request, _('Failed to add content package.'))
|
for chunk in multipart_file.chunks():
|
||||||
return redirect(reverse_lazy('kiwix:index'))
|
zim_file.write(chunk)
|
||||||
|
|
||||||
|
try:
|
||||||
|
privileged.add_package(zim_file_name)
|
||||||
|
except Exception:
|
||||||
|
messages.error(self.request,
|
||||||
|
_('Failed to add content package.'))
|
||||||
|
return redirect(reverse_lazy('kiwix:index'))
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
def delete_content(request, zim_id):
|
def delete_package(request, zim_id):
|
||||||
"""View to delete a library."""
|
"""View to delete a library."""
|
||||||
packages = privileged.list_content_packages()
|
packages = privileged.list_packages()
|
||||||
if zim_id not in packages:
|
if zim_id not in packages:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
@ -90,8 +96,8 @@ def delete_content(request, zim_id):
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
try:
|
try:
|
||||||
privileged.delete_content_package(zim_id)
|
privileged.delete_package(zim_id)
|
||||||
messages.success(request, _(f'{name} deleted.'))
|
messages.success(request, _('{name} deleted.').format(name=name))
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
@ -99,7 +105,7 @@ def delete_content(request, zim_id):
|
|||||||
name=name, error=error))
|
name=name, error=error))
|
||||||
return redirect(reverse_lazy('kiwix:index'))
|
return redirect(reverse_lazy('kiwix:index'))
|
||||||
|
|
||||||
return TemplateResponse(request, 'delete-content-package.html', {
|
return TemplateResponse(request, 'kiwix-delete-package.html', {
|
||||||
'title': app_module.App.get('kiwix').info.name,
|
'title': app_module.App.get('kiwix').info.name,
|
||||||
'name': name
|
'name': name
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user