diff --git a/inventree/api.py b/inventree/api.py index bbd8709f..90715bf4 100644 --- a/inventree/api.py +++ b/inventree/api.py @@ -99,10 +99,10 @@ def testServer(self): try: response = requests.get(self.api_url, timeout=2.5) except requests.exceptions.ConnectionError as e: - logger.fatal("Server connection error:", type(e)) + logger.fatal(f"Server connection error: {str(type(e))}") return False except Exception as e: - logger.fatal("Unhandled server error:", type(e)) + logger.fatal(f"Unhandled server error: {str(type(e))}") # Re-throw the exception raise e diff --git a/inventree/base.py b/inventree/base.py index 2786773d..019698e7 100644 --- a/inventree/base.py +++ b/inventree/base.py @@ -55,16 +55,16 @@ def __init__(self, api, pk=None, data=None): self.reload() @classmethod - def fields(cls, api): - """ - Returns a list of available fields for this model. + def options(cls, api): + """Perform an OPTIONS request for this model, to determine model information. - Introspects the available fields using an OPTIONS request. + InvenTree provides custom metadata for each API endpoint, accessed via a HTTP OPTIONS request. + This endpoint provides information on the various fields available for that endpoint. """ response = api.request( cls.URL, - method='options', + method='OPTIONS', ) if not response.status_code == 200: @@ -74,14 +74,38 @@ def fields(cls, api): try: data = json.loads(response.text) except json.decoder.JSONDecodeError: - logger.error(f"Error decoding JSON response for '{cls.URL}'") + logger.error(f"Error decoding OPTIONS response for '{cls.URL}'") return {} - actions = data.get('actions', {}) + return data + + @classmethod + def fields(cls, api): + """ + Returns a list of available fields for this model. + + Introspects the available fields using an OPTIONS request. + """ + + opts = cls.options(api) + + actions = opts.get('actions', {}) post = actions.get('POST', {}) return post + @classmethod + def fieldInfo(cls, field_name, api): + """Return metadata for a specific field on a model""" + + fields = cls.fields(api) + + if field_name in fields: + return fields[field_name] + else: + logger.warning(f"Field '{field_name}' not found in OPTIONS request for {cls.URL}") + return {} + @classmethod def fieldNames(cls, api): """ diff --git a/scripts/__init__.py b/scripts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/scripts/test_server_connection.py b/scripts/test_server_connection.py deleted file mode 100644 index ba78e0a2..00000000 --- a/scripts/test_server_connection.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -A simple script to test connection to an InvenTree server. -User must provide address / username / password information. -""" - -import argparse -import logging -import sys - -from inventree.api import InvenTreeAPI -from inventree.base import INVENTREE_PYTHON_VERSION - - -def test_server(address, **kwargs): - """ - Perform basic server checks. - """ - - if kwargs.get('debug', False): - # Print all debug messages - logging.basicConfig(level=logging.INFO) - - api = InvenTreeAPI(address, **kwargs) - - assert api.server_details is not None - assert api.token is not None - - print("Server Details:", api.server_details) - print("Token:", api.token) - - print("All checks passed OK...") - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser(description="InvenTree server test") - - parser.add_argument("address", help="InvenTree server address") - parser.add_argument("-u", "--username", help="Username", default=None) - parser.add_argument("-p", "--password", help="Password", default=None) - parser.add_argument("-t", "--token", help="Authentication token", default=None) - - args = parser.parse_args() - - print("InvenTree python version: " + INVENTREE_PYTHON_VERSION) - - # User must provide either USERNAME/PASSWORD or Token - if args.token is None: - if args.username is None: - logging.error("Username must be provided") - sys.exit(1) - - if args.password is None: - logging.error("Password must be provided") - sys.exit(1) - - test_server( - args.address, - username=args.username, - password=args.password, - token=args.token - ) diff --git a/test/test_part.py b/test/test_part.py index 97acad37..c238cee5 100644 --- a/test/test_part.py +++ b/test/test_part.py @@ -49,6 +49,32 @@ def test_fields(self): self.assertIn('full_name', field_names) self.assertIn('IPN', field_names) + def test_options(self): + """Extends tests for OPTIONS model metadata""" + + # Check for field which does not exist + with self.assertLogs(): + Part.fieldInfo('abcde', self.api) + + active = Part.fieldInfo('active', self.api) + + self.assertEqual(active['type'], 'boolean') + self.assertEqual(active['required'], True) + self.assertEqual(active['label'], 'Active') + self.assertEqual(active['default'], True) + + for field_name in [ + 'name', + 'description', + 'component', + 'assembly', + ]: + field = Part.fieldInfo(field_name, self.api) + + # Check required field attributes + for attr in ['type', 'required', 'read_only', 'label', 'help_text']: + self.assertIn(attr, field) + def test_part_cats(self): """ Tests for category filtering