kopia lustrzana https://github.com/OpenDroneMap/WebODM
OAM plugin skeleton, data store plugins API, OAM icon, interface
rodzic
720eefb4fd
commit
f9ad74cdcb
|
@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -1,3 +1,4 @@
|
|||
from .data_store import UserDataStore, GlobalDataStore
|
||||
from .plugin_base import PluginBase
|
||||
from .menu import Menu
|
||||
from .mount_point import MountPoint
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -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 %}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "WebODM",
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.3",
|
||||
"description": "Open Source Drone Image Processing",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .plugin import *
|
|
@ -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
|
||||
}
|
|
@ -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.
|
@ -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=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" 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.
|
@ -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;
|
||||
}
|
|
@ -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 %}
|
Ładowanie…
Reference in New Issue