OAM plugin skeleton, data store plugins API, OAM icon, interface

pull/492/head
Piero Toffanin 2018-07-24 18:32:24 -04:00
rodzic 720eefb4fd
commit f9ad74cdcb
18 zmienionych plików z 297 dodań i 2 usunięć

Wyświetl plik

@ -0,0 +1,30 @@
# Generated by Django 2.0.3 on 2018-07-24 21:01
from django.conf import settings
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('app', '0019_remove_task_processing_lock'),
]
operations = [
migrations.CreateModel(
name='PluginDatum',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(db_index=True, help_text='Setting key', max_length=255)),
('int_value', models.IntegerField(blank=True, default=None, help_text='Integer value', null=True)),
('float_value', models.FloatField(blank=True, default=None, help_text='Float value', null=True)),
('bool_value', models.NullBooleanField(default=None, help_text='Bool value')),
('string_value', models.TextField(blank=True, default=None, help_text='String value', null=True)),
('json_value', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='JSON value', null=True)),
('user', models.ForeignKey(help_text='The user this setting belongs to. If NULL, the setting is global.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

Wyświetl plik

@ -4,4 +4,5 @@ from .task import Task, validate_task_options, gcp_directory_path
from .preset import Preset
from .theme import Theme
from .setting import Setting
from .plugin_datum import PluginDatum

Wyświetl plik

@ -0,0 +1,18 @@
import logging
from django.db import models
from django.contrib.postgres import fields
from django.contrib.auth.models import User
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.")
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")
string_value = models.TextField(blank=True, null=True, default=None, help_text="String value")
json_value = fields.JSONField(default=None, blank=True, null=True, help_text="JSON value")
def __str__(self):
return self.key

Wyświetl plik

@ -1,3 +1,4 @@
from .data_store import UserDataStore, GlobalDataStore
from .plugin_base import PluginBase
from .menu import Menu
from .mount_point import MountPoint

Wyświetl plik

@ -0,0 +1,77 @@
from abc import ABC
from django.core.exceptions import MultipleObjectsReturned
from app.models import PluginDatum
import logging
logger = logging.getLogger('app.logger')
class DataStore(ABC):
def __init__(self, namespace, user=None):
"""
:param namespace: Namespace (typically the plugin's name) to use for this datastore
:param user: User tied to this datastore. If None, this is a global data store
"""
self.namespace = namespace
self.user = user
def db_key(self, key):
return "{}::{}".format(self.namespace, key)
def get_datum(self, key):
return PluginDatum.objects.filter(key=self.db_key(key), user=self.user).first()
def set_value(self, type, key, value):
try:
return PluginDatum.objects.update_or_create(key=self.db_key(key),
user=self.user,
defaults={type + '_value': value})
except MultipleObjectsReturned:
# This should never happen
logger.warning("A plugin data store for the {} plugin returned multiple objects. This is potentially bad. The plugin developer needs to fix this! The data store will not be changed.".format(self.namespace))
PluginDatum.objects.filter(key=self.db_key(key), user=self.user).delete()
def get_value(self, type, key, default=None):
datum = self.get_datum(key)
return default if datum is None else getattr(datum, type + '_value')
def get_string(self, key, default=""):
return self.get_value('string', key, default)
def set_string(self, key, value):
return self.set_value('string', key, value)
def get_int(self, key, default=0):
return self.get_value('int', key, default)
def set_int(self, key, value):
return self.set_value('int', key, value)
def get_float(self, key, default=0.0):
return self.get_value('float', key, default)
def set_float(self, key, value):
return self.set_value('float', key, value)
def get_bool(self, key, default=False):
return self.get_value('bool', key, default)
def set_bool(self, key, value):
return self.set_value('bool', key, value)
def get_json(self, key, default={}):
return self.get_value('json', key, default)
def set_json(self, key, value):
return self.set_value('json', key, value)
def has_key(self, key):
return self.get_datum(key) is not None
class UserDataStore(DataStore):
def __init__(self, namespace, user):
super().__init__(namespace, user)
class GlobalDataStore(DataStore):
pass

Wyświetl plik

@ -3,7 +3,6 @@ import re
class MountPoint:
def __init__(self, url, view, *args, **kwargs):
"""
:param url: path to mount this view to, relative to plugins directory
:param view: Django/DjangoRestFramework view
:param args: extra args to pass to url() call

Wyświetl plik

@ -1,5 +1,6 @@
import logging, os, sys
from abc import ABC
from app.plugins import UserDataStore, GlobalDataStore
logger = logging.getLogger('app.logger')
@ -23,6 +24,22 @@ class PluginBase(ABC):
"""
return self.name
def get_user_data_store(self, user):
"""
Helper function to instantiate a user data store associated
with this plugin
:return: UserDataStore
"""
return UserDataStore(self.get_name(), user)
def get_global_data_store(self, user):
"""
Helper function to instantiate a user data store associated
with this plugin
:return: GlobalDataStore
"""
return GlobalDataStore(self.get_name())
def get_module_name(self):
return self.__class__.__module__

Wyświetl plik

@ -0,0 +1,14 @@
{% load bootstrap_extras %}
{% for field in form %}
<div class="form-group {% if field.errors %}has-error{% endif %}">
<label for="{{ field.id_for_label }}" class="control-label">{{ field.label }}</label>
{{ field|with_class:'form-control' }}
{% if field.errors %}
<span class='text-danger'>{{ field.errors|join:'<br />' }}</span>
{% endif %}
{% if field.help_text %}
<span class="help-block ">{{ field.help_text }}</span>
{% endif %}
</div>
{% endfor %}

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "WebODM",
"version": "0.5.2",
"version": "0.5.3",
"description": "Open Source Drone Image Processing",
"main": "index.js",
"scripts": {

Wyświetl plik

@ -0,0 +1 @@
from .plugin import *

Wyświetl plik

@ -0,0 +1,13 @@
{
"name": "OpenAerialMap",
"webodmMinVersion": "0.5.3",
"description": "A plugin to upload orthophotos to OpenAerialMap",
"version": "0.1.0",
"author": "Piero Toffanin",
"email": "pt@masseranolabs.com",
"repository": "https://github.com/OpenDroneMap/WebODM",
"tags": ["oam", "openaerialmap"],
"homepage": "https://github.com/OpenDroneMap/WebODM",
"experimental": true,
"deprecated": false
}

Wyświetl plik

@ -0,0 +1,45 @@
from django.contrib import messages
from django.shortcuts import render
from app.plugins import PluginBase, Menu, MountPoint
from django.contrib.auth.decorators import login_required
from django import forms
class TokenForm(forms.Form):
token = forms.CharField(label='', required=False, max_length=1024, widget=forms.TextInput(attrs={'placeholder': 'Token'}))
class Plugin(PluginBase):
def main_menu(self):
return [Menu("OpenAerialMap", self.public_url(""), "oam-icon fa fa-fw")]
def include_css_files(self):
return ['style.css']
def app_mount_points(self):
return [
MountPoint('$', self.home_view())
]
def home_view(self):
@login_required
def home(request):
ds = self.get_user_data_store(request.user)
# if this is a POST request we need to process the form data
if request.method == 'POST':
form = TokenForm(request.POST)
if form.is_valid():
ds.set_string('token', form.cleaned_data['token'])
messages.success(request, 'Token updated. Tasks can now be shared to OpenAerialMap.')
form = TokenForm(initial={'token': ds.get_string('token', default="")})
return render(request, self.template_path("app.html"), {
'title': 'OpenAerialMap',
'form': form
})
return home

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="oam-icon" d="M256 140.8v-166.4l51.2 12.8 32.64 153.6zM360.96 243.2l151.040 716.8-256-358.4v-358.4zM1024 243.2l-256 358.4v-358.4zM663.040 243.2h2.56v501.76l-153.6 215.040zM0 243.2l102.4-307.2 51.2 12.8v509.44zM716.8-12.8l204.8-51.2 68.48 204.8h-305.92zM339.84 140.8l-32.64-153.6 204.8 153.6v102.4zM512 345.6v614.4l-151.040-716.8zM663.040 243.2l-151.040 716.8v-614.4zM684.16 140.8l-172.16 102.4v-102.4l204.8-153.6zM360.96 243.2l-21.12-102.4 172.16 102.4v102.4zM512 345.6v-102.4l172.16-102.4-21.12 102.4z" />
</font></defs></svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 979 B

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,37 @@
@font-face {
font-family: 'oamfont';
src: url('fonts/oamfont.eot?7ho5xe');
src: url('fonts/oamfont.eot?7ho5xe#iefix') format('embedded-opentype'),
url('fonts/oamfont.ttf?7ho5xe') format('truetype'),
url('fonts/oamfont.woff?7ho5xe') format('woff'),
url('fonts/oamfont.svg?7ho5xe#oamfont') format('svg');
font-weight: normal;
font-style: normal;
}
.oam-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'oamfont' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.oam-icon:before {
content: "\e900";
}
.oam-form{
margin-top: 16px;
}
.oam-token-form label{
display: none;
}

Wyświetl plik

@ -0,0 +1,31 @@
{% extends "app/plugins/templates/base.html" %}
{% block content %}
<h3><i class="oam-icon fa"></i> OpenAerialMap</h3>
<p>OpenAerialMap (OAM) is a set of tools for searching, sharing, and using openly licensed satellite and unmanned aerial vehicle (UAV) imagery.</p>
{% if not form.token.value %}
<p>To share your results with OAM:</p>
<ol>
<li>Sign-in from <a href="https://map.openaerialmap.org" target="_blank">map.openaerialmap.org</a>.</li>
<li>Navigate to your OAM profile page (click your user's avatar) and press the <b>Request 3rd Party API Token</b> button.</li>
<li>Copy and paste the token in the form below.</li>
</ol>
{% else %}
<p><a class="btn btn-sm btn-default" href="https://map.openaerialmap.org" target="_blank"><i class="fa fa-external-link"></i> Go To OpenAerialMap</a></p>
{% endif %}
<form action="" method="post" class="oam-form oam-token-form">
<h4>Token Settings</h4>
{% csrf_token %}
{% include "app/plugins/templates/form.html" %}
<button type="submit" class="btn btn-primary"><i class="fa fa-save fa-fw"></i> Set Token</i></button>
</form>
<!--{% if form.token.value %}
<div class="oam-form">
<h4>Advanced Settings</h4>
</div>
{% endif %}-->
{% endblock %}