mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'inventree:master' into pr/ChristianSchindler/6305
This commit is contained in:
commit
839d9f9075
@ -1,33 +0,0 @@
|
||||
version = 1
|
||||
exclude_patterns = [
|
||||
"docs/docs/javascripts/**", # Docs: Helpers
|
||||
"docs/ci/**", # Docs: CI
|
||||
"InvenTree/InvenTree/static/**", # Backend: CUI static files
|
||||
"ci/**", # Backend: CI
|
||||
"InvenTree/**/migrations/*.py", # Backend: Migration files
|
||||
"src/frontend/src/locales/**", # Frontend: Translations
|
||||
]
|
||||
test_patterns = ["**/test_*.py", "**/test.py", "**/tests.py"]
|
||||
|
||||
|
||||
[[analyzers]]
|
||||
name = "shell"
|
||||
|
||||
[[analyzers]]
|
||||
name = "javascript"
|
||||
|
||||
[analyzers.meta]
|
||||
plugins = ["react"]
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
||||
|
||||
[[analyzers]]
|
||||
name = "docker"
|
||||
|
||||
[[analyzers]]
|
||||
name = "test-coverage"
|
||||
enabled = false
|
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -43,7 +43,9 @@ body:
|
||||
label: "Deployment Method"
|
||||
options:
|
||||
- label: "Docker"
|
||||
- label: "Package"
|
||||
- label: "Bare metal"
|
||||
- label: "Other - added info in Steps to Reproduce"
|
||||
- type: textarea
|
||||
id: version-info
|
||||
validations:
|
||||
|
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@ -16,6 +16,8 @@ jobs:
|
||||
backport:
|
||||
name: Backport PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
if: |
|
||||
github.event.pull_request.merged == true
|
||||
&& contains(github.event.pull_request.labels.*.name, 'backport')
|
||||
|
1
.github/workflows/check_translations.yaml
vendored
1
.github/workflows/check_translations.yaml
vendored
@ -27,6 +27,7 @@ jobs:
|
||||
INVENTREE_MEDIA_ROOT: ./media
|
||||
INVENTREE_STATIC_ROOT: ./static
|
||||
INVENTREE_BACKUP_DIR: ./backup
|
||||
INVENTREE_SITE_URL: http://localhost:8000
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
|
4
.github/workflows/docker.yaml
vendored
4
.github/workflows/docker.yaml
vendored
@ -76,8 +76,8 @@ jobs:
|
||||
python-version: ${{ env.python_version }}
|
||||
- name: Version Check
|
||||
run: |
|
||||
pip install requests
|
||||
pip install pyyaml
|
||||
pip install requests==2.31.0
|
||||
pip install pyyaml==6.0.1
|
||||
python3 ci/version_check.py
|
||||
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
|
||||
|
8
.github/workflows/qc_checks.yaml
vendored
8
.github/workflows/qc_checks.yaml
vendored
@ -92,7 +92,7 @@ jobs:
|
||||
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # pin@v3.0.1
|
||||
- name: Check Version
|
||||
run: |
|
||||
pip install requests
|
||||
pip install requests==2.31.0
|
||||
python3 ci/version_check.py
|
||||
|
||||
mkdocs:
|
||||
@ -110,7 +110,7 @@ jobs:
|
||||
python-version: ${{ env.python_version }}
|
||||
- name: Check Config
|
||||
run: |
|
||||
pip install pyyaml
|
||||
pip install pyyaml==6.0.1
|
||||
pip install -r docs/requirements.txt
|
||||
python docs/ci/check_mkdocs_config.py
|
||||
- name: Check Links
|
||||
@ -156,7 +156,7 @@ jobs:
|
||||
- name: Download public schema
|
||||
if: needs.paths-filter.outputs.api == 'false'
|
||||
run: |
|
||||
pip install requests >/dev/null 2>&1
|
||||
pip install requests==2.31.0 >/dev/null 2>&1
|
||||
version="$(python3 ci/version_check.py only_version 2>&1)"
|
||||
echo "Version: $version"
|
||||
url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml"
|
||||
@ -175,7 +175,7 @@ jobs:
|
||||
id: version
|
||||
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
|
||||
run: |
|
||||
pip install requests >/dev/null 2>&1
|
||||
pip install requests==2.31.0 >/dev/null 2>&1
|
||||
version="$(python3 ci/version_check.py only_version 2>&1)"
|
||||
echo "Version: $version"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||
- name: Version Check
|
||||
run: |
|
||||
pip install requests
|
||||
pip install requests==2.31.0
|
||||
python3 ci/version_check.py
|
||||
- name: Push to Stable Branch
|
||||
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
|
||||
|
2
.github/workflows/scorecard.yml
vendored
2
.github/workflows/scorecard.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: false
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
|
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@ -10,12 +10,14 @@ env:
|
||||
node_version: 18
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
18
Dockerfile
18
Dockerfile
@ -10,7 +10,7 @@
|
||||
# - Monitors source files for any changes, and live-reloads server
|
||||
|
||||
ARG base_image=python:3.11-alpine3.18
|
||||
FROM ${base_image} as inventree_base
|
||||
FROM ${base_image} AS inventree_base
|
||||
|
||||
# Build arguments for this image
|
||||
ARG commit_tag=""
|
||||
@ -92,7 +92,7 @@ RUN chmod +x init.sh
|
||||
|
||||
ENTRYPOINT ["/bin/ash", "./init.sh"]
|
||||
|
||||
FROM inventree_base as prebuild
|
||||
FROM inventree_base AS prebuild
|
||||
|
||||
ENV PATH=/root/.local/bin:$PATH
|
||||
RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
|
||||
@ -100,9 +100,9 @@ RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
|
||||
apk --purge del .build-deps
|
||||
|
||||
# Frontend builder image:
|
||||
FROM prebuild as frontend
|
||||
FROM prebuild AS frontend
|
||||
|
||||
RUN apk add --no-cache --update nodejs npm && npm install -g yarn
|
||||
RUN apk add --no-cache --update nodejs npm && npm install -g yarn@v1.22.22
|
||||
RUN yarn config set network-timeout 600000 -g
|
||||
COPY InvenTree ${INVENTREE_HOME}/InvenTree
|
||||
COPY src ${INVENTREE_HOME}/src
|
||||
@ -117,7 +117,7 @@ COPY db_version ${INVENTREE_HOME}/db_version
|
||||
# InvenTree production image:
|
||||
# - Copies required files from local directory
|
||||
# - Starts a gunicorn webserver
|
||||
FROM inventree_base as production
|
||||
FROM inventree_base AS production
|
||||
|
||||
ENV INVENTREE_DEBUG=False
|
||||
|
||||
@ -134,11 +134,9 @@ COPY InvenTree ./InvenTree
|
||||
COPY --from=frontend ${INVENTREE_HOME}/InvenTree/web/static/web ./InvenTree/web/static/web
|
||||
|
||||
# Launch the production server
|
||||
# TODO: Work out why environment variables cannot be interpolated in this command
|
||||
# TODO: e.g. -b ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} fails here
|
||||
CMD gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ./InvenTree
|
||||
|
||||
FROM inventree_base as dev
|
||||
FROM inventree_base AS dev
|
||||
|
||||
# Vite server (for local frontend development)
|
||||
EXPOSE 5173
|
||||
@ -146,11 +144,11 @@ EXPOSE 5173
|
||||
# Install packages required for building python packages
|
||||
RUN ./install_build_packages.sh
|
||||
|
||||
RUN pip install uv --no-cache-dir && pip install -r base_requirements.txt --no-cache
|
||||
RUN pip install uv==0.1.26 --no-cache-dir && pip install -r base_requirements.txt --no-cache
|
||||
|
||||
# Install nodejs / npm / yarn
|
||||
|
||||
RUN apk add --no-cache --update nodejs npm && npm install -g yarn
|
||||
RUN apk add --no-cache --update nodejs npm && npm install -g yarn@v1.22.22
|
||||
RUN yarn config set network-timeout 600000 -g
|
||||
|
||||
# The development image requires the source code to be mounted to /home/inventree/
|
||||
|
@ -347,7 +347,7 @@ def get_secret_key():
|
||||
|
||||
# Create a random key file
|
||||
options = string.digits + string.ascii_letters + string.punctuation
|
||||
key = ''.join([random.choice(options) for i in range(100)])
|
||||
key = ''.join([random.choice(options) for _idx in range(100)])
|
||||
secret_key_file.write_text(key)
|
||||
|
||||
logger.debug("Loading SECRET_KEY from '%s'", secret_key_file)
|
||||
|
@ -95,7 +95,7 @@ def from_engineering_notation(value):
|
||||
"""
|
||||
value = str(value).strip()
|
||||
|
||||
pattern = f'(\d+)([a-zA-Z]+)(\d+)(.*)'
|
||||
pattern = '(\d+)([a-zA-Z]+)(\d+)(.*)'
|
||||
|
||||
if match := re.match(pattern, value):
|
||||
left, prefix, right, suffix = match.groups()
|
||||
@ -198,7 +198,6 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
|
||||
break
|
||||
except Exception as exc:
|
||||
value = None
|
||||
pass
|
||||
|
||||
if value is None:
|
||||
if unit:
|
||||
|
@ -20,7 +20,7 @@ class InvenTreeExchange(SimpleExchangeBackend):
|
||||
|
||||
name = 'InvenTreeExchange'
|
||||
|
||||
def get_rates(self, **kwargs) -> None:
|
||||
def get_rates(self, **kwargs) -> dict:
|
||||
"""Set the requested currency codes and get rates."""
|
||||
from common.models import InvenTreeSetting
|
||||
from plugin import registry
|
||||
|
@ -17,11 +17,10 @@ class InvenTreeDateFilter(rest_filters.DateFilter):
|
||||
|
||||
def filter(self, qs, value):
|
||||
"""Override the filter method to handle timezones correctly."""
|
||||
if settings.USE_TZ:
|
||||
if value is not None:
|
||||
tz = timezone.get_current_timezone()
|
||||
value = datetime(value.year, value.month, value.day)
|
||||
value = make_aware(value, tz, True)
|
||||
if settings.USE_TZ and value is not None:
|
||||
tz = timezone.get_current_timezone()
|
||||
value = datetime(value.year, value.month, value.day)
|
||||
value = make_aware(value, tz, True)
|
||||
|
||||
return super().filter(qs, value)
|
||||
|
||||
|
@ -69,7 +69,7 @@ def construct_format_regex(fmt_string: str) -> str:
|
||||
for group in string.Formatter().parse(fmt_string):
|
||||
prefix = group[0] # Prefix (literal text appearing before this group)
|
||||
name = group[1] # Name of this format variable
|
||||
format = group[2] # Format specifier e.g :04d
|
||||
_fmt = group[2] # Format specifier e.g :04d
|
||||
|
||||
rep = [
|
||||
'+',
|
||||
@ -106,16 +106,16 @@ def construct_format_regex(fmt_string: str) -> str:
|
||||
# Add a named capture group for the format entry
|
||||
if name:
|
||||
# Check if integer values are required
|
||||
if format.endswith('d'):
|
||||
chr = '\d'
|
||||
if _fmt.endswith('d'):
|
||||
c = '\d'
|
||||
else:
|
||||
chr = '.'
|
||||
c = '.'
|
||||
|
||||
# Specify width
|
||||
# TODO: Introspect required width
|
||||
w = '+'
|
||||
|
||||
pattern += f'(?P<{name}>{chr}{w})'
|
||||
pattern += f'(?P<{name}>{c}{w})'
|
||||
|
||||
pattern += '$'
|
||||
|
||||
|
@ -248,11 +248,7 @@ def str2int(text, default=None):
|
||||
|
||||
def is_bool(text):
|
||||
"""Determine if a string value 'looks' like a boolean."""
|
||||
if str2bool(text, True):
|
||||
return True
|
||||
elif str2bool(text, False):
|
||||
return True
|
||||
return False
|
||||
return str2bool(text, True) or str2bool(text, False)
|
||||
|
||||
|
||||
def isNull(text):
|
||||
@ -473,7 +469,7 @@ def DownloadFile(
|
||||
return response
|
||||
|
||||
|
||||
def increment_serial_number(serial: str):
|
||||
def increment_serial_number(serial):
|
||||
"""Given a serial number, (attempt to) generate the *next* serial number.
|
||||
|
||||
Note: This method is exposed to custom plugins.
|
||||
@ -857,9 +853,9 @@ def hash_barcode(barcode_data):
|
||||
barcode_data = str(barcode_data).strip()
|
||||
barcode_data = remove_non_printable_characters(barcode_data)
|
||||
|
||||
hash = hashlib.md5(str(barcode_data).encode())
|
||||
barcode_hash = hashlib.md5(str(barcode_data).encode())
|
||||
|
||||
return str(hash.hexdigest())
|
||||
return str(barcode_hash.hexdigest())
|
||||
|
||||
|
||||
def hash_file(filename: Union[str, Path], storage: Union[Storage, None] = None):
|
||||
|
@ -7,7 +7,7 @@ from rest_framework import permissions
|
||||
import users.models
|
||||
|
||||
|
||||
def get_model_for_view(view, raise_error=True):
|
||||
def get_model_for_view(view):
|
||||
"""Attempt to introspect the 'model' type for an API view."""
|
||||
if hasattr(view, 'get_permission_model'):
|
||||
return view.get_permission_model()
|
||||
|
@ -426,17 +426,17 @@ TEMPLATES = [
|
||||
REST_FRAMEWORK = {
|
||||
'EXCEPTION_HANDLER': 'InvenTree.exceptions.exception_handler',
|
||||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M',
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'users.authentication.ApiTokenAuthentication',
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
),
|
||||
],
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
'rest_framework.permissions.DjangoModelPermissions',
|
||||
'InvenTree.permissions.RolePermission',
|
||||
),
|
||||
],
|
||||
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||
'DEFAULT_METADATA_CLASS': 'InvenTree.metadata.InvenTreeMetadata',
|
||||
'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'],
|
||||
@ -462,8 +462,8 @@ REST_AUTH_REGISTER_SERIALIZERS = {
|
||||
if USE_JWT:
|
||||
JWT_AUTH_COOKIE = 'inventree-auth'
|
||||
JWT_AUTH_REFRESH_COOKIE = 'inventree-token'
|
||||
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] + (
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
|
||||
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append(
|
||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||
)
|
||||
INSTALLED_APPS.append('rest_framework_simplejwt')
|
||||
|
||||
@ -1017,7 +1017,10 @@ if not ALLOWED_HOSTS:
|
||||
# Ensure that the ALLOWED_HOSTS do not contain any scheme info
|
||||
for i, host in enumerate(ALLOWED_HOSTS):
|
||||
if '://' in host:
|
||||
ALLOWED_HOSTS[i] = host.split('://')[1]
|
||||
ALLOWED_HOSTS[i] = host = host.split('://')[1]
|
||||
|
||||
if ':' in host:
|
||||
ALLOWED_HOSTS[i] = host = host.split(':')[0]
|
||||
|
||||
# List of trusted origins for unsafe requests
|
||||
# Ref: https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins
|
||||
|
@ -85,7 +85,7 @@ for name, provider in providers.registry.provider_map.items():
|
||||
cls
|
||||
for cls in prov_mod.__dict__.values()
|
||||
if isinstance(cls, type)
|
||||
and not cls == OAuth2Adapter
|
||||
and cls != OAuth2Adapter
|
||||
and issubclass(cls, OAuth2Adapter)
|
||||
]
|
||||
|
||||
|
@ -27,7 +27,7 @@ def get_provider_app(provider):
|
||||
return apps.first()
|
||||
|
||||
|
||||
def check_provider(provider, raise_error=False):
|
||||
def check_provider(provider):
|
||||
"""Check if the given provider is correctly configured.
|
||||
|
||||
To be correctly configured, the following must be true:
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -379,11 +379,11 @@ class BomItemResource(InvenTreeResource):
|
||||
report_skipped = False
|
||||
clean_model_instances = True
|
||||
|
||||
exclude = ['checksum', 'id', 'part', 'sub_part', 'validated']
|
||||
exclude = ['checksum', 'part', 'sub_part', 'validated']
|
||||
|
||||
level = Field(attribute='level', column_name=_('BOM Level'), readonly=True)
|
||||
|
||||
bom_id = Field(
|
||||
id = Field(
|
||||
attribute='pk', column_name=_('BOM Item ID'), widget=widgets.IntegerWidget()
|
||||
)
|
||||
|
||||
@ -476,7 +476,6 @@ class BomItemResource(InvenTreeResource):
|
||||
if is_importing:
|
||||
to_remove += [
|
||||
'level',
|
||||
'pk',
|
||||
'part',
|
||||
'part__IPN',
|
||||
'part__name',
|
||||
|
@ -267,6 +267,8 @@ class CategoryDetail(CategoryMixin, CustomRetrieveUpdateDestroyAPI):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
|
@ -30,7 +30,7 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin):
|
||||
}
|
||||
|
||||
TME_IS_QRCODE_REGEX = re.compile(r'([^\s:]+:[^\s:]+\s+)+(\S+(\s|$)+)+')
|
||||
TME_IS_BARCODE2D_REGEX = re.compile(r'(([^\s]+)(\s+|$))+')
|
||||
TME_IS_OLD_BARCODE2D_REGEX = re.compile(r'(([^\s]+)(\s+|$))+')
|
||||
|
||||
# Custom field mapping
|
||||
TME_QRCODE_FIELDS = {
|
||||
@ -52,22 +52,19 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin):
|
||||
key, value = item.split(':')
|
||||
if key in self.TME_QRCODE_FIELDS:
|
||||
barcode_fields[self.TME_QRCODE_FIELDS[key]] = value
|
||||
|
||||
return barcode_fields
|
||||
|
||||
elif self.TME_IS_BARCODE2D_REGEX.fullmatch(barcode_data):
|
||||
# 2D Barcode format e.g. "PWBP-302 1PMPNWBP-302 Q1 K19361337/1"
|
||||
elif self.TME_IS_OLD_BARCODE2D_REGEX.fullmatch(barcode_data):
|
||||
# Old 2D Barcode format e.g. "PWBP-302 1PMPNWBP-302 Q1 K19361337/1"
|
||||
for item in barcode_data.split(' '):
|
||||
for k, v in self.ecia_field_map().items():
|
||||
if item.startswith(k):
|
||||
barcode_fields[v] = item[len(k) :]
|
||||
else:
|
||||
return {}
|
||||
barcode_fields = self.parse_ecia_barcode2d(barcode_data)
|
||||
|
||||
# Custom handling for order number
|
||||
if SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER in barcode_fields:
|
||||
order_number = barcode_fields[SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER]
|
||||
if SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER in barcode_fields:
|
||||
order_number = barcode_fields[SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER]
|
||||
order_number = order_number.split('/')[0]
|
||||
barcode_fields[SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER] = order_number
|
||||
barcode_fields[SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER] = order_number
|
||||
|
||||
return barcode_fields
|
||||
|
@ -13,12 +13,12 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='stockitemtestresult',
|
||||
name='finished_datetime',
|
||||
field=models.DateTimeField(blank=True, help_text='The timestamp of the test finish', verbose_name='Finished'),
|
||||
field=models.DateTimeField(blank=True, help_text='The timestamp of the test finish', null=True, verbose_name='Finished'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stockitemtestresult',
|
||||
name='started_datetime',
|
||||
field=models.DateTimeField(blank=True, help_text='The timestamp of the test start', verbose_name='Started'),
|
||||
field=models.DateTimeField(blank=True, help_text='The timestamp of the test start', null=True, verbose_name='Started'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stockitemtestresult',
|
||||
|
@ -11,7 +11,7 @@
|
||||
[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7179/badge)](https://bestpractices.coreinfrastructure.org/projects/7179)
|
||||
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/inventree/InvenTree/badge)](https://securityscorecards.dev/viewer/?uri=github.com/inventree/InvenTree)
|
||||
[![Netlify Status](https://api.netlify.com/api/v1/badges/9bbb2101-0a4d-41e7-ad56-b63fb6053094/deploy-status)](https://app.netlify.com/sites/inventree/deploys)
|
||||
[![DeepSource](https://app.deepsource.com/gh/inventree/InvenTree.svg/?label=active+issues&show_trend=false&token=trZWqixKLk2t-RXtpSIAslVJ)](https://app.deepsource.com/gh/inventree/InvenTree/)
|
||||
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=inventree_InvenTree&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=inventree_InvenTree)
|
||||
|
||||
[![Coveralls](https://img.shields.io/coveralls/github/inventree/InvenTree)](https://coveralls.io/github/inventree/InvenTree)
|
||||
[![Crowdin](https://badges.crowdin.net/inventree/localized.svg)](https://crowdin.com/project/inventree)
|
||||
|
@ -90,7 +90,7 @@ function detect_envs() {
|
||||
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
|
||||
|
||||
# Install parser
|
||||
pip install jc -q
|
||||
pip install jc==1.25.2 -q
|
||||
|
||||
# Load config
|
||||
local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
|
||||
|
@ -45,6 +45,8 @@
|
||||
root * /var/www/media
|
||||
file_server
|
||||
|
||||
header Content-Disposition attachment
|
||||
|
||||
forward_auth {$INVENTREE_SERVER:"http://inventree-server:8000"} {
|
||||
uri /auth/
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ pip==24.0
|
||||
pip-tools==7.4.1
|
||||
platformdirs==4.2.0
|
||||
# via virtualenv
|
||||
pre-commit==3.6.2
|
||||
pre-commit==3.7.0
|
||||
pycparser==2.21
|
||||
# via cffi
|
||||
pyproject-hooks==1.0.0
|
||||
|
@ -23,27 +23,27 @@
|
||||
"@mantine/core": "<7",
|
||||
"@mantine/dates": "<7",
|
||||
"@mantine/dropzone": "<7",
|
||||
"@mantine/form": "<7",
|
||||
"@mantine/form": "<8",
|
||||
"@mantine/hooks": "<7",
|
||||
"@mantine/modals": "<7",
|
||||
"@mantine/notifications": "<7",
|
||||
"@naisutech/react-tree": "^3.1.0",
|
||||
"@sentry/react": "^7.108.0",
|
||||
"@sentry/react": "^7.109.0",
|
||||
"@tabler/icons-react": "^3.1.0",
|
||||
"@tanstack/react-query": "^5.28.8",
|
||||
"@tanstack/react-query": "^5.28.9",
|
||||
"@uiw/codemirror-theme-vscode": "^4.21.25",
|
||||
"@uiw/react-codemirror": "^4.21.25",
|
||||
"@uiw/react-split": "^5.9.3",
|
||||
"axios": "^1.6.7",
|
||||
"dayjs": "^1.11.10",
|
||||
"easymde": "^2.18.0",
|
||||
"embla-carousel-react": "^7.1.0",
|
||||
"embla-carousel-react": "^8.0.0",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"mantine-datatable": "<7",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-grid-layout": "^1.4.4",
|
||||
"react-hook-form": "^7.51.1",
|
||||
"react-hook-form": "^7.51.2",
|
||||
"react-is": "^18.2.0",
|
||||
"react-router-dom": "^6.22.1",
|
||||
"react-select": "^5.8.0",
|
||||
@ -58,15 +58,15 @@
|
||||
"@lingui/cli": "^4.7.2",
|
||||
"@lingui/macro": "^4.7.2",
|
||||
"@playwright/test": "^1.41.2",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/react": "^18.2.71",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/node": "^20.12.2",
|
||||
"@types/react": "^18.2.73",
|
||||
"@types/react-dom": "^18.2.23",
|
||||
"@types/react-grid-layout": "^1.3.5",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite": "^5.2.7",
|
||||
"vite-plugin-babel-macros": "^1.0.6"
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { useDisclosure } from '@mantine/hooks';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
@ -32,6 +32,7 @@ export function AuthenticationForm() {
|
||||
const [classicLoginMode, setMode] = useDisclosure(true);
|
||||
const [auth_settings] = useServerApiState((state) => [state.auth_settings]);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
|
||||
|
||||
@ -52,7 +53,7 @@ export function AuthenticationForm() {
|
||||
color: 'green',
|
||||
icon: <IconCheck size="1rem" />
|
||||
});
|
||||
navigate('/home');
|
||||
navigate(location?.state?.redirectFrom ?? '/home');
|
||||
} else {
|
||||
notifications.show({
|
||||
title: t`Login failed`,
|
||||
|
@ -64,6 +64,7 @@ export type ApiFormFieldType = {
|
||||
| 'string'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'integer'
|
||||
| 'decimal'
|
||||
| 'float'
|
||||
@ -215,6 +216,7 @@ export function ApiFormField({
|
||||
/>
|
||||
);
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
return <DateField controller={controller} definition={definition} />;
|
||||
case 'integer':
|
||||
case 'decimal':
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { DateInput } from '@mantine/dates';
|
||||
import dayjs from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import { useCallback, useId, useMemo } from 'react';
|
||||
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
export default function DateField({
|
||||
controller,
|
||||
definition
|
||||
@ -18,13 +22,16 @@ export default function DateField({
|
||||
fieldState: { error }
|
||||
} = controller;
|
||||
|
||||
const valueFormat =
|
||||
definition.field_type == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
const onChange = useCallback(
|
||||
(value: any) => {
|
||||
// Convert the returned date object to a string
|
||||
if (value) {
|
||||
value = value.toString();
|
||||
let date = new Date(value);
|
||||
value = date.toISOString().split('T')[0];
|
||||
value = dayjs(value).format(valueFormat);
|
||||
}
|
||||
|
||||
field.onChange(value);
|
||||
@ -50,7 +57,7 @@ export default function DateField({
|
||||
value={dateValue}
|
||||
clearable={!definition.required}
|
||||
onChange={onChange}
|
||||
valueFormat="YYYY-MM-DD"
|
||||
valueFormat={valueFormat}
|
||||
label={definition.label}
|
||||
description={definition.description}
|
||||
placeholder={definition.placeholder}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Container, Flex, Space } from '@mantine/core';
|
||||
import { Navigate, Outlet } from 'react-router-dom';
|
||||
import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { InvenTreeStyle } from '../../globalStyle';
|
||||
import { useSessionState } from '../../states/SessionState';
|
||||
@ -9,8 +9,12 @@ import { Header } from './Header';
|
||||
export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
|
||||
const [token] = useSessionState((state) => [state.token]);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
if (!token) {
|
||||
return <Navigate to="/logged-in" replace />;
|
||||
return (
|
||||
<Navigate to="/logged-in" state={{ redirectFrom: location.pathname }} />
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
|
@ -88,6 +88,7 @@ export function formatPriceRange(
|
||||
|
||||
interface renderDateOptionsType {
|
||||
showTime?: boolean;
|
||||
showSeconds?: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -106,6 +107,9 @@ export function renderDate(date: string, options: renderDateOptionsType = {}) {
|
||||
|
||||
if (options.showTime) {
|
||||
fmt += ' HH:mm';
|
||||
if (options.showSeconds) {
|
||||
fmt += ':ss';
|
||||
}
|
||||
}
|
||||
|
||||
const m = dayjs(date);
|
||||
|
@ -150,7 +150,9 @@ export function checkLoginState(
|
||||
// Callback function when login fails
|
||||
const loginFailure = () => {
|
||||
useSessionState.getState().clearToken();
|
||||
if (!no_redirect) navigate('/login');
|
||||
if (!no_redirect) {
|
||||
navigate('/login', { state: { redirectFrom: redirect } });
|
||||
}
|
||||
};
|
||||
|
||||
if (useSessionState.getState().hasToken()) {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user