From a81ea01e8e250d8b47a884b7419603c0a07b1109 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 12 May 2022 17:28:55 +1000 Subject: [PATCH] Model introspection - Find the class registered to the model (or log an error) - Pass the api_url through to the frontend --- InvenTree/common/models.py | 61 +++++++++++++++++-- InvenTree/common/serializers.py | 5 ++ InvenTree/plugin/models.py | 2 +- .../plugin/samples/integration/sample.py | 7 ++- .../templates/InvenTree/settings/setting.html | 3 - InvenTree/templates/js/dynamic/settings.js | 15 +++++ 6 files changed, 84 insertions(+), 9 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 37a6289d75..a13bbec071 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -17,15 +17,16 @@ import base64 from secrets import compare_digest from datetime import datetime, timedelta +from django.apps import apps from django.db import models, transaction +from django.db.utils import IntegrityError, OperationalError +from django.conf import settings from django.contrib.auth.models import User, Group from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType -from django.db.utils import IntegrityError, OperationalError -from django.conf import settings +from django.contrib.humanize.templatetags.humanize import naturaltime from django.urls import reverse from django.utils.timezone import now -from django.contrib.humanize.templatetags.humanize import naturaltime from djmoney.settings import CURRENCY_CHOICES from djmoney.contrib.exchange.models import convert_money @@ -587,6 +588,58 @@ class BaseInvenTreeSetting(models.Model): return setting.get('model', None) + def model_class(self): + """ + Return the model class associated with this setting, if (and only if): + + - It has a defined 'model' parameter + - The 'model' parameter is of the form app.model + - The 'model' parameter has matches a known app model + """ + + model_name = self.model_name() + + if not model_name: + return None + + try: + (app, mdl) = model_name.strip().split('.') + except ValueError: + logger.error(f"Invalid 'model' parameter for setting {self.key} : '{model_name}'") + return None + + app_models = apps.all_models.get(app, None) + + if app_models is None: + logger.error(f"Error retrieving model class '{model_name}' for setting '{self.key}' - no app named '{app}'") + return None + + model = app_models.get(mdl, None) + + if model is None: + logger.error(f"Error retrieving model class '{model_name}' for setting '{self.key}' - no model named '{mdl}'") + return None + + # Looks like we have found a model! + return model + + def api_url(self): + """ + Return the API url associated with the linked model, + if provided, and valid! + """ + + model_class = self.model_class() + + if model_class: + # If a valid class has been found, see if it has registered an API URL + try: + return model_class.get_api_url() + except: + pass + + return None + def is_bool(self): """ Check if this setting is required to be a boolean value @@ -617,7 +670,7 @@ class BaseInvenTreeSetting(models.Model): return 'integer' elif self.is_model(): - return 'model' + return 'related field' else: return 'string' diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 27fc15bca5..8dd0f5bcee 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -30,6 +30,8 @@ class SettingsSerializer(InvenTreeModelSerializer): model_name = serializers.CharField(read_only=True) + api_url = serializers.CharField(read_only=True) + def get_choices(self, obj): """ Returns the choices available for a given item @@ -78,6 +80,7 @@ class GlobalSettingsSerializer(SettingsSerializer): 'type', 'choices', 'model_name', + 'api_url', ] @@ -100,6 +103,7 @@ class UserSettingsSerializer(SettingsSerializer): 'type', 'choices', 'model_name', + 'api_url', ] @@ -129,6 +133,7 @@ class GenericReferencedSettingSerializer(SettingsSerializer): 'type', 'choices', 'model_name', + 'api_url', ] # set Meta class diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index 1620bed230..18320cc34b 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -137,7 +137,7 @@ class PluginSetting(common.models.BaseInvenTreeSetting): if 'settings' not in kwargs: - plugin = kwargs.pop('plugin') + plugin = kwargs.pop('plugin', None) if plugin: diff --git a/InvenTree/plugin/samples/integration/sample.py b/InvenTree/plugin/samples/integration/sample.py index af99727ed6..a3a26e7609 100644 --- a/InvenTree/plugin/samples/integration/sample.py +++ b/InvenTree/plugin/samples/integration/sample.py @@ -68,7 +68,12 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi 'SELECT_COMPANY': { 'name': 'Company', 'description': 'Select a company object from the database', - 'model': 'company.Company', + 'model': 'company.company', + }, + 'SELECT_PART': { + 'name': 'Part', + 'description': 'Select a part object from the database', + 'model': 'part.part', }, } diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html index 55c323faec..0bc099f8a2 100644 --- a/InvenTree/templates/InvenTree/settings/setting.html +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -22,9 +22,6 @@ {{ setting.description }} - {% if setting.model_name %} - Model name: {{ setting.model_name }} - {% endif %} {% if setting.is_bool %}
diff --git a/InvenTree/templates/js/dynamic/settings.js b/InvenTree/templates/js/dynamic/settings.js index 21eb9df5e2..5b52c2a015 100644 --- a/InvenTree/templates/js/dynamic/settings.js +++ b/InvenTree/templates/js/dynamic/settings.js @@ -71,9 +71,24 @@ function editSetting(key, options={}) { help_text: response.description, type: response.type, choices: response.choices, + value: response.value, } }; + // Foreign key lookup available! + if (response.type == 'related field') { + + if (response.model_name && response.api_url) { + fields.value.type = 'related field'; + fields.value.model = response.model_name.split('.').at(-1); + fields.value.api_url = response.api_url; + } else { + // Unknown / unsupported model type, default to 'text' field + fields.value.type = 'text'; + console.warn(`Unsupported model type: '${response.model_name}' for setting '${response.key}'`); + } + } + constructChangeForm(fields, { url: url, method: 'PATCH',