diff --git a/src/backend/InvenTree/.gitignore b/src/backend/InvenTree/.gitignore new file mode 100644 index 0000000000..4af4473ebe --- /dev/null +++ b/src/backend/InvenTree/.gitignore @@ -0,0 +1,2 @@ +# Files generated during unit testing +_testfolder/ diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 7d1eccf75b..ebfdca7691 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -11,29 +11,32 @@ v304 - 2025-01-25 : https://github.com/inventree/InvenTree/pull/6293 - Removes a considerable amount of old auth endpoints - Introduces allauth based REST API -v303 - 2025-01-20 - https://github.com/inventree/InvenTree/pull/8915 +v304 - 2025-01-22 : https://github.com/inventree/InvenTree/pull/8940 + - Adds "category" filter to build list API + +v303 - 2025-01-20 : https://github.com/inventree/InvenTree/pull/8915 - Adds "start_date" field to Build model and API endpoints - Adds additional API filtering and sorting options for Build list -v302 - 2025-01-18 - https://github.com/inventree/InvenTree/pull/8905 +v302 - 2025-01-18 : https://github.com/inventree/InvenTree/pull/8905 - Fix schema definition on the /label/print endpoint -v301 - 2025-01-14 - https://github.com/inventree/InvenTree/pull/8894 +v301 - 2025-01-14 : https://github.com/inventree/InvenTree/pull/8894 - Remove ui preferences from the API -v300 - 2025-01-13 - https://github.com/inventree/InvenTree/pull/8886 +v300 - 2025-01-13 : https://github.com/inventree/InvenTree/pull/8886 - Allow null value for 'expiry_date' field introduced in #8867 -v299 - 2025-01-10 - https://github.com/inventree/InvenTree/pull/8867 +v299 - 2025-01-10 : https://github.com/inventree/InvenTree/pull/8867 - Adds 'expiry_date' field to the PurchaseOrderReceive API endpoint - Adds 'default_expiry` field to the PartBriefSerializer, affecting API endpoints which use it -v298 - 2025-01-07 - https://github.com/inventree/InvenTree/pull/8848 +v298 - 2025-01-07 : https://github.com/inventree/InvenTree/pull/8848 - Adds 'created_by' field to PurchaseOrder API endpoints - Adds 'created_by' field to SalesOrder API endpoints - Adds 'created_by' field to ReturnOrder API endpoints -v297 - 2024-12-29 - https://github.com/inventree/InvenTree/pull/8438 +v297 - 2024-12-29 : https://github.com/inventree/InvenTree/pull/8438 - Adjustments to the CustomUserState API endpoints and serializers v296 - 2024-12-25 : https://github.com/inventree/InvenTree/pull/8732 diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py index 455548effb..4f8274b5ea 100644 --- a/src/backend/InvenTree/build/api.py +++ b/src/backend/InvenTree/build/api.py @@ -13,7 +13,7 @@ from rest_framework.exceptions import ValidationError import build.admin import build.serializers import common.models -import part.models +import part.models as part_models from build.models import Build, BuildItem, BuildLine from build.status_codes import BuildStatus, BuildStatusGroups from generic.states.api import StatusView @@ -77,7 +77,10 @@ class BuildFilter(rest_filters.FilterSet): return queryset part = rest_filters.ModelChoiceFilter( - queryset=part.models.Part.objects.all(), field_name='part', method='filter_part' + queryset=part_models.Part.objects.all(), + field_name='part', + method='filter_part', + label=_('Part'), ) def filter_part(self, queryset, name, part): @@ -94,6 +97,17 @@ class BuildFilter(rest_filters.FilterSet): else: return queryset.filter(part=part) + category = rest_filters.ModelChoiceFilter( + queryset=part_models.PartCategory.objects.all(), + method='filter_category', + label=_('Category'), + ) + + def filter_category(self, queryset, name, category): + """Filter by part category (including sub-categories).""" + categories = category.get_descendants(include_self=True) + return queryset.filter(part__category__in=categories) + ancestor = rest_filters.ModelChoiceFilter( queryset=Build.objects.all(), label=_('Ancestor Build'), @@ -417,7 +431,7 @@ class BuildLineFilter(rest_filters.FilterSet): ) part = rest_filters.ModelChoiceFilter( - queryset=part.models.Part.objects.all(), + queryset=part_models.Part.objects.all(), label=_('Part'), field_name='bom_item__sub_part', ) @@ -729,7 +743,7 @@ class BuildItemFilter(rest_filters.FilterSet): return queryset part = rest_filters.ModelChoiceFilter( - queryset=part.models.Part.objects.all(), + queryset=part_models.Part.objects.all(), label=_('Part'), method='filter_part', field_name='stock_item__part', diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index 4c78ff14e7..d3f45c4a19 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -10,7 +10,6 @@ import traceback from importlib.metadata import entry_points from importlib.util import module_from_spec -from django import template from django.conf import settings from django.core.exceptions import AppRegistryNotReady from django.db.utils import IntegrityError @@ -244,35 +243,3 @@ def get_plugins(pkg, baseclass, path=None): # endregion - - -# region templates -def render_template(plugin, template_file, context=None): - """Locate and render a template file, available in the global template context.""" - try: - tmp = template.loader.get_template(template_file) - except template.TemplateDoesNotExist: - logger.exception( - "Plugin %s could not locate template '%s'", plugin.slug, template_file - ) - - return f""" -
- Template file {template_file} does not exist. -
- """ - - # Render with the provided context - html = tmp.render(context) - - return html - - -def render_text(text, context=None): - """Locate a raw string with provided context.""" - ctx = template.Context(context) - - return template.Template(text).render(ctx) - - -# endregion diff --git a/src/backend/InvenTree/plugin/models.py b/src/backend/InvenTree/plugin/models.py index 18d4c6c51f..3068dbcfad 100644 --- a/src/backend/InvenTree/plugin/models.py +++ b/src/backend/InvenTree/plugin/models.py @@ -142,7 +142,7 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model): def save(self, force_insert=False, force_update=False, *args, **kwargs): """Extend save method to reload plugins if the 'active' status changes.""" - reload = kwargs.pop('no_reload', False) # check if no_reload flag is set + no_reload = kwargs.pop('no_reload', False) # check if no_reload flag is set super().save(force_insert, force_update, *args, **kwargs) @@ -150,10 +150,10 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model): # Force active if builtin self.active = True - if not reload and self.active != self.__org_active: + if not no_reload and self.active != self.__org_active: if settings.PLUGIN_TESTING: - warnings.warn('A reload was triggered', stacklevel=2) - registry.reload_plugins() + warnings.warn('A plugin registry reload was triggered', stacklevel=2) + registry.reload_plugins(full_reload=True, force_reload=True, collect=True) @admin.display(boolean=True, description=_('Installed')) def is_installed(self) -> bool: diff --git a/src/backend/InvenTree/plugin/registry.py b/src/backend/InvenTree/plugin/registry.py index 014f41d72d..f6d0b17b23 100644 --- a/src/backend/InvenTree/plugin/registry.py +++ b/src/backend/InvenTree/plugin/registry.py @@ -453,6 +453,8 @@ class PluginsRegistry: Args: plugin: Plugin module + configs: Plugin configuration dictionary + force_reload (bool, optional): Force reload of plugin. Defaults to False. """ from InvenTree import version @@ -485,6 +487,7 @@ class PluginsRegistry: # Check if this is a 'builtin' plugin builtin = plugin.check_is_builtin() + sample = plugin.check_is_sample() package_name = None @@ -510,11 +513,37 @@ class PluginsRegistry: # Initialize package - we can be sure that an admin has activated the plugin logger.debug('Loading plugin `%s`', plg_name) + # If this is a third-party plugin, reload the source module + # This is required to ensure that separate processes are using the same code + if not builtin and not sample: + plugin_name = plugin.__name__ + module_name = plugin.__module__ + + if plugin_module := sys.modules.get(module_name): + logger.debug('Reloading plugin `%s`', plg_name) + # Reload the module + try: + importlib.reload(plugin_module) + plugin = getattr(plugin_module, plugin_name) + except ModuleNotFoundError: + # No module found - try to import it directly + try: + raw_module = _load_source( + module_name, plugin_module.__file__ + ) + plugin = getattr(raw_module, plugin_name) + except Exception: + pass + except Exception: + logger.exception('Failed to reload plugin `%s`', plg_name) + try: t_start = time.time() plg_i: InvenTreePlugin = plugin() dt = time.time() - t_start logger.debug('Loaded plugin `%s` in %.3fs', plg_name, dt) + except ModuleNotFoundError as e: + raise e except Exception as error: handle_error( error, log_name='init' @@ -745,7 +774,7 @@ class PluginsRegistry: if old_hash != self.registry_hash: try: - logger.debug( + logger.info( 'Updating plugin registry hash: %s', str(self.registry_hash) ) set_global_setting( @@ -839,11 +868,16 @@ def _load_source(modname, filename): See https://docs.python.org/3/whatsnew/3.12.html#imp """ - loader = importlib.machinery.SourceFileLoader(modname, filename) - spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) + if modname in sys.modules: + del sys.modules[modname] + + # loader = importlib.machinery.SourceFileLoader(modname, filename) + spec = importlib.util.spec_from_file_location(modname, filename) # , loader=loader) module = importlib.util.module_from_spec(spec) - # The module is always executed and not cached in sys.modules. - # Uncomment the following line to cache the module. - # sys.modules[module.__name__] = module - loader.exec_module(module) + + sys.modules[module.__name__] = module + + if spec.loader: + spec.loader.exec_module(module) + return module diff --git a/src/backend/InvenTree/plugin/test_api.py b/src/backend/InvenTree/plugin/test_api.py index 2b73bea416..5f3a481ce8 100644 --- a/src/backend/InvenTree/plugin/test_api.py +++ b/src/backend/InvenTree/plugin/test_api.py @@ -205,7 +205,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase): plg_inactive.active = True plg_inactive.save() - self.assertEqual(cm.warning.args[0], 'A reload was triggered') + self.assertEqual(cm.warning.args[0], 'A plugin registry reload was triggered') def test_check_plugin(self): """Test check_plugin function.""" diff --git a/src/backend/InvenTree/plugin/test_helpers.py b/src/backend/InvenTree/plugin/test_helpers.py deleted file mode 100644 index 95cff3ea95..0000000000 --- a/src/backend/InvenTree/plugin/test_helpers.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Unit tests for helpers.py.""" - -from django.test import TestCase - -from .helpers import render_template - - -class HelperTests(TestCase): - """Tests for helpers.""" - - def test_render_template(self): - """Check if render_template helper works.""" - - class ErrorSource: - slug = 'sampleplg' - - # working sample - response = render_template(ErrorSource(), 'sample/sample.html', {'abc': 123}) - self.assertEqual(response, '

123

\n') - - # Wrong sample - response = render_template( - ErrorSource(), 'sample/wrongsample.html', {'abc': 123} - ) - self.assertIn('lert alert-block alert-danger', response) - self.assertIn('Template file sample/wrongsample.html', response) diff --git a/src/backend/InvenTree/plugin/test_plugin.py b/src/backend/InvenTree/plugin/test_plugin.py index cc034acdee..5f0810073b 100644 --- a/src/backend/InvenTree/plugin/test_plugin.py +++ b/src/backend/InvenTree/plugin/test_plugin.py @@ -4,9 +4,11 @@ import os import shutil import subprocess import tempfile +import textwrap from datetime import datetime from pathlib import Path from unittest import mock +from unittest.mock import patch from django.test import TestCase, override_settings @@ -18,6 +20,9 @@ from plugin.samples.integration.another_sample import ( ) from plugin.samples.integration.sample import SampleIntegrationPlugin +# Directory for testing plugins during CI +PLUGIN_TEST_DIR = '_testfolder/test_plugins' + class PluginTagTests(TestCase): """Tests for the plugin extras.""" @@ -287,3 +292,111 @@ class RegistryTests(TestCase): self.assertEqual( registry.errors.get('init')[0]['broken_sample'], "'This is a dummy error'" ) + + @override_settings(PLUGIN_TESTING=True, PLUGIN_TESTING_SETUP=True) + @patch.dict(os.environ, {'INVENTREE_PLUGIN_TEST_DIR': PLUGIN_TEST_DIR}) + def test_registry_reload(self): + """Test that the registry correctly reloads plugin modules. + + - Create a simple plugin which we can change the version + - Ensure that the "hash" of the plugin registry changes + """ + dummy_file = os.path.join(PLUGIN_TEST_DIR, 'dummy_ci_plugin.py') + + # Ensure the plugin dir exists + os.makedirs(PLUGIN_TEST_DIR, exist_ok=True) + + # Create an __init__.py file + init_file = os.path.join(PLUGIN_TEST_DIR, '__init__.py') + if not os.path.exists(init_file): + with open(os.path.join(init_file), 'w', encoding='utf-8') as f: + f.write('') + + def plugin_content(version): + """Return the content of the plugin file.""" + content = f""" + from plugin import InvenTreePlugin + + PLG_VERSION = "{version}" + + print(">>> LOADING DUMMY PLUGIN v" + PLG_VERSION + " <<<") + + class DummyCIPlugin(InvenTreePlugin): + + NAME = "DummyCIPlugin" + SLUG = "dummyci" + TITLE = "Dummy plugin for CI testing" + + VERSION = PLG_VERSION + + """ + + return textwrap.dedent(content) + + def create_plugin_file( + version: str, enabled: bool = True, reload: bool = True + ) -> str: + """Create a plugin file with the given version. + + Arguments: + version: The version string to use for the plugin file + enabled: Whether the plugin should be enabled or not + + Returns: + str: The plugin registry hash + """ + import time + + content = plugin_content(version) + + with open(dummy_file, 'w', encoding='utf-8') as f: + f.write(content) + + # Wait for the file to be written + time.sleep(2) + + if reload: + # Ensure the plugin is activated + registry.set_plugin_state('dummyci', enabled) + registry.reload_plugins( + full_reload=True, collect=True, force_reload=True + ) + + registry.update_plugin_hash() + + return registry.registry_hash + + # Initial hash, with plugin disabled + hash_disabled = create_plugin_file('0.0.1', enabled=False, reload=False) + + # Perform initial registry reload + registry.reload_plugins(full_reload=True, collect=True, force_reload=True) + + # Start plugin in known state + registry.set_plugin_state('dummyci', False) + + hash_disabled = create_plugin_file('0.0.1', enabled=False) + + # Enable the plugin + hash_enabled = create_plugin_file('0.1.0', enabled=True) + + # Hash must be different! + self.assertNotEqual(hash_disabled, hash_enabled) + + plugin_hash = hash_enabled + + for v in ['0.1.1', '7.1.2', '1.2.1', '4.0.1']: + h = create_plugin_file(v, enabled=True) + self.assertNotEqual(plugin_hash, h) + plugin_hash = h + + # Revert back to original 'version' + h = create_plugin_file('0.1.0', enabled=True) + self.assertEqual(hash_enabled, h) + + # Disable the plugin + h = create_plugin_file('0.0.1', enabled=False) + self.assertEqual(hash_disabled, h) + + # Finally, ensure that the plugin file is removed after testing + os.remove(dummy_file) diff --git a/src/frontend/package.json b/src/frontend/package.json index bdc84ebdb0..eb3d4a295e 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -91,7 +91,7 @@ "nyc": "^17.1.0", "rollup-plugin-license": "^3.5.3", "typescript": "^5.7.3", - "vite": "^6.0.7", + "vite": "^6.0.9", "vite-plugin-babel-macros": "^1.0.6", "vite-plugin-istanbul": "^6.0.2" } diff --git a/src/frontend/src/tables/FilterSelectDrawer.tsx b/src/frontend/src/tables/FilterSelectDrawer.tsx index cdb82344cc..c209d30d04 100644 --- a/src/frontend/src/tables/FilterSelectDrawer.tsx +++ b/src/frontend/src/tables/FilterSelectDrawer.tsx @@ -179,10 +179,17 @@ function FilterAddGroup({ // Determine the "type" of filter (default = boolean) const filterType: TableFilterType = useMemo(() => { - return ( - availableFilters?.find((flt) => flt.name === selectedFilter)?.type ?? - 'boolean' - ); + const filter = availableFilters?.find((flt) => flt.name === selectedFilter); + + if (filter?.type) { + return filter.type; + } else if (filter?.choices) { + // If choices are provided, it is a choice filter + return 'choice'; + } else { + // Default fallback + return 'boolean'; + } }, [selectedFilter]); const setSelectedValue = useCallback( diff --git a/src/frontend/src/tables/build/BuildOrderTable.tsx b/src/frontend/src/tables/build/BuildOrderTable.tsx index faa96952ff..9aae96df0a 100644 --- a/src/frontend/src/tables/build/BuildOrderTable.tsx +++ b/src/frontend/src/tables/build/BuildOrderTable.tsx @@ -8,7 +8,9 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { useBuildOrderFields } from '../../forms/BuildForms'; +import { shortenString } from '../../functions/tables'; import { + useFilters, useOwnerFilters, useProjectCodeFilters, useUserFilters @@ -131,6 +133,17 @@ export function BuildOrderTable({ const ownerFilters = useOwnerFilters(); const userFilters = useUserFilters(); + const categoryFilters = useFilters({ + url: apiUrl(ApiEndpoints.category_list), + transform: (item) => ({ + value: item.pk, + label: shortenString({ + str: item.pathstring, + len: 50 + }) + }) + }); + const tableFilters: TableFilter[] = useMemo(() => { const filters: TableFilter[] = [ OutstandingFilter(), @@ -177,7 +190,13 @@ export function BuildOrderTable({ description: t`Filter by user who issued this order`, choices: userFilters.choices }, - ResponsibleFilter({ choices: ownerFilters.choices }) + ResponsibleFilter({ choices: ownerFilters.choices }), + { + name: 'category', + label: t`Category`, + description: t`Filter by part category`, + choices: categoryFilters.choices + } ]; // If we are filtering on a specific part, we can include the "include variants" filter @@ -193,6 +212,7 @@ export function BuildOrderTable({ return filters; }, [ partId, + categoryFilters.choices, projectCodeFilters.choices, ownerFilters.choices, userFilters.choices diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index bb478953fa..e7ff844e01 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -1724,41 +1724,21 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" integrity sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA== -"@rollup/rollup-android-arm-eabi@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz#e0f5350845090ca09690fe4a472717f3b8aae225" - integrity sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww== - "@rollup/rollup-android-arm-eabi@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz#14c737dc19603a096568044eadaa60395eefb809" integrity sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q== -"@rollup/rollup-android-arm64@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz#08270faef6747e2716d3e978a8bbf479f75fb19a" - integrity sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ== - "@rollup/rollup-android-arm64@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz#9d81ea54fc5650eb4ebbc0a7d84cee331bfa30ad" integrity sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w== -"@rollup/rollup-darwin-arm64@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz#691671133b350661328d42c8dbdedd56dfb97dfd" - integrity sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw== - "@rollup/rollup-darwin-arm64@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz#29448cb1370cf678b50743d2e392be18470abc23" integrity sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q== -"@rollup/rollup-darwin-x64@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz#b2ec52a1615f24b1cd40bc8906ae31af81e8a342" - integrity sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg== - "@rollup/rollup-darwin-x64@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz#0ca99741c3ed096700557a43bb03359450c7857d" @@ -1774,41 +1754,21 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz#dfba762a023063dc901610722995286df4a48360" integrity sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw== -"@rollup/rollup-linux-arm-gnueabihf@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz#217f01f304808920680bd269002df38e25d9205f" - integrity sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw== - "@rollup/rollup-linux-arm-gnueabihf@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz#b9da54171726266c5ef4237f462a85b3c3cf6ac9" integrity sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg== -"@rollup/rollup-linux-arm-musleabihf@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz#93ac1c5a1e389f4482a2edaeec41fcffee54a930" - integrity sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ== - "@rollup/rollup-linux-arm-musleabihf@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz#b9db69b3f85f5529eb992936d8f411ee6d04297b" integrity sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug== -"@rollup/rollup-linux-arm64-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz#a7f146787d6041fecc4ecdf1aa72234661ca94a4" - integrity sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w== - "@rollup/rollup-linux-arm64-gnu@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz#2550cf9bb4d47d917fd1ab4af756d7bbc3ee1528" integrity sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw== -"@rollup/rollup-linux-arm64-musl@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz#6a37236189648e678bd564d6e8ca798f42cf42c5" - integrity sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw== - "@rollup/rollup-linux-arm64-musl@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz#9d06b26d286c7dded6336961a2f83e48330e0c80" @@ -1819,81 +1779,41 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz#e957bb8fee0c8021329a34ca8dfa825826ee0e2e" integrity sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ== -"@rollup/rollup-linux-powerpc64le-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz#5661420dc463bec31ecb2d17d113de858cfcfe2d" - integrity sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w== - "@rollup/rollup-linux-powerpc64le-gnu@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz#e8585075ddfb389222c5aada39ea62d6d2511ccc" integrity sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw== -"@rollup/rollup-linux-riscv64-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz#cb00342b7432bdef723aa606281de2f522d6dcf7" - integrity sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A== - "@rollup/rollup-linux-riscv64-gnu@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz#7d0d40cee7946ccaa5a4e19a35c6925444696a9e" integrity sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw== -"@rollup/rollup-linux-s390x-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz#0708889674dccecccd28e2befccf791e0767fcb7" - integrity sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ== - "@rollup/rollup-linux-s390x-gnu@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz#c2dcd8a4b08b2f2778eceb7a5a5dfde6240ebdea" integrity sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA== -"@rollup/rollup-linux-x64-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz#a135b040b21582e91cfed2267ccfc7d589e1dbc6" - integrity sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA== - "@rollup/rollup-linux-x64-gnu@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz#183637d91456877cb83d0a0315eb4788573aa588" integrity sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg== -"@rollup/rollup-linux-x64-musl@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz#88395a81a3ab7ee3dc8dc31a73ff62ed3185f34d" - integrity sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g== - "@rollup/rollup-linux-x64-musl@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz#036a4c860662519f1f9453807547fd2a11d5bb01" integrity sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow== -"@rollup/rollup-win32-arm64-msvc@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz#12ee49233b1125f2c1da38392f63b1dbb0c31bba" - integrity sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w== - "@rollup/rollup-win32-arm64-msvc@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz#51cad812456e616bfe4db5238fb9c7497e042a52" integrity sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw== -"@rollup/rollup-win32-ia32-msvc@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz#0f987b134c6b3123c22842b33ba0c2b6fb78cc3b" - integrity sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg== - "@rollup/rollup-win32-ia32-msvc@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz#661c8b3e4cd60f51deaa39d153aac4566e748e5e" integrity sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw== -"@rollup/rollup-win32-x64-msvc@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz#f2feb149235a5dc1deb5439758f8871255e5a161" - integrity sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ== - "@rollup/rollup-win32-x64-msvc@4.30.1": version "4.30.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz#73bf1885ff052b82fbb0f82f8671f73c36e9137c" @@ -4204,16 +4124,7 @@ postcss@8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.4.43: - version "8.4.45" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" - integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.1" - source-map-js "^1.2.0" - -postcss@^8.4.49: +postcss@^8.4.43, postcss@^8.4.49: version "8.5.1" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214" integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ== @@ -4579,32 +4490,7 @@ rollup-plugin-license@^3.5.3: spdx-expression-validate "~2.0.0" spdx-satisfies "~5.0.1" -rollup@^4.20.0: - version "4.22.5" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.5.tgz#d5108cc470249417e50492456253884d19f5d40f" - integrity sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w== - dependencies: - "@types/estree" "1.0.6" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.22.5" - "@rollup/rollup-android-arm64" "4.22.5" - "@rollup/rollup-darwin-arm64" "4.22.5" - "@rollup/rollup-darwin-x64" "4.22.5" - "@rollup/rollup-linux-arm-gnueabihf" "4.22.5" - "@rollup/rollup-linux-arm-musleabihf" "4.22.5" - "@rollup/rollup-linux-arm64-gnu" "4.22.5" - "@rollup/rollup-linux-arm64-musl" "4.22.5" - "@rollup/rollup-linux-powerpc64le-gnu" "4.22.5" - "@rollup/rollup-linux-riscv64-gnu" "4.22.5" - "@rollup/rollup-linux-s390x-gnu" "4.22.5" - "@rollup/rollup-linux-x64-gnu" "4.22.5" - "@rollup/rollup-linux-x64-musl" "4.22.5" - "@rollup/rollup-win32-arm64-msvc" "4.22.5" - "@rollup/rollup-win32-ia32-msvc" "4.22.5" - "@rollup/rollup-win32-x64-msvc" "4.22.5" - fsevents "~2.3.2" - -rollup@^4.23.0: +rollup@^4.20.0, rollup@^4.23.0: version "4.30.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.30.1.tgz#d5c3d066055259366cdc3eb6f1d051c5d6afaf74" integrity sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w== @@ -5147,10 +5033,10 @@ vite@^5.0.0, vite@^5.0.11: optionalDependencies: fsevents "~2.3.3" -vite@^6.0.7: - version "6.0.7" - resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.7.tgz#f0f8c120733b04af52b4a1e3e7cb54eb851a799b" - integrity sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ== +vite@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.9.tgz#0a830b767ef7aa762360b56bdef955c1395dc1ee" + integrity sha512-MSgUxHcaXLtnBPktkbUSoQUANApKYuxZ6DrbVENlIorbhL2dZydTLaZ01tjUoE3szeFzlFk9ANOKk0xurh4MKA== dependencies: esbuild "^0.24.2" postcss "^8.4.49" diff --git a/tasks.py b/tasks.py index 14187cea0e..709bc4ee71 100644 --- a/tasks.py +++ b/tasks.py @@ -910,13 +910,28 @@ def gunicorn(c, address='0.0.0.0:8000', workers=None): run(c, cmd, pty=True) -@task(pre=[wait], help={'address': 'Server address:port (default=127.0.0.1:8000)'}) -def server(c, address='127.0.0.1:8000'): +@task( + pre=[wait], + help={ + 'address': 'Server address:port (default=127.0.0.1:8000)', + 'no_reload': 'Do not automatically reload the server in response to code changes', + 'no_threading': 'Disable multi-threading for the development server', + }, +) +def server(c, address='127.0.0.1:8000', no_reload=False, no_threading=False): """Launch a (development) server using Django's in-built webserver. Note: This is *not* sufficient for a production installation. """ - manage(c, f'runserver {address}', pty=True) + cmd = f'runserver {address}' + + if no_reload: + cmd += ' --noreload' + + if no_threading: + cmd += ' --nothreading' + + manage(c, cmd, pty=True) @task(pre=[wait])