2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-15 11:35:41 +00:00

Merge branch 'master' into matmair/issue6281

This commit is contained in:
Matthias Mair
2025-01-22 19:49:44 +01:00
committed by GitHub
14 changed files with 246 additions and 211 deletions

2
src/backend/InvenTree/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Files generated during unit testing
_testfolder/

View File

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

View File

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

View File

@ -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"""
<div class='alert alert-block alert-danger'>
Template file <em>{template_file}</em> does not exist.
</div>
"""
# 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

View File

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

View File

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

View File

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

View File

@ -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, '<h1>123</h1>\n')
# Wrong sample
response = render_template(
ErrorSource(), 'sample/wrongsample.html', {'abc': 123}
)
self.assertIn('lert alert-block alert-danger', response)
self.assertIn('Template file <em>sample/wrongsample.html</em>', response)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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