Fixed unit tests

pull/846/head
Piero Toffanin 2020-04-02 14:29:27 -04:00
rodzic 37f887a265
commit 652bf1438e
7 zmienionych plików z 112 dodań i 21 usunięć

Wyświetl plik

@ -1,3 +1,8 @@
import os
import tempfile
import zipfile
import shutil
from django.conf.urls import url from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.contrib import messages from django.contrib import messages
@ -9,10 +14,13 @@ from guardian.admin import GuardedModelAdmin
from app.models import PluginDatum from app.models import PluginDatum
from app.models import Preset from app.models import Preset
from app.models import Plugin from app.models import Plugin
from app.plugins import get_plugin_by_name, enable_plugin, disable_plugin, delete_plugin from app.plugins import get_plugin_by_name, enable_plugin, disable_plugin, delete_plugin, valid_plugin, \
get_plugins_persistent_path, clear_plugins_cache, init_plugins
from .models import Project, Task, ImageUpload, Setting, Theme from .models import Project, Task, ImageUpload, Setting, Theme
from django import forms from django import forms
from codemirror2.widgets import CodeMirrorEditor from codemirror2.widgets import CodeMirrorEditor
from webodm import settings
from django.core.files.uploadedfile import InMemoryUploadedFile
admin.site.register(Project, GuardedModelAdmin) admin.site.register(Project, GuardedModelAdmin)
@ -144,8 +152,56 @@ class PluginAdmin(admin.ModelAdmin):
return HttpResponseRedirect(reverse('admin:app_plugin_changelist')) return HttpResponseRedirect(reverse('admin:app_plugin_changelist'))
def plugin_upload(self, request, *args, **kwargs): def plugin_upload(self, request, *args, **kwargs):
messages.info(request, "YAY") # messages.info(request, "YAY")
# TODO file = request.FILES.get('file')
if file is not None:
# Save to tmp dir
tmp_zip_path = tempfile.mktemp('plugin.zip', dir=settings.MEDIA_TMP)
tmp_extract_path = tempfile.mkdtemp('plugin', dir=settings.MEDIA_TMP)
try:
with open(tmp_zip_path, 'wb+') as fd:
if isinstance(file, InMemoryUploadedFile):
for chunk in file.chunks():
fd.write(chunk)
else:
with open(file.temporary_file_path(), 'rb') as f:
shutil.copyfileobj(f, fd)
# Extract
with zipfile.ZipFile(tmp_zip_path, "r") as zip_h:
zip_h.extractall(tmp_extract_path)
# Validate
folders = os.listdir(tmp_extract_path)
if len(folders) != 1:
raise ValueError("The plugin has more than 1 root directory (it should have only one)")
plugin_name = folders[0]
plugin_path = os.path.join(tmp_extract_path, plugin_name)
if not valid_plugin(plugin_path):
raise ValueError("This doesn't look like a plugin. Are plugin.py and manifest.json in the proper place?")
if os.path.exists(get_plugins_persistent_path(plugin_name)):
raise ValueError("A plugin with the name {} already exist. Please remove it before uploading one with the same name.".format(plugin_name))
# Move
shutil.move(plugin_path, get_plugins_persistent_path())
# Initialize
clear_plugins_cache()
init_plugins()
messages.info(request, "Plugin added successfully")
except Exception as e:
messages.warning(request, "Cannot load plugin: {}".format(str(e)))
if os.path.exists(tmp_zip_path):
os.remove(tmp_zip_path)
if os.path.exists(tmp_extract_path):
shutil.rmtree(tmp_extract_path)
else:
messages.error(request, "You need to upload a zip file")
return HttpResponseRedirect(reverse('admin:app_plugin_changelist')) return HttpResponseRedirect(reverse('admin:app_plugin_changelist'))

Wyświetl plik

@ -7,6 +7,8 @@ import platform
import django import django
import json import json
import shutil
from django.conf.urls import url from django.conf.urls import url
from functools import reduce from functools import reduce
from string import Template from string import Template
@ -149,6 +151,10 @@ def register_plugins():
disable_plugin(plugin.get_name()) disable_plugin(plugin.get_name())
logger.warning("Cannot register {}: {}".format(plugin, str(e))) logger.warning("Cannot register {}: {}".format(plugin, str(e)))
def valid_plugin(plugin_path):
pluginpy_path = os.path.join(plugin_path, "plugin.py")
manifest_path = os.path.join(plugin_path, "manifest.json")
return os.path.isfile(manifest_path) and os.path.isfile(pluginpy_path)
plugins = None plugins = None
def get_plugins(): def get_plugins():
@ -163,14 +169,15 @@ def get_plugins():
plugins = [] plugins = []
for plugins_path in plugins_paths: for plugins_path in plugins_paths:
for dir in [d for d in os.listdir(plugins_path) if os.path.isdir(plugins_path)]: if not os.path.isdir(plugins_path):
continue
for dir in os.listdir(plugins_path):
# Each plugin must have a manifest.json and a plugin.py # Each plugin must have a manifest.json and a plugin.py
plugin_path = os.path.join(plugins_path, dir) plugin_path = os.path.join(plugins_path, dir)
pluginpy_path = os.path.join(plugin_path, "plugin.py")
manifest_path = os.path.join(plugin_path, "manifest.json")
# Do not load test plugin unless we're in test mode # Do not load test plugin unless we're in test mode
if os.path.basename(plugin_path) == 'test' and not settings.TESTING: if os.path.basename(plugin_path).endswith('test') and not settings.TESTING:
continue continue
# Ignore .gitignore # Ignore .gitignore
@ -178,7 +185,7 @@ def get_plugins():
continue continue
# Check plugin required files # Check plugin required files
if not os.path.isfile(manifest_path) or not os.path.isfile(pluginpy_path): if not valid_plugin(plugin_path):
continue continue
# Instantiate the plugin # Instantiate the plugin
@ -194,6 +201,7 @@ def get_plugins():
manifest = plugin.get_manifest() manifest = plugin.get_manifest()
if 'webodmMinVersion' in manifest: if 'webodmMinVersion' in manifest:
min_version = manifest['webodmMinVersion'] min_version = manifest['webodmMinVersion']
manifest_path = os.path.join(plugin_path, "manifest.json")
if versionToInt(min_version) > versionToInt(settings.VERSION): if versionToInt(min_version) > versionToInt(settings.VERSION):
logger.warning( logger.warning(
@ -305,7 +313,10 @@ def disable_plugin(plugin_name):
Plugin.objects.get(pk=plugin_name).disable() Plugin.objects.get(pk=plugin_name).disable()
def delete_plugin(plugin_name): def delete_plugin(plugin_name):
Plugin.objects.get(pk=plugin_name).disable() Plugin.objects.get(pk=plugin_name).delete()
if os.path.exists(get_plugins_persistent_path(plugin_name)):
shutil.rmtree(get_plugins_persistent_path(plugin_name))
clear_plugins_cache()
def get_site_settings(): def get_site_settings():
return Setting.objects.first() return Setting.objects.first()

Wyświetl plik

@ -188,6 +188,16 @@ class PluginBase(ABC):
""" """
return [] return []
def serve_public_assets(self, request):
"""
Should be overriden by plugins that want to control which users
have access to the public assets. By default anyone can access them,
including anonymous users.
:param request: HTTP request
:return: boolean (whether the plugin's public assets should be exposed for this request)
"""
return True
def get_dynamic_script(self, script_path, callback = None, **template_args): def get_dynamic_script(self, script_path, callback = None, **template_args):
""" """
Retrieves a view handler that serves a dynamic script from Retrieves a view handler that serves a dynamic script from

Wyświetl plik

@ -8,10 +8,12 @@ from django.http import HttpResponse, Http404
from .functions import get_plugin_by_name from .functions import get_plugin_by_name
from django.conf.urls import url from django.conf.urls import url
from django.views.static import serve from django.views.static import serve
from urllib.parse import urlparse
def try_resolve_url(request, url): def try_resolve_url(request, url):
res = url.resolve(request.get_full_path()) o = urlparse(request.get_full_path())
res = url.resolve(o.path)
if res: if res:
return res return res
else: else:
@ -28,12 +30,11 @@ def app_view_handler(request, plugin_name=None):
mount_point.view, mount_point.view,
*mount_point.args, *mount_point.args,
**mount_point.kwargs)) **mount_point.kwargs))
if view: if view:
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
# Try public assets # Try public assets
if os.path.exists(plugin.get_path("public")): if os.path.exists(plugin.get_path("public")) and plugin.serve_public_assets(request):
view, args, kwargs = try_resolve_url(request, url('^/plugins/{}/(.*)'.format(plugin_name), view, args, kwargs = try_resolve_url(request, url('^/plugins/{}/(.*)'.format(plugin_name),
serve, serve,
{'document_root': plugin.get_path("public")})) {'document_root': plugin.get_path("public")}))

Wyświetl plik

@ -29,6 +29,22 @@ class TestPlugins(BootTestCase):
def test_core_plugins(self): def test_core_plugins(self):
client = Client() client = Client()
# We cannot access public files core plugins (plugin is disabled)
res = client.get('/plugins/test/file.txt')
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# Cannot access mount point (plugin is disabled)
res = client.get('/plugins/test/app_mountpoint/')
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# No python packages have been installed (plugin is disabled)
self.assertFalse(os.path.exists(get_plugins_persistent_path("test", "site-packages")))
enable_plugin("test")
# Python packages have been installed
self.assertTrue(os.path.exists(get_plugins_persistent_path("test", "site-packages")))
# We can access public files core plugins (without auth) # We can access public files core plugins (without auth)
res = client.get('/plugins/test/file.txt') res = client.get('/plugins/test/file.txt')
self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertEqual(res.status_code, status.HTTP_200_OK)
@ -38,13 +54,10 @@ class TestPlugins(BootTestCase):
self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertTemplateUsed(res, 'plugins/test/templates/app.html') self.assertTemplateUsed(res, 'plugins/test/templates/app.html')
# No python packages have been installed (plugin is disabled)
self.assertFalse(os.path.exists(get_plugins_persistent_path("test", "site-packages")))
enable_plugin("test")
# Form was rendered correctly # Form was rendered correctly
self.assertContains(res, '<input type="text" name="testField" class="form-control" required id="id_testField" />', count=1, status_code=200, html=True) self.assertContains(res,
'<input type="text" name="testField" class="form-control" required id="id_testField" />',
count=1, status_code=200, html=True)
# It uses regex properly # It uses regex properly
res = client.get('/plugins/test/app_mountpoint/a') res = client.get('/plugins/test/app_mountpoint/a')

Wyświetl plik

@ -1,6 +1,6 @@
{ {
"name": "WebODM", "name": "WebODM",
"version": "1.3.4", "version": "1.3.5",
"description": "User-friendly, extendable application and API for processing aerial imagery.", "description": "User-friendly, extendable application and API for processing aerial imagery.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {