diff --git a/debian/copyright b/debian/copyright
index 6e8cf2899..c6cbdcb28 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -132,8 +132,8 @@ Files: plinth/modules/janus/static/icons/janus.png
Copyright: 2014-2022 Meetecho
License: GPL-3 with OpenSSL exception
-Files: static/themes/default/icons/kiwix.png
- static/themes/default/icons/kiwix.svg
+Files: plinth/modules/kiwix/static/icons/kiwix.png
+ plinth/modules/kiwix/static/icons/kiwix.svg
Copyright: 2020 The other Kiwix guy
Comment: https://commons.wikimedia.org/wiki/File:Kiwix_logo_v3.svg
License: CC-BY-SA-4.0
diff --git a/plinth/modules/kiwix/__init__.py b/plinth/modules/kiwix/__init__.py
index 7243ddcb2..af21083f1 100644
--- a/plinth/modules/kiwix/__init__.py
+++ b/plinth/modules/kiwix/__init__.py
@@ -5,13 +5,14 @@ FreedomBox app for Kiwix content server.
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.daemon import Daemon
from plinth.modules.apache.components import Webserver
from plinth.modules.backups.components import BackupRestore
-from plinth.modules.kiwix import manifest
-from plinth.modules.firewall.components import Firewall, FirewallLocalProtection
+from plinth.modules.firewall.components import (Firewall,
+ FirewallLocalProtection)
from plinth.modules.users.components import UsersAndGroups
from . import manifest, privileged
@@ -28,11 +29,13 @@ _description = [
Educational materials: PHET, TED Ed, Vikidia
eBooks: Project Gutenberg
Magazines: Low-tech Magazine
- ''')
+ '''),
+ _('You can download content packages from the Kiwix '
+ 'project or create your own.'),
]
-SYSTEM_USER = 'kiwix'
-
class KiwixApp(app_module.App):
"""FreedomBox app for Kiwix."""
@@ -116,6 +119,6 @@ class KiwixApp(app_module.App):
def validate_file_name(file_name: str):
- """Check if the content archive file has a valid extension."""
- if not file_name.endswith(".zim"):
- raise ValueError(f"Expected a ZIM file. Found {file_name}")
+ """Check if the content package file has a valid extension."""
+ if not file_name.endswith('.zim'):
+ raise ValueError(f'Expected a ZIM file. Found {file_name}')
diff --git a/plinth/modules/kiwix/data/usr/lib/systemd/system/kiwix-server-freedombox.service b/plinth/modules/kiwix/data/usr/lib/systemd/system/kiwix-server-freedombox.service
index 7abe49c60..a1ceaa2e9 100644
--- a/plinth/modules/kiwix/data/usr/lib/systemd/system/kiwix-server-freedombox.service
+++ b/plinth/modules/kiwix/data/usr/lib/systemd/system/kiwix-server-freedombox.service
@@ -13,7 +13,7 @@ Environment=HOME="/var/lib/kiwix-server-freedombox"
Environment=LIBRARY_PATH="/var/lib/kiwix-server-freedombox/library_zim.xml"
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 '\n\n ' > \"${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
ExecReload=/bin/kill -HUP $MAINPID
DynamicUser=yes
diff --git a/plinth/modules/kiwix/forms.py b/plinth/modules/kiwix/forms.py
index c926c36ca..d41629d24 100644
--- a/plinth/modules/kiwix/forms.py
+++ b/plinth/modules/kiwix/forms.py
@@ -8,18 +8,21 @@ from django.core import validators
from django.utils.translation import gettext_lazy as _
from plinth import cfg
+from plinth.utils import format_lazy
from .privileged import KIWIX_HOME
-class AddContentForm(forms.Form):
- """Form to create an empty library."""
+class AddPackageForm(forms.Form):
+ """Form to upload a content package to a library."""
# Would be nice to have a progress bar when uploading large files.
file = forms.FileField(
label=_('Upload File'), required=True, validators=[
validators.FileExtensionValidator(
['zim'], _('Content packages have to be in .zim format'))
- ], help_text=_(f'''Uploaded ZIM files will be stored under
- {KIWIX_HOME}/content on your {cfg.box_name}. If Kiwix fails to add the file,
- it will be deleted immediately to save disk space.'''))
+ ], help_text=format_lazy(
+ _('Uploaded ZIM files will be stored under {kiwix_home}/content '
+ '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))
diff --git a/plinth/modules/kiwix/manifest.py b/plinth/modules/kiwix/manifest.py
index e7e3bc3d8..bf45ebe8d 100644
--- a/plinth/modules/kiwix/manifest.py
+++ b/plinth/modules/kiwix/manifest.py
@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
from plinth.clients import validate
clients = validate([{
- 'name': _('kiwix'),
+ 'name': _('Kiwix'),
'platforms': [{
'type': 'web',
'url': '/kiwix'
diff --git a/plinth/modules/kiwix/privileged.py b/plinth/modules/kiwix/privileged.py
index 5a47db19b..43cabd5fd 100644
--- a/plinth/modules/kiwix/privileged.py
+++ b/plinth/modules/kiwix/privileged.py
@@ -3,10 +3,11 @@
Privileged actions for Kiwix content server.
"""
-import subprocess
+import os
import pathlib
import shutil
-import xml.etree.ElementTree as ET
+import subprocess
+from xml.etree import ElementTree
from plinth import action_utils
from plinth.actions import privileged
@@ -19,19 +20,19 @@ CONTENT_DIR = KIWIX_HOME / 'content'
@privileged
-def add_content(file_name: str):
+def add_package(file_name: str):
"""Adds a content package to Kiwix.
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
- Provide a link to the ZIM file
- Provide a magnet link to the ZIM file
- The commandline download manager aria2c is a dependency of kiwix-tools.
- aria2c is used for both HTTP and Magnet downloads.
- """
+ The commandline download manager aria2c is a dependency of kiwix-tools.
+ aria2c is used for both HTTP and Magnet downloads.
+ """
kiwix.validate_file_name(file_name)
# 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
CONTENT_DIR.mkdir(exist_ok=True)
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)
_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])
# 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
-def uninstall():
+def uninstall() -> None:
"""Remove all content during uninstall."""
- shutil.rmtree(str(CONTENT_DIR))
- LIBRARY_FILE.unlink()
+ shutil.rmtree(str(CONTENT_DIR), ignore_errors=True)
+ LIBRARY_FILE.unlink(missing_ok=True)
@privileged
-def list_content_packages() -> dict[str, dict]:
- library = ET.parse(LIBRARY_FILE).getroot()
+def list_packages() -> dict[str, dict[str, str]]:
+ """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.
- return {
- book.attrib['id']: {
- 'title': book.attrib['title'],
- 'description': book.attrib['description'],
- # strip '.zim' from the path
- 'path': book.attrib['path'].split('/')[-1][:-4].lower()
- }
- for book in library
- }
+ books = {}
+ for book in library:
+ path = book.attrib['path'].split('/')[-1]
+ path = path.removesuffix('.zim').lower() # Strip '.zim' from the path
+ try:
+ books[book.attrib['id']] = {
+ 'title': book.attrib['title'],
+ 'description': book.attrib['description'],
+ 'path': path
+ }
+ except KeyError:
+ pass # Ignore entries that don't have expected properties
+
+ return books
@privileged
-def delete_content_package(zim_id: str):
- library = ET.parse(LIBRARY_FILE).getroot()
+def delete_package(zim_id: str):
+ """Remove a content package from the library file."""
+ library = ElementTree.parse(LIBRARY_FILE).getroot()
for book in library:
- if book.attrib['id'] == zim_id:
+ try:
+ if book.attrib['id'] != zim_id:
+ continue
+
subprocess.check_call(
['kiwix-manage', LIBRARY_FILE, 'remove', zim_id])
(KIWIX_HOME / book.attrib['path']).unlink()
- action_utils.service_restart('kiwix-server-freedombox')
+ action_utils.service_try_restart('kiwix-server-freedombox')
return
+ except KeyError: # Expected properties not found on elements
+ pass
diff --git a/plinth/modules/kiwix/static/icons/kiwix.png b/plinth/modules/kiwix/static/icons/kiwix.png
index 021762824..9abe720c8 100644
Binary files a/plinth/modules/kiwix/static/icons/kiwix.png and b/plinth/modules/kiwix/static/icons/kiwix.png differ
diff --git a/plinth/modules/kiwix/static/icons/kiwix.svg b/plinth/modules/kiwix/static/icons/kiwix.svg
index 566b1064b..f944fd9ff 100644
--- a/plinth/modules/kiwix/static/icons/kiwix.svg
+++ b/plinth/modules/kiwix/static/icons/kiwix.svg
@@ -1,20 +1,48 @@
-
+
-
-
-
+
diff --git a/plinth/modules/kiwix/templates/add-content-package.html b/plinth/modules/kiwix/templates/add-content-package.html
deleted file mode 100644
index 53011c404..000000000
--- a/plinth/modules/kiwix/templates/add-content-package.html
+++ /dev/null
@@ -1,58 +0,0 @@
-{% extends "base.html" %}
-{% comment %}
-# SPDX-License-Identifier: AGPL-3.0-or-later
-{% endcomment %}
-
-{% load bootstrap %}
-{% load i18n %}
-
-{% block content %}
-
- {{ title }}
-
-
- {% blocktrans %}
- You can download
- content packages from the Kiwix project or
- create your own.
- {% endblocktrans %}
-
-
-
- {% blocktrans %}
- Content packages can be added in the following ways:
-
- upload a ZIM file
-
-
-
-
-
-
-
-
- {% endblocktrans %}
-
-
- {% if max_filesize %}
-
-
-
- {% blocktrans trimmed %}
- You have {{ max_filesize }} of free disk space available.
- {% endblocktrans %}
-
- {% endif %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/kiwix/templates/kiwix-add-package.html b/plinth/modules/kiwix/templates/kiwix-add-package.html
new file mode 100644
index 000000000..1e64d958e
--- /dev/null
+++ b/plinth/modules/kiwix/templates/kiwix-add-package.html
@@ -0,0 +1,39 @@
+{% extends "base.html" %}
+{% comment %}
+# SPDX-License-Identifier: AGPL-3.0-or-later
+{% endcomment %}
+
+{% load bootstrap %}
+{% load i18n %}
+
+{% block content %}
+
+ {{ title }}
+
+
+ {% blocktrans trimmed %}
+ You can download content packages from the Kiwix
+ project or create your own.
+ {% endblocktrans %}
+
+
+ {% if max_filesize %}
+
+ {% blocktrans trimmed %}
+ You have {{ max_filesize }} of free disk space available.
+ {% endblocktrans %}
+
+ {% endif %}
+
+
+
+{% endblock %}
diff --git a/plinth/modules/kiwix/templates/delete-content-package.html b/plinth/modules/kiwix/templates/kiwix-delete-package.html
similarity index 93%
rename from plinth/modules/kiwix/templates/delete-content-package.html
rename to plinth/modules/kiwix/templates/kiwix-delete-package.html
index 70f209af7..44159849a 100644
--- a/plinth/modules/kiwix/templates/delete-content-package.html
+++ b/plinth/modules/kiwix/templates/kiwix-delete-package.html
@@ -15,7 +15,8 @@
{% 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 %}
diff --git a/plinth/modules/kiwix/templates/kiwix.html b/plinth/modules/kiwix/templates/kiwix.html
index 45fb03c7d..e3a30a822 100644
--- a/plinth/modules/kiwix/templates/kiwix.html
+++ b/plinth/modules/kiwix/templates/kiwix.html
@@ -8,32 +8,31 @@
{% block configuration %}
{{ block.super }}
- {% trans "Manage Content" %}
+ {% trans "Manage Content Packages" %}
{% if not packages %}
-
{% trans 'No content available.' %}
+
{% trans 'No content packages available.' %}
{% else %}
{% for id, package in packages.items %}
-
+ title="{{ package.description }}">
{{ package.title }}
-
diff --git a/plinth/modules/kiwix/tests/data/invalid.zim b/plinth/modules/kiwix/tests/data/invalid.zim
index bd7fab739..944c593a9 100644
--- a/plinth/modules/kiwix/tests/data/invalid.zim
+++ b/plinth/modules/kiwix/tests/data/invalid.zim
@@ -1 +1 @@
-Nothing to see here.
\ No newline at end of file
+Nothing to see here.
diff --git a/plinth/modules/kiwix/tests/test_functional.py b/plinth/modules/kiwix/tests/test_functional.py
index 02d364939..91c8ea682 100644
--- a/plinth/modules/kiwix/tests/test_functional.py
+++ b/plinth/modules/kiwix/tests/test_functional.py
@@ -3,94 +3,97 @@
Functional, browser based tests for Kiwix app.
"""
-import pkg_resources
+import pathlib
+from time import sleep
+
import pytest
-from time import sleep
-from plinth.modules.kiwix.tests.test_privileged import ZIM_ID
-
from plinth.tests import functional
+from .test_privileged import ZIM_ID
+
pytestmark = [pytest.mark.apps, pytest.mark.sso, pytest.mark.kiwix]
_default_url = functional.config['DEFAULT']['url']
-ZIM_ID = 'bc4f8cdf-5626-2b13-3860-0033deddfbea'
+_data_dir = pathlib.Path(__file__).parent / 'data'
class TestKiwixApp(functional.BaseAppTests):
+ """Basic functional tests for Kiwix app."""
+
app_name = 'kiwix'
has_service = 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."""
functional.app_enable(session_browser, 'kiwix')
- zim_file = pkg_resources.resource_filename(
- 'plinth.modules.kiwix.tests', 'data/FreedomBox.zim')
- _add_content_package(session_browser, zim_file)
- assert _is_content_package_listed(session_browser, 'freedombox')
- assert _is_content_package_available(session_browser, 'FreedomBox')
+ zim_file = _data_dir / 'FreedomBox.zim'
+ _add_package(session_browser, str(zim_file))
+ assert _is_package_listed(session_browser, 'freedombox')
+ assert _is_package_available(session_browser, 'FreedomBox')
- _delete_content_package(session_browser, ZIM_ID)
- assert not _is_content_package_listed(session_browser, 'freedombox')
- assert not _is_content_package_available(session_browser, 'FreedomBox')
+ _delete_package(session_browser, ZIM_ID)
+ assert not _is_package_listed(session_browser, 'freedombox')
+ assert not _is_package_available(session_browser, 'FreedomBox')
@pytest.mark.backups
def test_backup_restore(self, session_browser):
"""Test backing up and restoring."""
functional.app_enable(session_browser, 'kiwix')
- zim_file = pkg_resources.resource_filename(
- 'plinth.modules.kiwix.tests', 'data/FreedomBox.zim')
- _add_content_package(session_browser, zim_file)
+ zim_file = _data_dir / 'FreedomBox.zim'
+ _add_package(session_browser, str(zim_file))
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')
- assert _is_content_package_listed(session_browser, 'freedombox')
- assert _is_content_package_available(session_browser, 'FreedomBox')
+ assert _is_package_listed(session_browser, 'freedombox')
+ assert _is_package_available(session_browser, 'FreedomBox')
def test_add_invalid_zim_file(self, session_browser):
"""Test handling of invalid zim files."""
functional.app_enable(session_browser, 'kiwix')
- zim_file = pkg_resources.resource_filename(
- 'plinth.modules.kiwix.tests', 'data/invalid.zim')
- _add_content_package(session_browser, zim_file)
+ zim_file = _data_dir / 'invalid.zim'
+ _add_package(session_browser, str(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):
- browser.links.find_by_href('/plinth/apps/kiwix/content/add/').first.click()
+def _add_package(browser, file_name):
+ """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)
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')
sleep(1) # Allow time for the books to appear
titles = browser.find_by_id('book__title')
- print(len(titles))
- print([title.value for title in titles])
- return any(map(lambda e: e.value == title, titles))
+ return any(element.value == title for element in 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')
links_found = browser.links.find_by_partial_href(f'/kiwix/viewer#{name}')
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')
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:
raise ValueError('ZIM file missing!')
+
link.first.click()
functional.submit(browser, form_class='form-delete')
diff --git a/plinth/modules/kiwix/tests/test_privileged.py b/plinth/modules/kiwix/tests/test_privileged.py
index 3050c70b3..34b98707d 100644
--- a/plinth/modules/kiwix/tests/test_privileged.py
+++ b/plinth/modules/kiwix/tests/test_privileged.py
@@ -4,7 +4,7 @@ Test module for Kiwix actions.
"""
import pathlib
-import pkg_resources
+import shutil
from unittest.mock import patch
import pytest
@@ -22,65 +22,57 @@ ZIM_ID = 'bc4f8cdf-5626-2b13-3860-0033deddfbea'
@pytest.fixture(autouse=True)
-def fixture_kiwix_home(tmpdir):
- """Set Kiwix home to a new temporary directory
- initialized with an empty library file."""
- privileged.KIWIX_HOME = pathlib.Path(str(tmpdir / 'kiwix'))
+def fixture_kiwix_home(tmp_path):
+ """Create a new Kiwix home in a new temporary directory.
+
+ Initialize with a sample, valid library file.
+ """
+ privileged.KIWIX_HOME = tmp_path / 'kiwix'
privileged.KIWIX_HOME.mkdir()
privileged.CONTENT_DIR = privileged.KIWIX_HOME / 'content'
privileged.CONTENT_DIR.mkdir()
privileged.LIBRARY_FILE = privileged.KIWIX_HOME / 'library_zim.xml'
- with open(privileged.LIBRARY_FILE, 'w', encoding='utf_8') as library_file:
- library_file.write(EMPTY_LIBRARY_CONTENTS)
+ source_file = pathlib.Path(__file__).parent / 'data/sample_library_zim.xml'
+ shutil.copy(source_file, privileged.LIBRARY_FILE)
@pytest.fixture(autouse=True)
def fixture_patch():
"""Patch some underlying methods."""
- with patch('subprocess.check_call'), patch('subprocess.run'):
+ with patch('subprocess.check_call'), patch('subprocess.run'), patch(
+ 'os.chown'):
yield
-def test_add_content(tmpdir):
+def test_add_package(tmp_path):
"""Test adding a content package to Kiwix."""
- some_dir = tmpdir / 'some' / 'dir'
- pathlib.Path(some_dir).mkdir(parents=True, exist_ok=True)
+ some_dir = tmp_path / 'some' / 'dir'
+ some_dir.mkdir(parents=True, exist_ok=True)
zim_file_name = 'wikipedia_en_all_maxi_2022-05.zim'
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 not orig_file.exists()
-def test_list_content_packages():
+def test_list_packages():
"""Test listing the content packages from a library file."""
- privileged.LIBRARY_FILE = pkg_resources.resource_filename(
- 'plinth.modules.kiwix.tests', 'data/sample_library_zim.xml')
- content_packages = privileged.list_content_packages()
- assert content_packages[ZIM_ID] == {
+ content = privileged.list_packages()
+ assert content[ZIM_ID] == {
'title': 'FreedomBox',
'description': 'A sample content archive',
'path': 'freedombox'
}
-def test_delete_content_package():
+def test_delete_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.touch()
- privileged.delete_content_package(ZIM_ID)
+ privileged.delete_package(ZIM_ID)
assert not zim_file.exists()
# Cannot check that the book is removed from library_zim.xml
diff --git a/plinth/modules/kiwix/tests/test_validations.py b/plinth/modules/kiwix/tests/test_validations.py
index f4113e131..5be411a25 100644
--- a/plinth/modules/kiwix/tests/test_validations.py
+++ b/plinth/modules/kiwix/tests/test_validations.py
@@ -3,19 +3,18 @@
Test module for Kiwix validations.
"""
-import unittest
+import pytest
+
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):
- self.assertRaises(ValueError,
- lambda: kiwix.validate_file_name('wikipedia.zip'))
+ # We don't support the legacy format of split zim files.
+ with pytest.raises(ValueError):
+ kiwix.validate_file_name('wikipedia_en_all_maxi_2022-05.zima')
- # We don't support the legacy format of split zim files.
- 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')
+ kiwix.validate_file_name('wikipedia_en_all_maxi_2022-05.zim')
diff --git a/plinth/modules/kiwix/tests/test_views.py b/plinth/modules/kiwix/tests/test_views.py
index 099ccf290..fc77cd22d 100644
--- a/plinth/modules/kiwix/tests/test_views.py
+++ b/plinth/modules/kiwix/tests/test_views.py
@@ -3,15 +3,15 @@
Test module for Kiwix views.
"""
-from plinth import module_loader
-from django import urls
+import pathlib
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
+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
# 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'
+_data_dir = pathlib.Path(__file__).parent / 'data'
+
@pytest.fixture(autouse=True, scope='module')
def fixture_kiwix_urls():
@@ -41,107 +43,88 @@ def make_request(request, view, **kwargs):
@pytest.fixture(autouse=True)
-def kiwix_patch():
+def fiture_kiwix_patch():
"""Patch kiwix methods."""
- with patch('plinth.modules.kiwix.privileged.list_content_packages'
- ) as list_libraries:
+ with patch(
+ 'plinth.modules.kiwix.privileged.list_packages') as list_libraries:
list_libraries.return_value = {
ZIM_ID: {
- 'title': 'TestExistingContentPackage',
+ 'title': 'TestExistingPackage',
'description': 'A sample content package',
- 'path': 'test_existing_content_package'
+ 'path': 'test_existing_package'
}
}
yield
-@pytest.fixture()
-def storage_info_patch():
- """Patch storage info method."""
- 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):
+@patch('tempfile.TemporaryDirectory')
+@patch('plinth.modules.kiwix.privileged.add_package')
+def test_add_package(add_package, temp_dir_class, rf, tmp_path):
"""Test that adding content view works."""
- with open('plinth/modules/kiwix/tests/data/FreedomBox.zim',
- 'rb') as zim_file:
- 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')
+ temp_dir_class.return_value.__enter__.return_value = str(tmp_path)
+ with open(_data_dir / 'FreedomBox.zim', 'rb') as zim_file:
+ post_data = {'kiwix-file': zim_file}
+ request = rf.post('', data=post_data)
response, messages = make_request(request,
- views.AddContentView.as_view())
+ views.AddPackageView.as_view())
assert response.status_code == 302
assert response.url == urls.reverse('kiwix:index')
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')
-def test_add_content_package_failed(add_content, rf):
+@patch('plinth.modules.kiwix.privileged.add_package')
+def test_add_package_failed(add_package, rf):
"""Test that adding content package fails in case of an error."""
- add_content.side_effect = RuntimeError('TestError')
- with open('plinth/modules/kiwix/tests/data/FreedomBox.zim',
- 'rb') as zim_file:
- 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')
+ add_package.side_effect = RuntimeError('TestError')
+ with open(_data_dir / 'FreedomBox.zim', 'rb') as zim_file:
+ post_data = {'kiwix-file': zim_file}
+ request = rf.post('', data=post_data)
response, messages = make_request(request,
- views.AddContentView.as_view())
+ views.AddPackageView.as_view())
assert response.status_code == 302
assert response.url == urls.reverse('kiwix:index')
- assert list(messages)[0].message == \
- 'Failed to add content package.'
+ assert list(messages)[0].message == 'Failed to add content package.'
@patch('plinth.app.App.get')
def test_delete_package_confirmation_view(_app, rf):
- """Test that deleting package confirmation shows correct title."""
- response, _ = make_request(rf.get(''), views.delete_content, zim_id=ZIM_ID)
+ """Test that deleting content confirmation shows correct title."""
+ response, _ = make_request(rf.get(''), views.delete_package, zim_id=ZIM_ID)
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')
-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."""
- response, messages = make_request(rf.post(''), views.delete_content,
+ response, messages = make_request(rf.post(''), views.delete_package,
zim_id=ZIM_ID)
assert response.status_code == 302
assert response.url == urls.reverse('kiwix:index')
- assert list(messages)[0].message == 'TestExistingContentPackage deleted.'
- delete_content_package.assert_has_calls([call(ZIM_ID)])
+ assert list(messages)[0].message == 'TestExistingPackage deleted.'
+ delete_package.assert_has_calls([call(ZIM_ID)])
-@patch('plinth.modules.kiwix.privileged.delete_content_package')
-def test_delete_content_package_error(delete_content_package, rf):
- """Test that deleting a content package shows an error when operation fails."""
- delete_content_package.side_effect = ValueError('TestError')
- response, messages = make_request(rf.post(''), views.delete_content,
+@patch('plinth.modules.kiwix.privileged.delete_package')
+def test_delete_package_error(delete_package, rf):
+ """Test that deleting content shows an error when operation fails."""
+ delete_package.side_effect = ValueError('TestError')
+ response, messages = make_request(rf.post(''), views.delete_package,
zim_id=ZIM_ID)
assert response.status_code == 302
assert response.url == urls.reverse('kiwix:index')
assert list(messages)[0].message == \
- 'Could not delete TestExistingContentPackage: TestError'
+ 'Could not delete TestExistingPackage: TestError'
-def test_delete_content_package_non_existing(rf):
- """Test that deleting a content package shows error when operation fails."""
+def test_delete_package_non_existing(rf):
+ """Test that deleting content shows error when operation fails."""
with pytest.raises(Http404):
- make_request(rf.post(''), views.delete_content,
+ make_request(rf.post(''), views.delete_package,
zim_id='NonExistentZimId')
with pytest.raises(Http404):
- make_request(rf.get(''), views.delete_content,
+ make_request(rf.get(''), views.delete_package,
zim_id='NonExistentZimId')
diff --git a/plinth/modules/kiwix/urls.py b/plinth/modules/kiwix/urls.py
index 1170d12b5..892a46d00 100644
--- a/plinth/modules/kiwix/urls.py
+++ b/plinth/modules/kiwix/urls.py
@@ -9,8 +9,8 @@ from . import views
urlpatterns = [
re_path(r'^apps/kiwix/$', views.KiwixAppView.as_view(), name='index'),
- re_path(r'^apps/kiwix/content/add/$', views.AddContentView.as_view(),
- name='add-content'),
- re_path(r'^apps/kiwix/content/(?P[a-zA-Z0-9-]+)/delete/$',
- views.delete_content, name='delete-content'),
+ re_path(r'^apps/kiwix/package/add/$', views.AddPackageView.as_view(),
+ name='add-package'),
+ re_path(r'^apps/kiwix/package/(?P[a-zA-Z0-9-]+)/delete/$',
+ views.delete_package, name='delete-package'),
]
diff --git a/plinth/modules/kiwix/views.py b/plinth/modules/kiwix/views.py
index 470a6f6f5..0f7a2dc40 100644
--- a/plinth/modules/kiwix/views.py
+++ b/plinth/modules/kiwix/views.py
@@ -4,6 +4,7 @@ Views for the Kiwix module.
"""
import logging
+import tempfile
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
@@ -27,21 +28,23 @@ logger = logging.getLogger(__name__)
class KiwixAppView(views.AppView):
"""Serve configuration form."""
+
app_id = 'kiwix'
template_name = 'kiwix.html'
def get_context_data(self, **kwargs):
"""Return additional context for rendering the template."""
context = super().get_context_data(**kwargs)
- context['packages'] = privileged.list_content_packages()
+ context['packages'] = privileged.list_packages()
return context
-class AddContentView(SuccessMessageMixin, FormView):
- """View to add content in the form of ZIM files."""
- form_class = forms.AddContentForm
+class AddPackageView(SuccessMessageMixin, FormView):
+ """View to add content package in the form of ZIM files."""
+
+ form_class = forms.AddPackageForm
prefix = 'kiwix'
- template_name = 'add-content-package.html'
+ template_name = 'kiwix-add-package.html'
success_url = reverse_lazy('kiwix:index')
success_message = _('Content package added.')
@@ -66,23 +69,26 @@ class AddContentView(SuccessMessageMixin, FormView):
def form_valid(self, form):
"""Store the uploaded 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:
- privileged.add_content(zim_file_name)
- except Exception:
- messages.error(self.request, _('Failed to add content package.'))
- return redirect(reverse_lazy('kiwix:index'))
+ with tempfile.TemporaryDirectory() as temp_dir:
+ zim_file_name = temp_dir + '/' + multipart_file.name
+ with open(zim_file_name, 'wb+') as zim_file:
+ for chunk in multipart_file.chunks():
+ 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)
-def delete_content(request, zim_id):
+def delete_package(request, zim_id):
"""View to delete a library."""
- packages = privileged.list_content_packages()
+ packages = privileged.list_packages()
if zim_id not in packages:
raise Http404
@@ -90,8 +96,8 @@ def delete_content(request, zim_id):
if request.method == 'POST':
try:
- privileged.delete_content_package(zim_id)
- messages.success(request, _(f'{name} deleted.'))
+ privileged.delete_package(zim_id)
+ messages.success(request, _('{name} deleted.').format(name=name))
except Exception as error:
messages.error(
request,
@@ -99,7 +105,7 @@ def delete_content(request, zim_id):
name=name, error=error))
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,
'name': name
})