Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions inventree/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
38 changes: 31 additions & 7 deletions inventree/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
"""
Expand Down
Empty file removed scripts/__init__.py
Empty file.
64 changes: 0 additions & 64 deletions scripts/test_server_connection.py

This file was deleted.

26 changes: 26 additions & 0 deletions test/test_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down