2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-07-10 09:00:57 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue6281

This commit is contained in:
Matthias Mair
2024-04-22 20:14:43 +02:00
218 changed files with 84850 additions and 66643 deletions
-27
View File
@@ -1,27 +0,0 @@
env:
commonjs: false
browser: true
es2021: true
jquery: true
extends:
- eslint:recommended
parserOptions:
ecmaVersion: 12
rules:
no-var: off
guard-for-in: off
no-trailing-spaces: off
camelcase: off
padded-blocks: off
prefer-const: off
max-len: off
require-jsdoc: off
valid-jsdoc: off
no-multiple-empty-lines: off
comma-dangle: off
no-unused-vars: off
no-useless-escape: off
prefer-spread: off
indent:
- error
- 4
+18 -1
View File
@@ -1,11 +1,28 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 186
INVENTREE_API_VERSION = 190
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v190 - 2024-04-19 : https://github.com/inventree/InvenTree/pull/7024
- Adds "active" field to the Company API endpoints
- Allow company list to be filtered by "active" status
v189 - 2024-04-19 : https://github.com/inventree/InvenTree/pull/7066
- Adds "currency" field to CompanyBriefSerializer class
v188 - 2024-04-16 : https://github.com/inventree/InvenTree/pull/6970
- Adds session authentication support for the API
- Improvements for login / logout endpoints for better support of React web interface
v187 - 2024-04-10 : https://github.com/inventree/InvenTree/pull/6985
- Allow Part list endpoint to be sorted by pricing_min and pricing_max values
- Allow BomItem list endpoint to be sorted by pricing_min and pricing_max values
- Allow InternalPrice and SalePrice endpoints to be sorted by quantity
- Adds total pricing values to BomItem serializer
v186 - 2024-03-26 : https://github.com/inventree/InvenTree/pull/6855
- Adds license information to the API
+27 -8
View File
@@ -489,10 +489,18 @@ if DEBUG:
'rest_framework.renderers.BrowsableAPIRenderer'
)
# dj-rest-auth
# JWT switch
USE_JWT = get_boolean_setting('INVENTREE_USE_JWT', 'use_jwt', False)
REST_USE_JWT = USE_JWT
# dj-rest-auth
REST_AUTH = {
'SESSION_LOGIN': True,
'TOKEN_MODEL': 'users.models.ApiToken',
'TOKEN_CREATOR': 'users.models.default_create_token',
'USE_JWT': USE_JWT,
}
OLD_PASSWORD_FIELD_ENABLED = True
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'InvenTree.forms.CustomRegisterSerializer'
@@ -507,6 +515,7 @@ if USE_JWT:
)
INSTALLED_APPS.append('rest_framework_simplejwt')
# WSGI default setting
WSGI_APPLICATION = 'InvenTree.wsgi.application'
@@ -1075,20 +1084,30 @@ CSRF_TRUSTED_ORIGINS = get_setting(
if SITE_URL and SITE_URL not in CSRF_TRUSTED_ORIGINS:
CSRF_TRUSTED_ORIGINS.append(SITE_URL)
if not TESTING and len(CSRF_TRUSTED_ORIGINS) == 0:
if DEBUG:
logger.warning(
'No CSRF_TRUSTED_ORIGINS specified. Defaulting to http://* for debug mode. This is not recommended for production use'
)
CSRF_TRUSTED_ORIGINS = ['http://*']
if DEBUG:
for origin in [
'http://localhost',
'http://*.localhost' 'http://*localhost:8000',
'http://*localhost:5173',
]:
if origin not in CSRF_TRUSTED_ORIGINS:
CSRF_TRUSTED_ORIGINS.append(origin)
elif isInMainThread():
if not TESTING and len(CSRF_TRUSTED_ORIGINS) == 0:
if isInMainThread():
# Server thread cannot run without CSRF_TRUSTED_ORIGINS
logger.error(
'No CSRF_TRUSTED_ORIGINS specified. Please provide a list of trusted origins, or specify INVENTREE_SITE_URL'
)
sys.exit(-1)
# Additional CSRF settings
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
CSRF_COOKIE_NAME = 'csrftoken'
CSRF_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'Lax'
USE_X_FORWARDED_HOST = get_boolean_setting(
'INVENTREE_USE_X_FORWARDED_HOST',
config_key='use_x_forwarded_host',
+1
View File
@@ -160,6 +160,7 @@ apipatterns = [
SocialAccountDisconnectView.as_view(),
name='social_account_disconnect',
),
path('login/', users.api.Login.as_view(), name='api-login'),
path('logout/', users.api.Logout.as_view(), name='api-logout'),
path(
'login-redirect/',
+1 -1
View File
@@ -269,7 +269,7 @@ class FileManagementFormView(MultiStepFormView):
for idx, item in row_data.items():
column_data = {
'name': self.column_names[idx],
'guess': self.column_selections[idx],
'guess': self.column_selections.get(idx, ''),
}
cell_data = {'cell': item, 'idx': idx, 'column': column_data}
+32 -6
View File
@@ -2,6 +2,7 @@
from django.db.models import Q
from django.urls import include, path, re_path
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as rest_filters
@@ -58,11 +59,17 @@ class CompanyList(ListCreateAPI):
filter_backends = SEARCH_ORDER_FILTER
filterset_fields = ['is_customer', 'is_manufacturer', 'is_supplier', 'name']
filterset_fields = [
'is_customer',
'is_manufacturer',
'is_supplier',
'name',
'active',
]
search_fields = ['name', 'description', 'website']
ordering_fields = ['name', 'parts_supplied', 'parts_manufactured']
ordering_fields = ['active', 'name', 'parts_supplied', 'parts_manufactured']
ordering = 'name'
@@ -153,7 +160,13 @@ class ManufacturerPartFilter(rest_filters.FilterSet):
fields = ['manufacturer', 'MPN', 'part', 'tags__name', 'tags__slug']
# Filter by 'active' status of linked part
active = rest_filters.BooleanFilter(field_name='part__active')
part_active = rest_filters.BooleanFilter(
field_name='part__active', label=_('Part is Active')
)
manufacturer_active = rest_filters.BooleanFilter(
field_name='manufacturer__active', label=_('Manufacturer is Active')
)
class ManufacturerPartList(ListCreateDestroyAPIView):
@@ -301,8 +314,16 @@ class SupplierPartFilter(rest_filters.FilterSet):
'tags__slug',
]
active = rest_filters.BooleanFilter(label=_('Supplier Part is Active'))
# Filter by 'active' status of linked part
active = rest_filters.BooleanFilter(field_name='part__active')
part_active = rest_filters.BooleanFilter(
field_name='part__active', label=_('Internal Part is Active')
)
supplier_active = rest_filters.BooleanFilter(
field_name='supplier__active', label=_('Supplier is Active')
)
# Filter by the 'MPN' of linked manufacturer part
MPN = rest_filters.CharFilter(
@@ -378,6 +399,7 @@ class SupplierPartList(ListCreateDestroyAPIView):
'part',
'supplier',
'manufacturer',
'active',
'MPN',
'packaging',
'pack_quantity',
@@ -468,9 +490,13 @@ class SupplierPriceBreakList(ListCreateAPI):
return self.serializer_class(*args, **kwargs)
filter_backends = ORDER_FILTER
filter_backends = SEARCH_ORDER_FILTER_ALIAS
ordering_fields = ['quantity']
ordering_fields = ['quantity', 'supplier', 'SKU', 'price']
search_fields = ['part__SKU', 'part__supplier__name']
ordering_field_aliases = {'supplier': 'part__supplier__name', 'SKU': 'part__SKU'}
ordering = 'quantity'
@@ -0,0 +1,23 @@
# Generated by Django 4.2.11 on 2024-04-15 14:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('company', '0068_auto_20231120_1108'),
]
operations = [
migrations.AddField(
model_name='company',
name='active',
field=models.BooleanField(default=True, help_text='Is this company active?', verbose_name='Active'),
),
migrations.AddField(
model_name='supplierpart',
name='active',
field=models.BooleanField(default=True, help_text='Is this supplier part active?', verbose_name='Active'),
),
]
+12
View File
@@ -81,6 +81,7 @@ class Company(
link: Secondary URL e.g. for link to internal Wiki page
image: Company image / logo
notes: Extra notes about the company
active: boolean value, is this company active
is_customer: boolean value, is this company a customer
is_supplier: boolean value, is this company a supplier
is_manufacturer: boolean value, is this company a manufacturer
@@ -155,6 +156,10 @@ class Company(
verbose_name=_('Image'),
)
active = models.BooleanField(
default=True, verbose_name=_('Active'), help_text=_('Is this company active?')
)
is_customer = models.BooleanField(
default=False,
verbose_name=_('is customer'),
@@ -654,6 +659,7 @@ class SupplierPart(
part: Link to the master Part (Obsolete)
source_item: The sourcing item linked to this SupplierPart instance
supplier: Company that supplies this SupplierPart object
active: Boolean value, is this supplier part active
SKU: Stock keeping unit (supplier part number)
link: Link to external website for this supplier part
description: Descriptive notes field
@@ -802,6 +808,12 @@ class SupplierPart(
help_text=_('Supplier stock keeping unit'),
)
active = models.BooleanField(
default=True,
verbose_name=_('Active'),
help_text=_('Is this supplier part active?'),
)
manufacturer_part = models.ForeignKey(
ManufacturerPart,
on_delete=models.CASCADE,
+15 -5
View File
@@ -42,9 +42,16 @@ class CompanyBriefSerializer(InvenTreeModelSerializer):
"""Metaclass options."""
model = Company
fields = ['pk', 'url', 'name', 'description', 'image', 'thumbnail']
url = serializers.CharField(source='get_absolute_url', read_only=True)
fields = [
'pk',
'active',
'name',
'description',
'image',
'thumbnail',
'currency',
]
read_only_fields = ['currency']
image = InvenTreeImageSerializerField(read_only=True)
@@ -116,6 +123,7 @@ class CompanySerializer(RemoteImageMixin, InvenTreeModelSerializer):
'contact',
'link',
'image',
'active',
'is_customer',
'is_manufacturer',
'is_supplier',
@@ -306,6 +314,7 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
'description',
'in_stock',
'link',
'active',
'manufacturer',
'manufacturer_detail',
'manufacturer_part',
@@ -369,8 +378,9 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
self.fields.pop('pretty_name')
# Annotated field showing total in-stock quantity
in_stock = serializers.FloatField(read_only=True)
available = serializers.FloatField(required=False)
in_stock = serializers.FloatField(read_only=True, label=_('In Stock'))
available = serializers.FloatField(required=False, label=_('Available'))
pack_quantity_native = serializers.FloatField(read_only=True)
@@ -10,6 +10,12 @@
{% block heading %}
{% trans "Company" %}: {{ company.name }}
{% if not company.active %}
 
<div class='badge rounded-pill bg-danger'>
{% trans 'Inactive' %}
</div>
{% endif %}
{% endblock heading %}
{% block actions %}
+71
View File
@@ -5,6 +5,7 @@ from django.urls import reverse
from rest_framework import status
from InvenTree.unit_test import InvenTreeAPITestCase
from part.models import Part
from .models import Address, Company, Contact, ManufacturerPart, SupplierPart
@@ -131,6 +132,32 @@ class CompanyTest(InvenTreeAPITestCase):
self.assertTrue('currency' in response.data)
def test_company_active(self):
"""Test that the 'active' value and filter works."""
Company.objects.filter(active=False).update(active=True)
n = Company.objects.count()
url = reverse('api-company-list')
self.assertEqual(
len(self.get(url, data={'active': True}, expected_code=200).data), n
)
self.assertEqual(
len(self.get(url, data={'active': False}, expected_code=200).data), 0
)
# Set one company to inactive
c = Company.objects.first()
c.active = False
c.save()
self.assertEqual(
len(self.get(url, data={'active': True}, expected_code=200).data), n - 1
)
self.assertEqual(
len(self.get(url, data={'active': False}, expected_code=200).data), 1
)
class ContactTest(InvenTreeAPITestCase):
"""Tests for the Contact models."""
@@ -528,6 +555,50 @@ class SupplierPartTest(InvenTreeAPITestCase):
self.assertEqual(sp.available, 999)
self.assertIsNotNone(sp.availability_updated)
def test_active(self):
"""Test that 'active' status filtering works correctly."""
url = reverse('api-supplier-part-list')
# Create a new company, which is inactive
company = Company.objects.create(
name='Inactive Company', is_supplier=True, active=False
)
part = Part.objects.filter(purchaseable=True).first()
# Create some new supplier part objects, *some* of which are inactive
for idx in range(10):
SupplierPart.objects.create(
part=part,
supplier=company,
SKU=f'CMP-{company.pk}-SKU-{idx}',
active=(idx % 2 == 0),
)
n = SupplierPart.objects.count()
# List *all* supplier parts
self.assertEqual(len(self.get(url, data={}, expected_code=200).data), n)
# List only active supplier parts (all except 5 from the new supplier)
self.assertEqual(
len(self.get(url, data={'active': True}, expected_code=200).data), n - 5
)
# List only from 'active' suppliers (all except this new supplier)
self.assertEqual(
len(self.get(url, data={'supplier_active': True}, expected_code=200).data),
n - 10,
)
# List active parts from inactive suppliers (only 5 from the new supplier)
response = self.get(
url, data={'supplier_active': False, 'active': True}, expected_code=200
)
self.assertEqual(len(response.data), 5)
for result in response.data:
self.assertEqual(result['supplier'], company.pk)
class CompanyMetadataAPITest(InvenTreeAPITestCase):
"""Unit tests for the various metadata endpoints of API."""
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
+77 -27
View File
@@ -154,11 +154,11 @@ class LineItemFilter(rest_filters.FilterSet):
# Filter by order status
order_status = rest_filters.NumberFilter(
label='order_status', field_name='order__status'
label=_('Order Status'), field_name='order__status'
)
has_pricing = rest_filters.BooleanFilter(
label='Has Pricing', method='filter_has_pricing'
label=_('Has Pricing'), method='filter_has_pricing'
)
def filter_has_pricing(self, queryset, name, value):
@@ -425,9 +425,38 @@ class PurchaseOrderLineItemFilter(LineItemFilter):
price_field = 'purchase_price'
model = models.PurchaseOrderLineItem
fields = ['order', 'part']
fields = []
pending = rest_filters.BooleanFilter(label='pending', method='filter_pending')
order = rest_filters.ModelChoiceFilter(
queryset=models.PurchaseOrder.objects.all(),
field_name='order',
label=_('Order'),
)
order_complete = rest_filters.BooleanFilter(
label=_('Order Complete'), method='filter_order_complete'
)
def filter_order_complete(self, queryset, name, value):
"""Filter by whether the order is 'complete' or not."""
if str2bool(value):
return queryset.filter(order__status=PurchaseOrderStatus.COMPLETE.value)
return queryset.exclude(order__status=PurchaseOrderStatus.COMPLETE.value)
part = rest_filters.ModelChoiceFilter(
queryset=SupplierPart.objects.all(), field_name='part', label=_('Supplier Part')
)
base_part = rest_filters.ModelChoiceFilter(
queryset=Part.objects.filter(purchaseable=True),
field_name='part__part',
label=_('Internal Part'),
)
pending = rest_filters.BooleanFilter(
method='filter_pending', label=_('Order Pending')
)
def filter_pending(self, queryset, name, value):
"""Filter by "pending" status (order status = pending)."""
@@ -435,7 +464,9 @@ class PurchaseOrderLineItemFilter(LineItemFilter):
return queryset.filter(order__status__in=PurchaseOrderStatusGroups.OPEN)
return queryset.exclude(order__status__in=PurchaseOrderStatusGroups.OPEN)
received = rest_filters.BooleanFilter(label='received', method='filter_received')
received = rest_filters.BooleanFilter(
label=_('Items Received'), method='filter_received'
)
def filter_received(self, queryset, name, value):
"""Filter by lines which are "received" (or "not" received).
@@ -542,25 +573,6 @@ class PurchaseOrderLineItemList(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
)
def filter_queryset(self, queryset):
"""Additional filtering options."""
params = self.request.query_params
queryset = super().filter_queryset(queryset)
base_part = params.get('base_part', None)
if base_part:
try:
base_part = Part.objects.get(pk=base_part)
queryset = queryset.filter(part__part=base_part)
except (ValueError, Part.DoesNotExist):
pass
return queryset
def download_queryset(self, queryset, export_format):
"""Download the requested queryset as a file."""
dataset = PurchaseOrderLineItemResource().export(queryset=queryset)
@@ -577,6 +589,8 @@ class PurchaseOrderLineItemList(
'MPN': 'part__manufacturer_part__MPN',
'SKU': 'part__SKU',
'part_name': 'part__part__name',
'order': 'order__reference',
'complete_date': 'order__complete_date',
}
ordering_fields = [
@@ -589,6 +603,8 @@ class PurchaseOrderLineItemList(
'SKU',
'total_price',
'target_date',
'order',
'complete_date',
]
search_fields = [
@@ -791,7 +807,15 @@ class SalesOrderLineItemFilter(LineItemFilter):
price_field = 'sale_price'
model = models.SalesOrderLineItem
fields = ['order', 'part']
fields = []
order = rest_filters.ModelChoiceFilter(
queryset=models.SalesOrder.objects.all(), field_name='order', label=_('Order')
)
part = rest_filters.ModelChoiceFilter(
queryset=Part.objects.all(), field_name='part', label=_('Part')
)
completed = rest_filters.BooleanFilter(label='completed', method='filter_completed')
@@ -806,6 +830,17 @@ class SalesOrderLineItemFilter(LineItemFilter):
return queryset.filter(q)
return queryset.exclude(q)
order_complete = rest_filters.BooleanFilter(
label=_('Order Complete'), method='filter_order_complete'
)
def filter_order_complete(self, queryset, name, value):
"""Filter by whether the order is 'complete' or not."""
if str2bool(value):
return queryset.filter(order__status__in=SalesOrderStatusGroups.COMPLETE)
return queryset.exclude(order__status__in=SalesOrderStatusGroups.COMPLETE)
class SalesOrderLineItemMixin:
"""Mixin class for SalesOrderLineItem endpoints."""
@@ -862,9 +897,24 @@ class SalesOrderLineItemList(SalesOrderLineItemMixin, APIDownloadMixin, ListCrea
return DownloadFile(filedata, filename)
filter_backends = SEARCH_ORDER_FILTER
filter_backends = SEARCH_ORDER_FILTER_ALIAS
ordering_fields = ['part__name', 'quantity', 'reference', 'target_date']
ordering_fields = [
'customer',
'order',
'part',
'part__name',
'quantity',
'reference',
'sale_price',
'target_date',
]
ordering_field_aliases = {
'customer': 'order__customer__name',
'part': 'part__name',
'order': 'order__reference',
}
search_fields = ['part__name', 'quantity', 'reference']
+4 -2
View File
@@ -1459,9 +1459,11 @@ class PurchaseOrderLineItem(OrderLineItem):
def update_pricing(self):
"""Update pricing information based on the supplier part data."""
if self.part:
price = self.part.get_price(self.quantity)
price = self.part.get_price(
self.quantity, currency=self.purchase_price_currency
)
if price is None:
if price is None or self.quantity == 0:
return
self.purchase_price = Decimal(price) / Decimal(self.quantity)
+26 -6
View File
@@ -38,7 +38,6 @@ from InvenTree.helpers import (
is_ajax,
isNull,
str2bool,
str2int,
)
from InvenTree.mixins import (
CreateAPI,
@@ -386,9 +385,10 @@ class PartSalePriceList(ListCreateAPI):
queryset = PartSellPriceBreak.objects.all()
serializer_class = part_serializers.PartSalePriceSerializer
filter_backends = [DjangoFilterBackend]
filter_backends = SEARCH_ORDER_FILTER
filterset_fields = ['part']
ordering_fields = ['quantity', 'price']
ordering = 'quantity'
class PartInternalPriceDetail(RetrieveUpdateDestroyAPI):
@@ -405,9 +405,10 @@ class PartInternalPriceList(ListCreateAPI):
serializer_class = part_serializers.PartInternalPriceSerializer
permission_required = 'roles.sales_order.show'
filter_backends = [DjangoFilterBackend]
filter_backends = SEARCH_ORDER_FILTER
filterset_fields = ['part']
ordering_fields = ['quantity', 'price']
ordering = 'quantity'
class PartAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
@@ -1407,8 +1408,17 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
'category',
'last_stocktake',
'units',
'pricing_min',
'pricing_max',
'pricing_updated',
]
ordering_field_aliases = {
'pricing_min': 'pricing_data__overall_min',
'pricing_max': 'pricing_data__overall_max',
'pricing_updated': 'pricing_data__updated',
}
# Default ordering
ordering = 'name'
@@ -1939,9 +1949,19 @@ class BomList(BomMixin, ListCreateDestroyAPIView):
'inherited',
'optional',
'consumable',
'pricing_min',
'pricing_max',
'pricing_min_total',
'pricing_max_total',
'pricing_updated',
]
ordering_field_aliases = {'sub_part': 'sub_part__name'}
ordering_field_aliases = {
'sub_part': 'sub_part__name',
'pricing_min': 'sub_part__pricing_data__overall_min',
'pricing_max': 'sub_part__pricing_data__overall_max',
'pricing_updated': 'sub_part__pricing_data__updated',
}
class BomDetail(BomMixin, RetrieveUpdateDestroyAPI):
+38 -2
View File
@@ -616,6 +616,7 @@ class PartSerializer(
'virtual',
'pricing_min',
'pricing_max',
'pricing_updated',
'responsible',
# Annotated fields
'allocated_to_build_orders',
@@ -678,6 +679,7 @@ class PartSerializer(
if not pricing:
self.fields.pop('pricing_min')
self.fields.pop('pricing_max')
self.fields.pop('pricing_updated')
def get_api_url(self):
"""Return the API url associated with this serializer."""
@@ -843,6 +845,9 @@ class PartSerializer(
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(
source='pricing_data.overall_max', allow_null=True, read_only=True
)
pricing_updated = serializers.DateTimeField(
source='pricing_data.updated', allow_null=True, read_only=True
)
parameters = PartParameterSerializer(many=True, read_only=True)
@@ -1413,6 +1418,9 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
'part_detail',
'pricing_min',
'pricing_max',
'pricing_min_total',
'pricing_max_total',
'pricing_updated',
'quantity',
'reference',
'sub_part',
@@ -1451,6 +1459,9 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
if not pricing:
self.fields.pop('pricing_min')
self.fields.pop('pricing_max')
self.fields.pop('pricing_min_total')
self.fields.pop('pricing_max_total')
self.fields.pop('pricing_updated')
quantity = InvenTree.serializers.InvenTreeDecimalField(required=True)
@@ -1481,10 +1492,22 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
# Cached pricing fields
pricing_min = InvenTree.serializers.InvenTreeMoneySerializer(
source='sub_part.pricing.overall_min', allow_null=True, read_only=True
source='sub_part.pricing_data.overall_min', allow_null=True, read_only=True
)
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(
source='sub_part.pricing.overall_max', allow_null=True, read_only=True
source='sub_part.pricing_data.overall_max', allow_null=True, read_only=True
)
pricing_min_total = InvenTree.serializers.InvenTreeMoneySerializer(
allow_null=True, read_only=True
)
pricing_max_total = InvenTree.serializers.InvenTreeMoneySerializer(
allow_null=True, read_only=True
)
pricing_updated = serializers.DateTimeField(
source='sub_part.pricing_data.updated', allow_null=True, read_only=True
)
# Annotated fields for available stock
@@ -1504,6 +1527,7 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
queryset = queryset.prefetch_related('sub_part')
queryset = queryset.prefetch_related('sub_part__category')
queryset = queryset.prefetch_related('sub_part__pricing_data')
queryset = queryset.prefetch_related(
'sub_part__stock_items',
@@ -1531,6 +1555,18 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
available_stock = total_stock - build_order_allocations - sales_order_allocations
"""
# Annotate with the 'total pricing' information based on unit pricing and quantity
queryset = queryset.annotate(
pricing_min_total=ExpressionWrapper(
F('quantity') * F('sub_part__pricing_data__overall_min'),
output_field=models.DecimalField(),
),
pricing_max_total=ExpressionWrapper(
F('quantity') * F('sub_part__pricing_data__overall_max'),
output_field=models.DecimalField(),
),
)
ref = 'sub_part__'
# Annotate with the total "on order" amount for the sub-part
@@ -21,12 +21,15 @@ function activatePanel(label, panel_name, options={}) {
$('.panel-visible').hide();
$('.panel-visible').removeClass('panel-visible');
// Remove illegal chars
panel_name = panel_name.replace('/', '');
// Find the target panel
var panel = `#panel-${panel_name}`;
var select = `#select-${panel_name}`;
// Check that the selected panel (and select) exist
if ($(panel).length && $(select).length) {
if ($(panel).exists() && $(panel).length && $(select).length) {
// Yep, both are displayed
} else {
// Either the select or the panel are not displayed!
@@ -426,7 +426,8 @@ function companyFormFields() {
},
is_supplier: {},
is_manufacturer: {},
is_customer: {}
is_customer: {},
active: {},
};
}
@@ -517,6 +518,15 @@ function loadCompanyTable(table, url, options={}) {
field: 'description',
title: '{% trans "Description" %}',
},
{
field: 'active',
title: '{% trans "Active" %}',
sortable: true,
switchable: true,
formatter: function(value) {
return yesNoLabel(value);
}
},
{
field: 'website',
title: '{% trans "Website" %}',
@@ -218,7 +218,7 @@ function renderStockItem(data, parameters={}) {
}
if (data.quantity == 0) {
stock_detail = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock"% }</span>`;
stock_detail = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
} else {
if (data.serial && data.quantity == 1) {
stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
@@ -1755,7 +1755,7 @@ function loadPurchaseOrderTable(table, options) {
sortable: true,
formatter: function(value, row) {
return formatCurrency(value, {
currency: row.order_currency,
currency: row.order_currency ?? row.supplier_detail?.currency,
});
},
},
@@ -300,7 +300,7 @@ function loadReturnOrderTable(table, options={}) {
return '{% trans "Invalid Customer" %}';
}
return imageHoverIcon(row.customer_detail.image) + renderLink(row.customer_detail.name, `/company/${row.customer}/sales-orders/`);
return imageHoverIcon(row.customer_detail.image) + renderLink(row.customer_detail.name, `/company/${row.customer}/?display=sales-orders/`);
}
},
{
@@ -384,7 +384,7 @@ function loadReturnOrderTable(table, options={}) {
visible: false,
formatter: function(value, row) {
return formatCurrency(value, {
currency: row.order_currency
currency: row.order_currency ?? row.customer_detail?.currency,
});
}
}
@@ -488,7 +488,7 @@ function receiveReturnOrderItems(order_id, line_items, options={}) {
if (line_items.length == 0) {
showAlertDialog(
'{% trans "Select Line Items"% }',
'{% trans "Select Line Items" %}',
'{% trans "At least one line item must be selected" %}'
);
return;
@@ -788,7 +788,7 @@ function loadSalesOrderTable(table, options) {
return '{% trans "Invalid Customer" %}';
}
return imageHoverIcon(row.customer_detail.image) + renderLink(row.customer_detail.name, `/company/${row.customer}/sales-orders/`);
return imageHoverIcon(row.customer_detail.image) + renderLink(row.customer_detail.name, `/company/${row.customer}/?display=sales-orders/`);
}
},
{
@@ -857,7 +857,7 @@ function loadSalesOrderTable(table, options) {
sortable: true,
formatter: function(value, row) {
return formatCurrency(value, {
currency: row.order_currency,
currency: row.order_currency ?? row.customer_detail?.currency,
});
}
}
@@ -791,6 +791,10 @@ function getContactFilters() {
// Return a dictionary of filters for the "company" table
function getCompanyFilters() {
return {
active: {
type: 'bool',
title: '{% trans "Active" %}'
},
is_manufacturer: {
type: 'bool',
title: '{% trans "Manufacturer" %}',
+15 -1
View File
@@ -8,9 +8,11 @@ from django.contrib.auth.models import Group, User
from django.urls import include, path, re_path
from django.views.generic.base import RedirectView
from dj_rest_auth.views import LogoutView
from dj_rest_auth.views import LoginView, LogoutView
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view
from rest_framework import exceptions, permissions
from rest_framework.authentication import BasicAuthentication
from rest_framework.decorators import authentication_classes
from rest_framework.response import Response
from rest_framework.views import APIView
@@ -205,6 +207,18 @@ class GroupList(ListCreateAPI):
ordering_fields = ['name']
@authentication_classes([BasicAuthentication])
@extend_schema_view(
post=extend_schema(
responses={200: OpenApiResponse(description='User successfully logged in')}
)
)
class Login(LoginView):
"""API view for logging in via API."""
...
@extend_schema_view(
post=extend_schema(
responses={200: OpenApiResponse(description='User successfully logged out')}
+11
View File
@@ -56,6 +56,17 @@ def default_token_expiry():
return InvenTree.helpers.current_date() + datetime.timedelta(days=365)
def default_create_token(token_model, user, serializer):
"""Generate a default value for the token."""
token = token_model.objects.filter(user=user, name='', revoked=False)
if token.exists():
return token.first()
else:
return token_model.objects.create(user=user, name='')
class ApiToken(AuthToken, InvenTree.models.MetadataMixin):
"""Extends the default token model provided by djangorestframework.authtoken.
+38
View File
@@ -0,0 +1,38 @@
// eslint.config.js
import js from "@eslint/js";
import globals from "globals";
export default [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 12,
sourceType: "module",
globals: {
...globals.browser,
...globals.es2021,
...globals.jquery,
}
},
rules: {
"no-var": "off",
"guard-for-in": "off",
"no-trailing-spaces": "off",
"camelcase": "off",
"padded-blocks": "off",
"prefer-const": "off",
"max-len": "off",
"require-jsdoc": "off",
"valid-jsdoc": "off",
"no-multiple-empty-lines": "off",
"comma-dangle": "off",
"no-unused-vars": "off",
"no-useless-escape": "off",
"prefer-spread": "off",
"indent": ["error", 4]
},
linterOptions: {
reportUnusedDisableDirectives: "off"
}
}
];
+65 -162
View File
@@ -5,7 +5,7 @@
"packages": {
"": {
"dependencies": {
"eslint": "^8.57.0",
"eslint": "^9.0.0",
"eslint-config-google": "^0.14.0"
}
},
@@ -31,6 +31,17 @@
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
@@ -40,14 +51,14 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz",
"integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.6.0",
"globals": "^13.19.0",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
@@ -55,26 +66,26 @@
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/js": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.0.0.tgz",
"integrity": "sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.12.3.tgz",
"integrity": "sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==",
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.2",
"@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
@@ -131,11 +142,6 @@
"node": ">= 8"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@@ -289,17 +295,6 @@
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dependencies": {
"esutils": "^2.0.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -312,40 +307,36 @@
}
},
"node_modules/eslint": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.0.0.tgz",
"integrity": "sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@eslint/eslintrc": "^3.0.2",
"@eslint/js": "9.0.0",
"@humanwhocodes/config-array": "^0.12.3",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"eslint-scope": "^8.0.1",
"eslint-visitor-keys": "^4.0.0",
"espree": "^10.0.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"file-entry-cache": "^8.0.0",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
@@ -359,7 +350,7 @@
"eslint": "bin/eslint.js"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -377,42 +368,42 @@
}
},
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz",
"integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==",
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz",
"integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==",
"dependencies": {
"acorn": "^8.9.0",
"acorn": "^8.11.3",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
"eslint-visitor-keys": "^4.0.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -480,14 +471,14 @@
}
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dependencies": {
"flat-cache": "^3.0.4"
"flat-cache": "^4.0.0"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
"node": ">=16.0.0"
}
},
"node_modules/find-up": {
@@ -506,16 +497,15 @@
}
},
"node_modules/flat-cache": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.3",
"rimraf": "^3.0.2"
"keyv": "^4.5.4"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
"node": ">=16"
}
},
"node_modules/flatted": {
@@ -523,30 +513,6 @@
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -559,14 +525,11 @@
}
},
"node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dependencies": {
"type-fest": "^0.20.2"
},
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"engines": {
"node": ">=8"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -616,20 +579,6 @@
"node": ">=0.8.19"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -748,14 +697,6 @@
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -819,14 +760,6 @@
"node": ">=8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -887,20 +820,6 @@
"node": ">=0.10.0"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -991,17 +910,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -1024,11 +932,6 @@
"node": ">= 8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+3 -2
View File
@@ -1,6 +1,7 @@
{
"dependencies": {
"eslint": "^8.57.0",
"eslint": "^9.0.0",
"eslint-config-google": "^0.14.0"
}
},
"type": "module"
}
+1 -1
View File
@@ -63,7 +63,7 @@ pyyaml==6.0.1
# via pre-commit
requests==2.31.0
# via coveralls
setuptools==69.2.0
setuptools==69.5.1
# via
# nodeenv
# pip-tools
+3 -3
View File
@@ -130,7 +130,7 @@ googleapis-common-protos==1.63.0
# opentelemetry-exporter-otlp-proto-http
grpcio==1.62.1
# via opentelemetry-exporter-otlp-proto-grpc
gunicorn==21.2.0
gunicorn==22.0.0
html5lib==1.1
# via weasyprint
icalendar==5.0.12
@@ -288,9 +288,9 @@ rpds-py==0.18.0
# via
# jsonschema
# referencing
sentry-sdk==1.44.1
sentry-sdk==1.45.0
# via django-q-sentry
setuptools==69.2.0
setuptools==69.5.1
# via
# django-money
# opentelemetry-instrumentation