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.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'))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")}))
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
Ładowanie…
Reference in New Issue