Model introspection

- Find the class registered to the model (or log an error)
- Pass the api_url through to the frontend
This commit is contained in:
Oliver 2022-05-12 17:28:55 +10:00
parent e112d555d4
commit a81ea01e8e
6 changed files with 84 additions and 9 deletions

View File

@ -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'

View File

@ -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

View File

@ -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:

View File

@ -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',
},
}

View File

@ -22,9 +22,6 @@
{{ setting.description }}
</td>
<td>
{% if setting.model_name %}
<b>Model name: {{ setting.model_name }}</b>
{% endif %}
{% if setting.is_bool %}
<div class='form-check form-switch'>
<input class='form-check-input boolean-setting' fieldname='{{ setting.key.upper }}' pk='{{ setting.pk }}' setting='{{ setting.key.upper }}' id='setting-value-{{ setting.key.upper }}' type='checkbox' {% if setting.as_bool %}checked=''{% endif %} {% if plugin %}plugin='{{ plugin.slug }}'{% endif %}{% if user_setting %}user='{{request.user.id}}'{% endif %}{% if notification_setting %}notification='{{request.user.id}}'{% endif %}>

View File

@ -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',