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.contrib import admin
from django.contrib import messages
@ -9,10 +14,13 @@ from guardian.admin import GuardedModelAdmin
from app.models import PluginDatum
from app.models import Preset
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 django import forms
from codemirror2.widgets import CodeMirrorEditor
from webodm import settings
from django.core.files.uploadedfile import InMemoryUploadedFile
admin.site.register(Project, GuardedModelAdmin)
@ -144,8 +152,56 @@ class PluginAdmin(admin.ModelAdmin):
return HttpResponseRedirect(reverse('admin:app_plugin_changelist'))
def plugin_upload(self, request, *args, **kwargs):
messages.info(request, "YAY")
# TODO
# messages.info(request, "YAY")
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'))

Wyświetl plik

@ -7,6 +7,8 @@ import platform
import django
import json
import shutil
from django.conf.urls import url
from functools import reduce
from string import Template
@ -149,6 +151,10 @@ def register_plugins():
disable_plugin(plugin.get_name())
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
def get_plugins():
@ -163,14 +169,15 @@ def get_plugins():
plugins = []
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
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
if os.path.basename(plugin_path) == 'test' and not settings.TESTING:
if os.path.basename(plugin_path).endswith('test') and not settings.TESTING:
continue
# Ignore .gitignore
@ -178,7 +185,7 @@ def get_plugins():
continue
# 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
# Instantiate the plugin
@ -194,6 +201,7 @@ def get_plugins():
manifest = plugin.get_manifest()
if 'webodmMinVersion' in manifest:
min_version = manifest['webodmMinVersion']
manifest_path = os.path.join(plugin_path, "manifest.json")
if versionToInt(min_version) > versionToInt(settings.VERSION):
logger.warning(
@ -305,7 +313,10 @@ def disable_plugin(plugin_name):
Plugin.objects.get(pk=plugin_name).disable()
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():
return Setting.objects.first()

Wyświetl plik

@ -188,6 +188,16 @@ class PluginBase(ABC):
"""
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):
"""
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 django.conf.urls import url
from django.views.static import serve
from urllib.parse import urlparse
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:
return res
else:
@ -28,12 +30,11 @@ def app_view_handler(request, plugin_name=None):
mount_point.view,
*mount_point.args,
**mount_point.kwargs))
if view:
return view(request, *args, **kwargs)
# 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),
serve,
{'document_root': plugin.get_path("public")}))

Wyświetl plik

@ -29,6 +29,22 @@ class TestPlugins(BootTestCase):
def test_core_plugins(self):
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)
res = client.get('/plugins/test/file.txt')
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.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
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
res = client.get('/plugins/test/app_mountpoint/a')

Wyświetl plik

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

Wyświetl plik

@ -22,10 +22,10 @@ class Plugin(PluginBase):
return [Menu("Test", self.public_url("menu_url/"), "test-icon")]
def include_js_files(self):
return ['test.js']
return ['test.js']
def include_css_files(self):
return ['test.css']
return ['test.css']
def build_jsx_components(self):
return ['component.jsx']