kopia lustrzana https://github.com/OpenDroneMap/WebODM
Fixed unit tests
rodzic
37f887a265
commit
652bf1438e
62
app/admin.py
62
app/admin.py
|
@ -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'))
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")}))
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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']
|
||||
|
|
Ładowanie…
Reference in New Issue