Plugin API refactoring, upload to public URL working, celery tasks from plugins

pull/492/head
Piero Toffanin 2018-07-26 17:55:20 -04:00
rodzic 78b6c41dd2
commit a847edcd06
11 zmienionych plików z 142 dodań i 9 usunięć

Wyświetl plik

@ -0,0 +1,20 @@
# Generated by Django 2.0.3 on 2018-07-26 21:46
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('app', '0020_plugindatum'),
]
operations = [
migrations.AlterField(
model_name='plugindatum',
name='user',
field=models.ForeignKey(default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

Wyświetl plik

@ -7,7 +7,7 @@ logger = logging.getLogger('app.logger')
class PluginDatum(models.Model):
key = models.CharField(max_length=255, help_text="Setting key", db_index=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, help_text="The user this setting belongs to. If NULL, the setting is global.")
user = models.ForeignKey(User, null=True, default=None, on_delete=models.CASCADE, help_text="The user this setting belongs to. If NULL, the setting is global.")
int_value = models.IntegerField(blank=True, null=True, default=None, help_text="Integer value")
float_value = models.FloatField(blank=True, null=True, default=None, help_text="Float value")
bool_value = models.NullBooleanField(blank=True, null=True, default=None, help_text="Bool value")

Wyświetl plik

@ -3,3 +3,5 @@ from .plugin_base import PluginBase
from .menu import Menu
from .mount_point import MountPoint
from .functions import *

Wyświetl plik

@ -46,7 +46,6 @@ def register_plugins():
with open(plugin.get_path('public/webpack.config.js'), 'w') as f:
f.write(wpc_content)
logger.info('Wrote public/webpack.config.js for {}'.format(plugin))
else:
logger.warning("Cannot generate webpack.config.js for {}, a path is missing: {}".format(plugin, ' '.join(build_paths)))
@ -115,6 +114,10 @@ def get_active_plugins():
if os.path.basename(plugin_path) == 'test' and not settings.TESTING:
continue
# Ignore .gitignore
if os.path.basename(plugin_path) == '.gitignore':
continue
if not os.path.isfile(manifest_path) or not os.path.isfile(pluginpy_path):
logger.warning("Found invalid plugin in {}".format(plugin_path))
continue

Wyświetl plik

@ -0,0 +1 @@
from app.api.tasks import TaskNestedView as TaskView

Wyświetl plik

@ -0,0 +1,5 @@
from worker.celery import app
# noinspection PyUnresolvedReferences
from worker.tasks import execute_grass_script
task = app.task

Wyświetl plik

@ -5,7 +5,7 @@ from rest_framework import serializers
from rest_framework import status
from rest_framework.response import Response
from app.api.tasks import TaskNestedView
from app.plugins.views import TaskView
from worker.tasks import execute_grass_script
@ -16,7 +16,7 @@ class GeoJSONSerializer(serializers.Serializer):
area = serializers.JSONField(help_text="Polygon contour defining the volume area to compute")
class TaskVolume(TaskNestedView):
class TaskVolume(TaskView):
def post(self, request, pk=None):
task = self.get_and_check_task(request, pk)
if task.dsm_extent is None:

Wyświetl plik

@ -0,0 +1,72 @@
from rest_framework import status
from rest_framework.response import Response
from app.plugins import GlobalDataStore
from app.plugins.views import TaskView
from app.plugins.worker import task
import requests
ds = GlobalDataStore('openaerialmap')
def get_key_for(task_id, key):
return "task_{}_{}".format(str(task_id), key)
def get_task_info(task_id):
return ds.get_json(get_key_for(task_id, "info"), {
'sharing': False,
'shared': False,
'error': ''
})
def set_task_info(task_id, json):
return ds.set_json(get_key_for(task_id, "info"), json)
# TODO: task info cleanup when task is deleted via signal
class ShareInfo(TaskView):
def get(self, request, pk=None):
task = self.get_and_check_task(request, pk)
return Response(get_task_info(task.id), status=status.HTTP_200_OK)
class Share(TaskView):
def post(self, request, pk=None):
task = self.get_and_check_task(request, pk)
upload_orthophoto_to_oam.delay(task.id, task.get_asset_download_path('orthophoto.tif'))
task_info = get_task_info(task.id)
task_info['sharing'] = True
set_task_info(task.id, task_info)
return Response(task_info, status=status.HTTP_200_OK)
@task
def upload_orthophoto_to_oam(task_id, orthophoto_path):
# Upload to temporary central location since
# OAM requires a public URL and not all WebODM
# instances are public
res = requests.post('https://www.webodm.org/oam/upload',
files=[
('file', ('orthophoto.tif', open(orthophoto_path, 'rb'), 'image/tiff')),
]).json()
if 'url' in res:
orthophoto_public_url = res['url']
import logging
logger = logging.getLogger('app.logger')
logger.info("UPLOADED TO " + orthophoto_public_url)
else:
task_info = get_task_info(task_id)
task_info['sharing'] = False
task_info['error'] = 'Could not upload orthophoto to intermediate location.'
set_task_info(task_id, task_info)

Wyświetl plik

@ -5,6 +5,9 @@ from app.plugins import PluginBase, Menu, MountPoint
from django.contrib.auth.decorators import login_required
from django import forms
from plugins.openaerialmap.api import ShareInfo, Share
class TokenForm(forms.Form):
token = forms.CharField(label='', required=False, max_length=1024, widget=forms.TextInput(attrs={'placeholder': 'Token'}))
@ -40,6 +43,12 @@ class Plugin(PluginBase):
)
]
def api_mount_points(self):
return [
MountPoint('task/(?P<pk>[^/.]+)/shareinfo', ShareInfo.as_view()),
MountPoint('task/(?P<pk>[^/.]+)/share', Share.as_view())
]
def home_view(self):
@login_required
def home(request):

Wyświetl plik

@ -1,6 +1,7 @@
import './ShareButton.scss';
import React from 'react';
import PropTypes from 'prop-types';
import $ from 'jquery';
module.exports = class ShareButton extends React.Component{
static defaultProps = {
@ -17,8 +18,26 @@ module.exports = class ShareButton extends React.Component{
super(props);
this.state = {
loading: true
loading: true,
shared: false,
error: ''
};
console.log("AH!");
}
componentDidMount(){
const { task } = this.props;
$.ajax({
type: 'GET',
url: `/api/plugins/openaerialmap/task/${task.id}/shareinfo`,
contentType: "application/json"
}).done(result => {
this.setState({shared: result.shared, loading: false})
}).fail(error => {
this.setState({error, loading: false});
});
}
handleClick = () => {
@ -26,12 +45,15 @@ module.exports = class ShareButton extends React.Component{
}
render(){
const { loading, shared } = this.state;
return (<button
onClick={this.handleClick}
disabled={loading || shared}
className="btn btn-sm btn-primary">
{this.state.loading
? <i className="fa fa-circle-o-notch fa-spin fa-fw"></i>
: [<i className="oam-icon fa"></i>, "Share To OAM"]}
{loading ?
<i className="fa fa-circle-o-notch fa-spin fa-fw"></i> :
[<i className="oam-icon fa"></i>, (shared ? "Shared To OAM" : " Share To OAM")]}
</button>);
}
}

Wyświetl plik

@ -2,7 +2,6 @@ import traceback
from celery.utils.log import get_task_logger
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.db.models import Count
from django.db.models import Q