diff --git a/app/migrations/0020_plugindatum.py b/app/migrations/0020_plugindatum.py new file mode 100644 index 00000000..5ba16d63 --- /dev/null +++ b/app/migrations/0020_plugindatum.py @@ -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)), + ], + ), + ] diff --git a/app/models/__init__.py b/app/models/__init__.py index 7b510c73..677002ef 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -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 diff --git a/app/models/plugin_datum.py b/app/models/plugin_datum.py new file mode 100644 index 00000000..b60d03d3 --- /dev/null +++ b/app/models/plugin_datum.py @@ -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 diff --git a/app/plugins/__init__.py b/app/plugins/__init__.py index 8bc5b80f..22666a83 100644 --- a/app/plugins/__init__.py +++ b/app/plugins/__init__.py @@ -1,3 +1,4 @@ +from .data_store import UserDataStore, GlobalDataStore from .plugin_base import PluginBase from .menu import Menu from .mount_point import MountPoint diff --git a/app/plugins/data_store.py b/app/plugins/data_store.py new file mode 100644 index 00000000..e8926cb4 --- /dev/null +++ b/app/plugins/data_store.py @@ -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 diff --git a/app/plugins/mount_point.py b/app/plugins/mount_point.py index e6cf4f2b..0509eedf 100644 --- a/app/plugins/mount_point.py +++ b/app/plugins/mount_point.py @@ -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 diff --git a/app/plugins/plugin_base.py b/app/plugins/plugin_base.py index 83244aa9..a28c1fb9 100644 --- a/app/plugins/plugin_base.py +++ b/app/plugins/plugin_base.py @@ -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__ diff --git a/app/plugins/templates/form.html b/app/plugins/templates/form.html new file mode 100644 index 00000000..793f341a --- /dev/null +++ b/app/plugins/templates/form.html @@ -0,0 +1,14 @@ +{% load bootstrap_extras %} + +{% for field in form %} +
OpenAerialMap (OAM) is a set of tools for searching, sharing, and using openly licensed satellite and unmanned aerial vehicle (UAV) imagery.
+ + {% if not form.token.value %} +To share your results with OAM:
+