mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 04:55:44 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue6281
This commit is contained in:
27
src/backend/.eslintrc.yml
Normal file
27
src/backend/.eslintrc.yml
Normal file
@ -0,0 +1,27 @@
|
||||
env:
|
||||
commonjs: false
|
||||
browser: true
|
||||
es2021: true
|
||||
jquery: true
|
||||
extends:
|
||||
- eslint:recommended
|
||||
parserOptions:
|
||||
ecmaVersion: 12
|
||||
rules:
|
||||
no-var: off
|
||||
guard-for-in: off
|
||||
no-trailing-spaces: off
|
||||
camelcase: off
|
||||
padded-blocks: off
|
||||
prefer-const: off
|
||||
max-len: off
|
||||
require-jsdoc: off
|
||||
valid-jsdoc: off
|
||||
no-multiple-empty-lines: off
|
||||
comma-dangle: off
|
||||
no-unused-vars: off
|
||||
no-useless-escape: off
|
||||
prefer-spread: off
|
||||
indent:
|
||||
- error
|
||||
- 4
|
@ -84,25 +84,48 @@ class InvenTreeResource(ModelResource):
|
||||
|
||||
return [f for f in fields if f.column_name not in fields_to_exclude]
|
||||
|
||||
def before_import(self, dataset, using_transactions, dry_run, **kwargs):
|
||||
"""Run custom code before importing data.
|
||||
|
||||
- Determine the list of fields which need to be converted to empty strings
|
||||
"""
|
||||
# Construct a map of field names
|
||||
db_fields = {field.name: field for field in self.Meta.model._meta.fields}
|
||||
|
||||
for field_name, field in self.fields.items():
|
||||
# Skip read-only fields (they cannot be imported)
|
||||
if field.readonly:
|
||||
continue
|
||||
|
||||
# Determine the name of the associated column in the dataset
|
||||
column = getattr(field, 'column_name', field_name)
|
||||
|
||||
# Determine the attribute name of the associated database field
|
||||
attribute = getattr(field, 'attribute', field_name)
|
||||
|
||||
# Check if the associated database field is a non-nullable string
|
||||
if db_field := db_fields.get(attribute):
|
||||
if (
|
||||
isinstance(db_field, CharField)
|
||||
and db_field.blank
|
||||
and not db_field.null
|
||||
):
|
||||
if column not in self.CONVERT_NULL_FIELDS:
|
||||
self.CONVERT_NULL_FIELDS.append(column)
|
||||
|
||||
return super().before_import(dataset, using_transactions, dry_run, **kwargs)
|
||||
|
||||
def before_import_row(self, row, row_number=None, **kwargs):
|
||||
"""Run custom code before importing each row.
|
||||
|
||||
- Convert any null fields to empty strings, for fields which do not support null values
|
||||
"""
|
||||
# We can automatically determine which fields might need such a conversion
|
||||
for field in self.Meta.model._meta.fields:
|
||||
if (
|
||||
isinstance(field, CharField)
|
||||
and field.blank
|
||||
and not field.null
|
||||
and field.name not in self.CONVERT_NULL_FIELDS
|
||||
):
|
||||
self.CONVERT_NULL_FIELDS.append(field.name)
|
||||
|
||||
for field in self.CONVERT_NULL_FIELDS:
|
||||
if field in row and row[field] is None:
|
||||
row[field] = ''
|
||||
|
||||
return super().before_import_row(row, row_number, **kwargs)
|
||||
|
||||
|
||||
class CustomRateAdmin(RateAdmin):
|
||||
"""Admin interface for the Rate class."""
|
||||
|
@ -1,6 +1,9 @@
|
||||
"""Main JSON interface views."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
@ -31,6 +34,60 @@ from .status import check_system_health, is_worker_running
|
||||
from .version import inventreeApiText
|
||||
from .views import AjaxView
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
class LicenseViewSerializer(serializers.Serializer):
|
||||
"""Serializer for license information."""
|
||||
|
||||
backend = serializers.CharField(help_text='Backend licenses texts', read_only=True)
|
||||
frontend = serializers.CharField(
|
||||
help_text='Frontend licenses texts', read_only=True
|
||||
)
|
||||
|
||||
|
||||
class LicenseView(APIView):
|
||||
"""Simple JSON endpoint for InvenTree license information."""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def read_license_file(self, path: Path) -> list:
|
||||
"""Extract license information from the provided file.
|
||||
|
||||
Arguments:
|
||||
path: Path to the license file
|
||||
|
||||
Returns: A list of items containing the license information
|
||||
"""
|
||||
# Check if the file exists
|
||||
if not path.exists():
|
||||
logger.error("License file not found at '%s'", path)
|
||||
return []
|
||||
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
except json.JSONDecodeError as e:
|
||||
logger.exception("Failed to parse license file '%s': %s", path, e)
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.exception("Exception while reading license file '%s': %s", path, e)
|
||||
return []
|
||||
|
||||
# Ensure consistent string between backend and frontend licenses
|
||||
return [{key.lower(): value for key, value in entry.items()} for entry in data]
|
||||
|
||||
@extend_schema(responses={200: OpenApiResponse(response=LicenseViewSerializer)})
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Return information about the InvenTree server."""
|
||||
backend = Path(__file__).parent.joinpath('licenses.txt')
|
||||
frontend = Path(__file__).parent.parent.joinpath(
|
||||
'web/static/web/.vite/dependencies.json'
|
||||
)
|
||||
return JsonResponse({
|
||||
'backend': self.read_license_file(backend),
|
||||
'frontend': self.read_license_file(frontend),
|
||||
})
|
||||
|
||||
|
||||
class VersionViewSerializer(serializers.Serializer):
|
||||
"""Serializer for a single version."""
|
||||
|
@ -1,11 +1,14 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 185
|
||||
INVENTREE_API_VERSION = 186
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v186 - 2024-03-26 : https://github.com/inventree/InvenTree/pull/6855
|
||||
- Adds license information to the API
|
||||
|
||||
v185 - 2024-03-24 : https://github.com/inventree/InvenTree/pull/6836
|
||||
- Remove /plugin/activate endpoint
|
||||
- Update docstrings and typing for various API endpoints (no functional changes)
|
||||
|
@ -265,6 +265,26 @@ MIDDLEWARE = CONFIG.get(
|
||||
],
|
||||
)
|
||||
|
||||
# In DEBUG mode, add support for django-querycount
|
||||
# Ref: https://github.com/bradmontgomery/django-querycount
|
||||
if DEBUG and get_boolean_setting(
|
||||
'INVENTREE_DEBUG_QUERYCOUNT', 'debug_querycount', False
|
||||
):
|
||||
MIDDLEWARE.append('querycount.middleware.QueryCountMiddleware')
|
||||
|
||||
QUERYCOUNT = {
|
||||
'THRESHOLDS': {
|
||||
'MEDIUM': 50,
|
||||
'HIGH': 200,
|
||||
'MIN_TIME_TO_LOG': 0,
|
||||
'MIN_QUERY_COUNT_TO_LOG': 0,
|
||||
},
|
||||
'IGNORE_REQUEST_PATTERNS': ['^(?!\/(api)?(plugin)?\/).*'],
|
||||
'IGNORE_SQL_PATTERNS': [],
|
||||
'DISPLAY_DUPLICATES': 3,
|
||||
'RESPONSE_HEADER': 'X-Django-Query-Count',
|
||||
}
|
||||
|
||||
AUTHENTICATION_BACKENDS = CONFIG.get(
|
||||
'authentication_backends',
|
||||
[
|
||||
|
@ -1148,12 +1148,8 @@ class TestSettings(InvenTreeTestCase):
|
||||
|
||||
superuser = True
|
||||
|
||||
def in_env_context(self, envs=None):
|
||||
def in_env_context(self, envs):
|
||||
"""Patch the env to include the given dict."""
|
||||
# Set default - see B006
|
||||
if envs is None:
|
||||
envs = {}
|
||||
|
||||
return mock.patch.dict(os.environ, envs)
|
||||
|
||||
def run_reload(self, envs=None):
|
||||
@ -1588,15 +1584,15 @@ class ClassValidationMixinTest(TestCase):
|
||||
|
||||
def test(self):
|
||||
"""Test function."""
|
||||
pass
|
||||
...
|
||||
|
||||
def test1(self):
|
||||
"""Test function."""
|
||||
pass
|
||||
...
|
||||
|
||||
def test2(self):
|
||||
"""Test function."""
|
||||
pass
|
||||
...
|
||||
|
||||
required_attributes = ['NAME']
|
||||
required_overrides = [test, [test1, test2]]
|
||||
@ -1616,11 +1612,11 @@ class ClassValidationMixinTest(TestCase):
|
||||
|
||||
def test(self):
|
||||
"""Test function."""
|
||||
pass
|
||||
...
|
||||
|
||||
def test2(self):
|
||||
"""Test function."""
|
||||
pass
|
||||
...
|
||||
|
||||
TestClass.validate()
|
||||
|
||||
@ -1643,7 +1639,7 @@ class ClassValidationMixinTest(TestCase):
|
||||
|
||||
def test2(self):
|
||||
"""Test function."""
|
||||
pass
|
||||
...
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
NotImplementedError,
|
||||
|
@ -157,12 +157,14 @@ class UserMixin:
|
||||
|
||||
if type(assign_all) is not bool:
|
||||
# Raise exception if common mistake is made!
|
||||
raise TypeError('assignRole: assign_all must be a boolean value')
|
||||
raise TypeError(
|
||||
'assignRole: assign_all must be a boolean value'
|
||||
) # pragma: no cover
|
||||
|
||||
if not role and not assign_all:
|
||||
raise ValueError(
|
||||
'assignRole: either role must be provided, or assign_all must be set'
|
||||
)
|
||||
) # pragma: no cover
|
||||
|
||||
if not assign_all and role:
|
||||
rule, perm = role.split('.')
|
||||
@ -241,14 +243,18 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
||||
yield # your test will be run here
|
||||
|
||||
if verbose:
|
||||
msg = '\r\n%s' % json.dumps(context.captured_queries, indent=4)
|
||||
msg = '\r\n%s' % json.dumps(
|
||||
context.captured_queries, indent=4
|
||||
) # pragma: no cover
|
||||
else:
|
||||
msg = None
|
||||
|
||||
n = len(context.captured_queries)
|
||||
|
||||
if debug:
|
||||
print(f'Expected less than {value} queries, got {n} queries')
|
||||
print(
|
||||
f'Expected less than {value} queries, got {n} queries'
|
||||
) # pragma: no cover
|
||||
|
||||
self.assertLess(n, value, msg=msg)
|
||||
|
||||
@ -258,7 +264,7 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
||||
if expected_code is None:
|
||||
return
|
||||
|
||||
if expected_code != response.status_code:
|
||||
if expected_code != response.status_code: # pragma: no cover
|
||||
print(
|
||||
f"Unexpected {method} response at '{url}': status_code = {response.status_code}"
|
||||
)
|
||||
@ -280,11 +286,7 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
||||
response = self.client.options(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
actions = response.data.get('actions', None)
|
||||
|
||||
if not actions:
|
||||
actions = {}
|
||||
|
||||
actions = response.data.get('actions', {})
|
||||
return actions
|
||||
|
||||
def get(self, url, data=None, expected_code=200, format='json', **kwargs):
|
||||
|
@ -39,7 +39,14 @@ from stock.urls import stock_urls
|
||||
from web.urls import api_urls as web_api_urls
|
||||
from web.urls import urlpatterns as platform_urls
|
||||
|
||||
from .api import APISearchView, InfoView, NotFoundView, VersionTextView, VersionView
|
||||
from .api import (
|
||||
APISearchView,
|
||||
InfoView,
|
||||
LicenseView,
|
||||
NotFoundView,
|
||||
VersionTextView,
|
||||
VersionView,
|
||||
)
|
||||
from .magic_login import GetSimpleLoginView
|
||||
from .social_auth_urls import (
|
||||
EmailListView,
|
||||
@ -99,6 +106,7 @@ apipatterns = [
|
||||
name='schema',
|
||||
),
|
||||
# InvenTree information endpoints
|
||||
path('license/', LicenseView.as_view(), name='api-license'), # license info
|
||||
path(
|
||||
'version-text', VersionTextView.as_view(), name='api-version-text'
|
||||
), # version text
|
||||
@ -377,6 +385,7 @@ if settings.ENABLE_CLASSIC_FRONTEND:
|
||||
|
||||
classic_frontendpatterns = [
|
||||
# Apps
|
||||
#
|
||||
path('build/', include(build_urls)),
|
||||
path('common/', include(common_urls)),
|
||||
path('company/', include(company_urls)),
|
||||
|
@ -46,7 +46,7 @@ class NewsFeedTests(TestCase):
|
||||
"""Tests that news feed is updated when accessing a valid URL."""
|
||||
try:
|
||||
common_tasks.update_news_feed()
|
||||
except Exception as ex:
|
||||
except Exception as ex: # pragma: no cover
|
||||
self.fail(f'News feed raised exceptions: {ex}')
|
||||
|
||||
self.assertNotEqual(NewsFeedEntry.objects.all().count(), 0)
|
||||
|
@ -1048,18 +1048,18 @@ class ColorThemeTest(TestCase):
|
||||
"""Test that default choices are returned."""
|
||||
result = ColorTheme.get_color_themes_choices()
|
||||
|
||||
# skip
|
||||
# skip due to directories not being set up
|
||||
if not result:
|
||||
return
|
||||
return # pragma: no cover
|
||||
self.assertIn(('default', 'Default'), result)
|
||||
|
||||
def test_valid_choice(self):
|
||||
"""Check that is_valid_choice works correctly."""
|
||||
result = ColorTheme.get_color_themes_choices()
|
||||
|
||||
# skip
|
||||
# skip due to directories not being set up
|
||||
if not result:
|
||||
return
|
||||
return # pragma: no cover
|
||||
|
||||
# check wrong reference
|
||||
self.assertFalse(ColorTheme.is_valid_choice('abcdd'))
|
||||
@ -1099,10 +1099,12 @@ class CurrencyAPITests(InvenTreeAPITestCase):
|
||||
# Exit early
|
||||
return
|
||||
|
||||
# Delay and try again
|
||||
time.sleep(10)
|
||||
# Delay and try again - might have problems with exchange rate endpoint
|
||||
time.sleep(10) # pragma: no cover
|
||||
|
||||
raise TimeoutError('Could not refresh currency exchange data after 5 attempts')
|
||||
raise TimeoutError(
|
||||
'Could not refresh currency exchange data after 5 attempts'
|
||||
) # pragma: no cover
|
||||
|
||||
|
||||
class NotesImageTest(InvenTreeAPITestCase):
|
||||
|
@ -5,9 +5,6 @@ from InvenTree.unit_test import InvenTreeTestCase
|
||||
from .transition import StateTransitionMixin, TransitionMethod, storage
|
||||
|
||||
# Global variables to determine which transition classes raises an exception
|
||||
global raise_storage
|
||||
global raise_function
|
||||
|
||||
raise_storage = False
|
||||
raise_function = False
|
||||
|
||||
@ -90,7 +87,7 @@ class TransitionTests(InvenTreeTestCase):
|
||||
if raise_function:
|
||||
return 1234
|
||||
else:
|
||||
return False
|
||||
return False # pragma: no cover # Return false to keep other transitions working
|
||||
|
||||
storage.collect()
|
||||
self.assertIn(ValidImplementationNoEffect, storage.list)
|
||||
|
@ -23,7 +23,7 @@ class GeneralStatus(StatusCode):
|
||||
|
||||
def GHI(self): # This should be ignored
|
||||
"""A invalid function."""
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class GeneralStateTest(InvenTreeTestCase):
|
||||
|
@ -64,11 +64,6 @@ class LabelTest(InvenTreeAPITestCase):
|
||||
response = self.get(url, {'enabled': False})
|
||||
self.assertEqual(len(response.data), 0)
|
||||
|
||||
# Disable each report
|
||||
for label in labels:
|
||||
label.enabled = False
|
||||
label.save()
|
||||
|
||||
# Filter by "enabled" status
|
||||
response = self.get(url, {'enabled': True})
|
||||
self.assertEqual(len(response.data), 0)
|
||||
|
@ -143,7 +143,7 @@ class MachineAPITest(TestMachineRegistryMixin, InvenTreeAPITestCase):
|
||||
for error in errors_msgs:
|
||||
if re.match(pattern, error):
|
||||
break
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
errors_str = '\n'.join([f'- {e}' for e in errors_msgs])
|
||||
self.fail(
|
||||
f"""Error message matching pattern '{pattern}' not found in machine registry errors:\n{errors_str}"""
|
||||
|
@ -272,15 +272,21 @@ class TestLabelPrinterMachineType(TestMachineRegistryMixin, InvenTreeAPITestCase
|
||||
self.print_labels.assert_called_once()
|
||||
self.assertEqual(self.print_labels.call_args.args[0], self.machine.machine)
|
||||
self.assertEqual(self.print_labels.call_args.args[1], label)
|
||||
self.assertQuerySetEqual(
|
||||
self.print_labels.call_args.args[2], parts, transform=lambda x: x
|
||||
)
|
||||
|
||||
# TODO re-activate test
|
||||
# self.assertQuerySetEqual(
|
||||
# self.print_labels.call_args.args[2], parts, transform=lambda x: x
|
||||
# )
|
||||
|
||||
self.assertIn('printing_options', self.print_labels.call_args.kwargs)
|
||||
self.assertEqual(
|
||||
self.print_labels.call_args.kwargs['printing_options'],
|
||||
{'copies': 1, 'test_option': 2},
|
||||
)
|
||||
|
||||
return
|
||||
# TODO re-activate test
|
||||
|
||||
# test the single print label method calls
|
||||
self.assertEqual(self.print_label.call_count, 2)
|
||||
self.assertEqual(self.print_label.call_args.args[0], self.machine.machine)
|
||||
|
@ -62,16 +62,19 @@ class SettingsMixin:
|
||||
"""Does this plugin use custom global settings."""
|
||||
return bool(self.settings)
|
||||
|
||||
def get_setting(self, key, cache=False):
|
||||
def get_setting(self, key, cache=False, backup_value=None):
|
||||
"""Return the 'value' of the setting associated with this plugin.
|
||||
|
||||
Arguments:
|
||||
key: The 'name' of the setting value to be retrieved
|
||||
cache: Whether to use RAM cached value (default = False)
|
||||
backup_value: A backup value to return if the setting is not found
|
||||
"""
|
||||
from plugin.models import PluginSetting
|
||||
|
||||
return PluginSetting.get_setting(key, plugin=self.plugin_config(), cache=cache)
|
||||
return PluginSetting.get_setting(
|
||||
key, plugin=self.plugin_config(), cache=cache, backup_value=backup_value
|
||||
)
|
||||
|
||||
def set_setting(self, key, value, user=None):
|
||||
"""Set plugin setting value by key."""
|
||||
|
@ -11,6 +11,15 @@ from stock.models import StockItem, StockLocation
|
||||
class LocatePluginTests(InvenTreeAPITestCase):
|
||||
"""Tests for LocateMixin."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up the test case."""
|
||||
super().setUp()
|
||||
|
||||
# Activate plugin
|
||||
config = registry.get_plugin('samplelocate').plugin_config()
|
||||
config.active = True
|
||||
config.save()
|
||||
|
||||
fixtures = ['category', 'part', 'location', 'stock']
|
||||
|
||||
def test_installed(self):
|
||||
|
@ -554,9 +554,6 @@ class StockItemListTest(StockAPITestCase):
|
||||
)
|
||||
self.assertTrue(len(response.data) < StockItem.objects.count())
|
||||
|
||||
for result in response.data:
|
||||
self.assertIsNone(result['location'])
|
||||
|
||||
# Filter with "cascade=True"
|
||||
response = self.get(
|
||||
self.list_url, {'location': 'null', 'cascade': True}, expected_code=200
|
||||
|
@ -26,6 +26,11 @@ class TemplateTagTest(InvenTreeTestCase):
|
||||
def test_spa_bundle(self):
|
||||
"""Test the 'spa_bundle' template tag."""
|
||||
resp = spa_helper.spa_bundle()
|
||||
if not resp:
|
||||
# No Vite, no test
|
||||
# TODO: Add a test for the non-Vite case (docker)
|
||||
return # pragma: no cover
|
||||
|
||||
shipped_js = resp.split('<script type="module" src="')[1:]
|
||||
self.assertTrue(len(shipped_js) > 0)
|
||||
self.assertTrue(len(shipped_js) == 3)
|
||||
|
42
src/backend/package-lock.json
generated
42
src/backend/package-lock.json
generated
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "InvenTree",
|
||||
"name": "backend",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@ -95,9 +95,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/object-schema": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
|
||||
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw=="
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
|
||||
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
@ -472,9 +472,9 @@
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
@ -506,11 +506,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
||||
"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
|
||||
"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
|
||||
"dependencies": {
|
||||
"flatted": "^3.1.0",
|
||||
"flatted": "^3.2.9",
|
||||
"keyv": "^4.5.3",
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@ -518,9 +519,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ=="
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
|
||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
@ -672,6 +673,11 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@ -682,6 +688,14 @@
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
|
@ -2,6 +2,7 @@
|
||||
-c requirements.txt
|
||||
coverage[toml] # Unit test coverage
|
||||
coveralls==2.1.2 # Coveralls linking (for tracking coverage) # PINNED 2022-06-28 - Old version needed for correct upload
|
||||
django-querycount # Display number of URL queries for requests
|
||||
django-slowtests # Show which unit tests are running slowly
|
||||
django-test-migrations # Unit testing for database migrations
|
||||
isort # python import sorting
|
||||
|
@ -1,8 +1,8 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile src/backend/requirements-dev.in -o src/backend/requirements-dev.txt --python-version=3.9 --no-strip-extras
|
||||
asgiref==3.8.0
|
||||
asgiref==3.8.1
|
||||
# via django
|
||||
build==1.1.1
|
||||
build==1.2.1
|
||||
# via pip-tools
|
||||
certifi==2024.2.2
|
||||
# via requests
|
||||
@ -25,17 +25,18 @@ distlib==0.3.8
|
||||
# via virtualenv
|
||||
django==4.2.11
|
||||
# via django-slowtests
|
||||
django-querycount==0.8.3
|
||||
django-slowtests==1.1.1
|
||||
django-test-migrations==1.3.0
|
||||
docopt==0.6.2
|
||||
# via coveralls
|
||||
filelock==3.13.1
|
||||
filelock==3.13.3
|
||||
# via virtualenv
|
||||
identify==2.5.35
|
||||
# via pre-commit
|
||||
idna==3.6
|
||||
# via requests
|
||||
importlib-metadata==6.11.0
|
||||
importlib-metadata==7.0.0
|
||||
# via build
|
||||
isort==5.13.2
|
||||
nodeenv==1.8.0
|
||||
@ -49,7 +50,7 @@ pip-tools==7.4.1
|
||||
platformdirs==4.2.0
|
||||
# via virtualenv
|
||||
pre-commit==3.7.0
|
||||
pycparser==2.21
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pyproject-hooks==1.0.0
|
||||
# via
|
||||
@ -72,7 +73,7 @@ tomli==2.0.1
|
||||
# build
|
||||
# pip-tools
|
||||
# pyproject-hooks
|
||||
typing-extensions==4.10.0
|
||||
typing-extensions==4.11.0
|
||||
# via
|
||||
# asgiref
|
||||
# django-test-migrations
|
||||
|
@ -17,7 +17,7 @@ django-maintenance-mode # Shut down application while reloading
|
||||
django-markdownify # Markdown rendering
|
||||
django-mptt # Modified Preorder Tree Traversal
|
||||
django-markdownify # Markdown rendering
|
||||
django-money>=3.0.0,<3.5.0 # Django app for currency management # FIXED 2023-10-31 3.3.0 breaks due to https://github.com/django-money/django-money/issues/731
|
||||
django-money>=3.0.0,<3.3.0 # Django app for currency management # FIXED 2023-10-31 3.3.0 breaks due to https://github.com/django-money/django-money/issues/731
|
||||
django-mptt # Modified Preorder Tree Traversal
|
||||
django-redis>=5.0.0 # Redis integration
|
||||
django-q2 # Background task scheduling
|
||||
@ -41,6 +41,7 @@ gunicorn # Gunicorn web server
|
||||
pdf2image # PDF to image conversion
|
||||
pillow # Image manipulation
|
||||
pint==0.21 # Unit conversion # FIXED 2023-05-30 breaks tests https://github.com/matmair/InvenTree/actions/runs/5095665936/jobs/9160852560
|
||||
pip-licenses # License information for installed packages
|
||||
python-barcode[images] # Barcode generator
|
||||
python-dotenv # Environment variable management
|
||||
pyyaml>=6.0.1 # YAML parsing
|
||||
|
@ -1,6 +1,6 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile src/backend/requirements.in -o src/backend/requirements.txt --python-version=3.9 --no-strip-extras
|
||||
asgiref==3.8.0
|
||||
asgiref==3.8.1
|
||||
# via
|
||||
# django
|
||||
# django-cors-headers
|
||||
@ -84,7 +84,7 @@ django-cors-headers==4.3.1
|
||||
django-crispy-forms==1.14.0
|
||||
django-dbbackup==4.1.0
|
||||
django-error-report-2==0.4.2
|
||||
django-filter==24.1
|
||||
django-filter==24.2
|
||||
django-flags==5.0.13
|
||||
django-formtools==2.5.1
|
||||
django-ical==1.9.2
|
||||
@ -96,7 +96,7 @@ django-markdownify==0.9.3
|
||||
django-money==3.2.0
|
||||
django-mptt==0.16.0
|
||||
django-otp==1.3.0
|
||||
django-picklefield==3.1
|
||||
django-picklefield==3.2
|
||||
# via django-q2
|
||||
django-q-sentry==0.1.6
|
||||
django-q2==1.6.2
|
||||
@ -117,12 +117,12 @@ djangorestframework==3.14.0
|
||||
# djangorestframework-simplejwt
|
||||
# drf-spectacular
|
||||
djangorestframework-simplejwt[crypto]==5.3.1
|
||||
drf-spectacular==0.27.1
|
||||
drf-spectacular==0.27.2
|
||||
dulwich==0.21.7
|
||||
et-xmlfile==1.1.0
|
||||
# via openpyxl
|
||||
feedparser==6.0.11
|
||||
fonttools[woff]==4.50.0
|
||||
fonttools[woff]==4.51.0
|
||||
# via weasyprint
|
||||
googleapis-common-protos==1.63.0
|
||||
# via
|
||||
@ -137,7 +137,7 @@ icalendar==5.0.12
|
||||
# via django-ical
|
||||
idna==3.6
|
||||
# via requests
|
||||
importlib-metadata==6.11.0
|
||||
importlib-metadata==7.0.0
|
||||
# via
|
||||
# django-q2
|
||||
# markdown
|
||||
@ -164,7 +164,7 @@ odfpy==1.4.1
|
||||
# via tablib
|
||||
openpyxl==3.1.2
|
||||
# via tablib
|
||||
opentelemetry-api==1.23.0
|
||||
opentelemetry-api==1.24.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
@ -174,43 +174,43 @@ opentelemetry-api==1.23.0
|
||||
# opentelemetry-instrumentation-requests
|
||||
# opentelemetry-instrumentation-wsgi
|
||||
# opentelemetry-sdk
|
||||
opentelemetry-exporter-otlp==1.23.0
|
||||
opentelemetry-exporter-otlp-proto-common==1.23.0
|
||||
opentelemetry-exporter-otlp==1.24.0
|
||||
opentelemetry-exporter-otlp-proto-common==1.24.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
opentelemetry-exporter-otlp-proto-grpc==1.23.0
|
||||
opentelemetry-exporter-otlp-proto-grpc==1.24.0
|
||||
# via opentelemetry-exporter-otlp
|
||||
opentelemetry-exporter-otlp-proto-http==1.23.0
|
||||
opentelemetry-exporter-otlp-proto-http==1.24.0
|
||||
# via opentelemetry-exporter-otlp
|
||||
opentelemetry-instrumentation==0.44b0
|
||||
opentelemetry-instrumentation==0.45b0
|
||||
# via
|
||||
# opentelemetry-instrumentation-django
|
||||
# opentelemetry-instrumentation-redis
|
||||
# opentelemetry-instrumentation-requests
|
||||
# opentelemetry-instrumentation-wsgi
|
||||
opentelemetry-instrumentation-django==0.44b0
|
||||
opentelemetry-instrumentation-redis==0.44b0
|
||||
opentelemetry-instrumentation-requests==0.44b0
|
||||
opentelemetry-instrumentation-wsgi==0.44b0
|
||||
opentelemetry-instrumentation-django==0.45b0
|
||||
opentelemetry-instrumentation-redis==0.45b0
|
||||
opentelemetry-instrumentation-requests==0.45b0
|
||||
opentelemetry-instrumentation-wsgi==0.45b0
|
||||
# via opentelemetry-instrumentation-django
|
||||
opentelemetry-proto==1.23.0
|
||||
opentelemetry-proto==1.24.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-common
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
opentelemetry-sdk==1.23.0
|
||||
opentelemetry-sdk==1.24.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-grpc
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
opentelemetry-semantic-conventions==0.44b0
|
||||
opentelemetry-semantic-conventions==0.45b0
|
||||
# via
|
||||
# opentelemetry-instrumentation-django
|
||||
# opentelemetry-instrumentation-redis
|
||||
# opentelemetry-instrumentation-requests
|
||||
# opentelemetry-instrumentation-wsgi
|
||||
# opentelemetry-sdk
|
||||
opentelemetry-util-http==0.44b0
|
||||
opentelemetry-util-http==0.45b0
|
||||
# via
|
||||
# opentelemetry-instrumentation-django
|
||||
# opentelemetry-instrumentation-requests
|
||||
@ -218,7 +218,7 @@ opentelemetry-util-http==0.44b0
|
||||
packaging==24.0
|
||||
# via gunicorn
|
||||
pdf2image==1.17.0
|
||||
pillow==10.2.0
|
||||
pillow==10.3.0
|
||||
# via
|
||||
# django-stdimage
|
||||
# pdf2image
|
||||
@ -226,13 +226,16 @@ pillow==10.2.0
|
||||
# qrcode
|
||||
# weasyprint
|
||||
pint==0.21
|
||||
pip-licenses==4.4.0
|
||||
prettytable==3.10.0
|
||||
# via pip-licenses
|
||||
protobuf==4.25.3
|
||||
# via
|
||||
# googleapis-common-protos
|
||||
# opentelemetry-proto
|
||||
py-moneyed==3.0
|
||||
# via django-money
|
||||
pycparser==2.21
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pydyf==0.9.0
|
||||
# via weasyprint
|
||||
@ -279,13 +282,13 @@ requests==2.31.0
|
||||
# django-allauth
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
# requests-oauthlib
|
||||
requests-oauthlib==1.4.0
|
||||
requests-oauthlib==2.0.0
|
||||
# via django-allauth
|
||||
rpds-py==0.18.0
|
||||
# via
|
||||
# jsonschema
|
||||
# referencing
|
||||
sentry-sdk==1.43.0
|
||||
sentry-sdk==1.44.1
|
||||
# via django-q-sentry
|
||||
setuptools==69.2.0
|
||||
# via
|
||||
@ -309,7 +312,7 @@ tinycss2==1.2.1
|
||||
# bleach
|
||||
# cssselect2
|
||||
# weasyprint
|
||||
typing-extensions==4.10.0
|
||||
typing-extensions==4.11.0
|
||||
# via
|
||||
# asgiref
|
||||
# drf-spectacular
|
||||
@ -325,6 +328,8 @@ urllib3==2.2.1
|
||||
# dulwich
|
||||
# requests
|
||||
# sentry-sdk
|
||||
wcwidth==0.2.13
|
||||
# via prettytable
|
||||
weasyprint==61.2
|
||||
# via django-weasyprint
|
||||
webencodings==0.5.1
|
||||
|
Reference in New Issue
Block a user