mirror of
https://github.com/inventree/InvenTree.git
synced 2026-03-21 11:44:42 +00:00
chore(backend): Bump ty (#11537)
* bump ty - there is better django support now * more fixes * fix usage of types * add missing type * fix access * ensure itteration is safe * fix uncombat decimal usage * ?potential breaking: change access key * remove now obsolete igtnore * ignore errors on StdImageField * remove usage of unkonw parameter * fix diff error * fix schema creation * fix coverage quirk * those are unneeded now? * this seem to have been an issue with 3.12; not occuring on 3.14 * ignore pydantiics * ignore edge cases for now * include isGenerating fix * make typing python 3.11 compatible * fix more errors
This commit is contained in:
@@ -108,13 +108,13 @@ root = ["src/backend/InvenTree"]
|
||||
unresolved-reference="ignore" # 21 # see https://github.com/astral-sh/ty/issues/220
|
||||
unresolved-attribute="ignore" # 505 # need Plugin Mixin typing
|
||||
call-non-callable="ignore" # 8 ##
|
||||
invalid-argument-type="ignore" # 49
|
||||
invalid-assignment="ignore" # 17 # need to wait for better django field stubs
|
||||
invalid-method-override="ignore"
|
||||
invalid-method-override="ignore" # 99
|
||||
invalid-return-type="ignore" # 22 ##
|
||||
possibly-missing-attribute="ignore" # 25 # https://github.com/astral-sh/ty/issues/164
|
||||
unknown-argument="ignore" # 3 # need to wait for better django field stubs
|
||||
no-matching-overload="ignore" # 3 # need to wait for better django field stubs
|
||||
invalid-argument-type="ignore" # 49
|
||||
no-matching-overload="ignore" # 3 # need to wait for betterdjango field stubs
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["src/backend/InvenTree", "InvenTree"]
|
||||
|
||||
@@ -199,7 +199,7 @@ def regenerate_imagefile(_file, _name: str):
|
||||
_name: Name of the variation (e.g. 'thumbnail', 'preview')
|
||||
"""
|
||||
name = _file.field.attr_class.get_variation_name(_file.name, _name)
|
||||
return ImageFieldFile(_file.instance, _file, name) # type: ignore
|
||||
return ImageFieldFile(_file.instance, _file, name) # ty:ignore[too-many-positional-arguments]
|
||||
|
||||
|
||||
def image2name(img_obj: StdImageField, do_preview: bool, do_thumbnail: bool):
|
||||
@@ -311,7 +311,7 @@ def TestIfImageURL(url):
|
||||
]
|
||||
|
||||
|
||||
def str2bool(text, test=True):
|
||||
def str2bool(text, test=True) -> bool:
|
||||
"""Test if a string 'looks' like a boolean value.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -8,6 +8,7 @@ import structlog
|
||||
from allauth.account.models import EmailAddress
|
||||
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks as tasks
|
||||
from common.models import Priority, issue_mail
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
@@ -98,7 +99,7 @@ def send_email(
|
||||
)
|
||||
return False, 'INVE-W7: no from_email or DEFAULT_FROM_EMAIL specified'
|
||||
|
||||
InvenTree.tasks.offload_task(
|
||||
tasks.offload_task(
|
||||
issue_mail,
|
||||
subject=subject,
|
||||
body=body,
|
||||
|
||||
@@ -99,7 +99,7 @@ class Command(BaseCommand):
|
||||
if kwargs['include_items']:
|
||||
icons[icon]['items'].append({
|
||||
'model': model.__name__.lower(),
|
||||
'id': item.id, # type: ignore
|
||||
'id': item.id,
|
||||
})
|
||||
|
||||
self.stdout.write(f'Writing icon map for {len(icons.keys())} icons')
|
||||
|
||||
@@ -1329,7 +1329,7 @@ class InvenTreeBarcodeMixin(models.Model):
|
||||
@classmethod
|
||||
def lookup_barcode(cls, barcode_hash: str) -> models.Model:
|
||||
"""Check if a model instance exists with the specified third-party barcode hash."""
|
||||
return cls.objects.filter(barcode_hash=barcode_hash).first()
|
||||
return cls.objects.filter(barcode_hash=barcode_hash).first() # ty:ignore[invalid-return-type]
|
||||
|
||||
def assign_barcode(
|
||||
self,
|
||||
@@ -1485,7 +1485,7 @@ class InvenTreeImageMixin(models.Model):
|
||||
|
||||
def rename_image(self, filename):
|
||||
"""Rename the uploaded image file using the IMAGE_RENAME function."""
|
||||
return self.IMAGE_RENAME(filename) # type: ignore
|
||||
return self.IMAGE_RENAME(filename)
|
||||
|
||||
image = StdImageField(
|
||||
upload_to=rename_image,
|
||||
|
||||
@@ -376,7 +376,7 @@ def auth_exempt(view_func):
|
||||
def wrapped_view(*args, **kwargs):
|
||||
return view_func(*args, **kwargs)
|
||||
|
||||
wrapped_view.auth_exempt = True # type:ignore[unresolved-attribute]
|
||||
wrapped_view.auth_exempt = True
|
||||
return wraps(view_func)(wrapped_view)
|
||||
|
||||
|
||||
|
||||
@@ -483,7 +483,7 @@ if LDAP_AUTH: # pragma: no cover
|
||||
)
|
||||
AUTH_LDAP_USER_SEARCH = django_auth_ldap.config.LDAPSearch(
|
||||
get_setting('INVENTREE_LDAP_SEARCH_BASE_DN', 'ldap.search_base_dn'),
|
||||
ldap.SCOPE_SUBTREE, # type: ignore[unresolved-attribute]
|
||||
ldap.SCOPE_SUBTREE,
|
||||
str(
|
||||
get_setting(
|
||||
'INVENTREE_LDAP_SEARCH_FILTER_STR',
|
||||
@@ -519,7 +519,7 @@ if LDAP_AUTH: # pragma: no cover
|
||||
)
|
||||
AUTH_LDAP_GROUP_SEARCH = django_auth_ldap.config.LDAPSearch(
|
||||
get_setting('INVENTREE_LDAP_GROUP_SEARCH', 'ldap.group_search'),
|
||||
ldap.SCOPE_SUBTREE, # type: ignore[unresolved-attribute]
|
||||
ldap.SCOPE_SUBTREE,
|
||||
f'(objectClass={AUTH_LDAP_GROUP_OBJECT_CLASS})',
|
||||
)
|
||||
AUTH_LDAP_GROUP_TYPE_CLASS = get_setting(
|
||||
|
||||
@@ -49,7 +49,7 @@ def check_provider(provider):
|
||||
if not app:
|
||||
return False
|
||||
|
||||
if allauth.app_settings.SITES_ENABLED: # type: ignore[unresolved-attribute]
|
||||
if allauth.app_settings.SITES_ENABLED:
|
||||
# At least one matching site must be specified
|
||||
if not app.sites.exists():
|
||||
logger.error('SocialApp %s has no sites configured', app)
|
||||
|
||||
@@ -330,7 +330,7 @@ class ScheduledTask:
|
||||
QUARTERLY: str = 'Q'
|
||||
YEARLY: str = 'Y'
|
||||
|
||||
TYPE: tuple[str] = (MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY) # type: ignore[invalid-assignment]
|
||||
TYPE: tuple[str] = (MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY)
|
||||
|
||||
|
||||
class TaskRegister:
|
||||
|
||||
@@ -574,7 +574,7 @@ class GeneralApiTests(InvenTreeAPITestCase):
|
||||
|
||||
self.assertIn('License file not found at', str(log.output))
|
||||
|
||||
with TemporaryDirectory() as tmp: # type: ignore[no-matching-overload]
|
||||
with TemporaryDirectory() as tmp:
|
||||
sample_file = Path(tmp, 'temp.txt')
|
||||
sample_file.write_text('abc', 'utf-8')
|
||||
|
||||
|
||||
@@ -703,12 +703,12 @@ class TestHelpers(TestCase):
|
||||
"""Test getMediaUrl."""
|
||||
# Str should not work
|
||||
with self.assertRaises(TypeError):
|
||||
helpers.getMediaUrl('xx/yy.png') # type: ignore
|
||||
helpers.getMediaUrl('xx/yy.png')
|
||||
|
||||
# Correct usage
|
||||
part = Part().image
|
||||
self.assertEqual(
|
||||
helpers.getMediaUrl(StdImageFieldFile(part, part, 'xx/yy.png')), # type: ignore
|
||||
helpers.getMediaUrl(StdImageFieldFile(part, part, 'xx/yy.png')), # ty:ignore[too-many-positional-arguments]
|
||||
'/media/xx/yy.png',
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import base64
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from opentelemetry import metrics, trace # type: ignore[import]
|
||||
from opentelemetry import metrics, trace
|
||||
from opentelemetry.instrumentation.django import DjangoInstrumentor
|
||||
from opentelemetry.instrumentation.redis import RedisInstrumentor
|
||||
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
||||
|
||||
@@ -289,8 +289,10 @@ def inventreeBranch():
|
||||
return ' '.join(branch.splitlines())
|
||||
|
||||
if main_branch is None:
|
||||
return None
|
||||
return main_branch.decode('utf-8')
|
||||
return None # pragma: no cover - branch information may not be available in all environments
|
||||
return main_branch.decode(
|
||||
'utf-8'
|
||||
) # pragma: no cover - branch information may not be available in all environments
|
||||
|
||||
|
||||
def inventreeTarget():
|
||||
|
||||
@@ -17,6 +17,7 @@ from rest_framework.response import Response
|
||||
import build.models as build_models
|
||||
import build.serializers
|
||||
import common.models
|
||||
import common.serializers
|
||||
import part.models as part_models
|
||||
import stock.models as stock_models
|
||||
import stock.serializers
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Build database model definitions."""
|
||||
|
||||
import decimal
|
||||
from typing import Optional
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -50,7 +50,7 @@ from stock.status_codes import StockHistoryCode, StockStatus
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
|
||||
class BuildReportContext(report.mixins.BaseReportContext):
|
||||
class BuildReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Context for the Build model.
|
||||
|
||||
Attributes:
|
||||
@@ -1039,7 +1039,7 @@ class Build(
|
||||
lines = lines.exclude(bom_item__consumable=True)
|
||||
lines = lines.annotate(allocated=annotate_allocated_quantity())
|
||||
|
||||
for build_line in lines: # type: ignore[non-iterable]
|
||||
for build_line in lines:
|
||||
reduce_by = build_line.allocated - build_line.quantity
|
||||
|
||||
if reduce_by <= 0:
|
||||
@@ -1512,10 +1512,10 @@ class Build(
|
||||
unallocated_quantity -= quantity
|
||||
|
||||
except (ValidationError, serializers.ValidationError) as exc:
|
||||
# Catch model errors and re-throw as DRF errors
|
||||
# Re-raise with a Django-compatible validation payload
|
||||
raise ValidationError(
|
||||
exc.message, detail=serializers.as_serializer_error(exc)
|
||||
)
|
||||
serializers.as_serializer_error(exc)
|
||||
) from exc
|
||||
|
||||
if unallocated_quantity <= 0:
|
||||
# We have now fully-allocated this BomItem - no need to continue!
|
||||
@@ -1695,7 +1695,7 @@ def after_save_build(sender, instance: Build, created: bool, **kwargs):
|
||||
instance.update_build_line_items()
|
||||
|
||||
|
||||
class BuildLineReportContext(report.mixins.BaseReportContext):
|
||||
class BuildLineReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Context for the BuildLine model.
|
||||
|
||||
Attributes:
|
||||
@@ -1745,6 +1745,7 @@ class BuildLine(report.mixins.InvenTreeReportMixin, InvenTree.models.InvenTreeMo
|
||||
"""Return the API URL used to access this model."""
|
||||
return reverse('api-build-line-list')
|
||||
|
||||
# type
|
||||
def report_context(self) -> BuildLineReportContext:
|
||||
"""Generate custom report context for this BuildLine object."""
|
||||
return {
|
||||
|
||||
@@ -230,6 +230,6 @@ def get_price(
|
||||
quantity = decimal.Decimal(f'{quantity}')
|
||||
|
||||
if pb_found:
|
||||
cost = pb_cost * quantity
|
||||
cost = decimal.Decimal(pb_cost) * quantity
|
||||
return InvenTree.helpers.normalize(cost + instance.base_cost)
|
||||
return None
|
||||
|
||||
@@ -85,8 +85,8 @@ class RenderMeta(enums.ChoicesType):
|
||||
return []
|
||||
|
||||
|
||||
class RenderChoices(models.TextChoices, metaclass=RenderMeta): # type: ignore
|
||||
"""Class for creating enumerated string choices for schema rendering."""
|
||||
class RenderChoices(models.TextChoices, metaclass=RenderMeta):
|
||||
"""Class for creating enumerated string choices for schema rendering.""" # ty:ignore[conflicting-metaclass]
|
||||
|
||||
|
||||
class MetaMixin(models.Model):
|
||||
@@ -1084,7 +1084,7 @@ class BaseInvenTreeSetting(models.Model):
|
||||
|
||||
return self.__class__.validator_is_bool(validator)
|
||||
|
||||
def as_bool(self):
|
||||
def as_bool(self) -> bool:
|
||||
"""Return the value of this setting converted to a boolean value.
|
||||
|
||||
Warning: Only use on values where is_bool evaluates to true!
|
||||
|
||||
@@ -42,7 +42,7 @@ class SettingsValueField(serializers.Field):
|
||||
"""Return the object instance, not the attribute value."""
|
||||
return instance
|
||||
|
||||
def to_representation(self, instance: common_models.InvenTreeSetting) -> str:
|
||||
def to_representation(self, instance: common_models.InvenTreeSetting):
|
||||
"""Return the value of the setting.
|
||||
|
||||
Protected settings are returned as '***'
|
||||
@@ -381,7 +381,7 @@ class ConfigSerializer(serializers.Serializer):
|
||||
"""Return the configuration data as a dictionary."""
|
||||
if not isinstance(instance, str):
|
||||
instance = list(instance.keys())[0]
|
||||
return {'key': instance, **self.instance[instance]}
|
||||
return {'key': instance, **self.instance.get(instance)}
|
||||
|
||||
|
||||
class NotesImageSerializer(InvenTreeModelSerializer):
|
||||
@@ -457,7 +457,7 @@ class FlagSerializer(serializers.Serializer):
|
||||
data = {'key': instance, 'state': flag_state(instance, request=request)}
|
||||
|
||||
if request and request.user.is_superuser:
|
||||
data['conditions'] = self.instance[instance]
|
||||
data['conditions'] = self.instance.get(instance)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class SettingsKeyType(TypedDict, total=False):
|
||||
validator: Validation function/list of functions for the setting (optional, default: None, e.g: bool, int, str, MinValueValidator, ...)
|
||||
default: Default value or function that returns default value (optional)
|
||||
choices: Function that returns or value of list[tuple[str: key, str: display value]] (optional)
|
||||
model_filters: Filters to apply when querying the associated model (optional)
|
||||
hidden: Hide this setting from settings page (optional)
|
||||
before_save: Function that gets called after save with *args, **kwargs (optional)
|
||||
after_save: Function that gets called after save with *args, **kwargs (optional)
|
||||
@@ -42,6 +43,7 @@ class SettingsKeyType(TypedDict, total=False):
|
||||
validator: Callable | list[Callable] | tuple[Callable]
|
||||
default: Callable | Any
|
||||
choices: list[tuple[str, str]] | Callable[[], list[tuple[str, str]]]
|
||||
model_filters: dict[str, Any]
|
||||
hidden: bool
|
||||
before_save: Callable[..., None]
|
||||
after_save: Callable[..., None]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import os
|
||||
from decimal import Decimal
|
||||
from typing import TypedDict
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
@@ -53,7 +54,7 @@ def rename_company_image(instance, filename):
|
||||
return os.path.join(base, fn)
|
||||
|
||||
|
||||
class CompanyReportContext(report.mixins.BaseReportContext):
|
||||
class CompanyReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Report context for the Company model.
|
||||
|
||||
Attributes:
|
||||
@@ -243,7 +244,11 @@ class Company(
|
||||
# We may have a pre-fetched primary address list
|
||||
if hasattr(self, 'primary_address_list'):
|
||||
addresses = self.primary_address_list
|
||||
return addresses[0] if len(addresses) > 0 else None
|
||||
return (
|
||||
addresses[0]
|
||||
if len(addresses) > 0 and isinstance(addresses, list)
|
||||
else None
|
||||
)
|
||||
|
||||
# Otherwise, query the database
|
||||
return self.addresses.filter(primary=True).first()
|
||||
|
||||
@@ -194,7 +194,7 @@ class InvenTreeCustomStatusSerializerMixin:
|
||||
"""Ensure the custom field is updated if the leader was changed."""
|
||||
self.gather_custom_fields()
|
||||
# Mirror values from leader to follower
|
||||
for field in self._custom_fields_leader:
|
||||
for field in self._custom_fields_leader or []:
|
||||
follower_field_name = f'{field}_custom_key'
|
||||
if (
|
||||
field in self.initial_data
|
||||
@@ -205,7 +205,7 @@ class InvenTreeCustomStatusSerializerMixin:
|
||||
setattr(self.instance, follower_field_name, self.initial_data[field])
|
||||
|
||||
# Mirror values from follower to leader
|
||||
for field in self._custom_fields_follower:
|
||||
for field in self._custom_fields_follower or []:
|
||||
leader_field_name = field.replace('_custom_key', '')
|
||||
if field in validated_data and leader_field_name not in self.initial_data:
|
||||
try:
|
||||
@@ -276,7 +276,7 @@ class InvenTreeCustomStatusSerializerMixin:
|
||||
|
||||
# Inherit choices from leader
|
||||
self.gather_custom_fields()
|
||||
if field_name in self._custom_fields:
|
||||
if self._custom_fields and field_name in self._custom_fields:
|
||||
leader_field_name = field_name.replace('_custom_key', '')
|
||||
leader_field = self.fields[leader_field_name]
|
||||
if hasattr(leader_field, 'choices'):
|
||||
|
||||
@@ -152,7 +152,7 @@ class DataImportSession(models.Model):
|
||||
|
||||
return supported_models().get(self.model_type, None)
|
||||
|
||||
def get_related_model(self, field_name: str) -> models.Model:
|
||||
def get_related_model(self, field_name: str) -> Optional[models.Model]:
|
||||
"""Return the related model for a given field name.
|
||||
|
||||
Arguments:
|
||||
@@ -699,7 +699,7 @@ class DataImportRow(models.Model):
|
||||
if commit:
|
||||
self.save()
|
||||
|
||||
def convert_date_field(self, value: str) -> str:
|
||||
def convert_date_field(self, value: str) -> Optional[str]:
|
||||
"""Convert an incoming date field to the correct format for the database."""
|
||||
if value in [None, '']:
|
||||
return None
|
||||
|
||||
@@ -111,7 +111,7 @@ class LabelPrinterBaseDriver(BaseDriver):
|
||||
Returns:
|
||||
A class instance of a DRF serializer class, by default this an instance of self.PrintingOptionsSerializer using the *args, **kwargs if existing for this driver
|
||||
"""
|
||||
return self.PrintingOptionsSerializer(*args, **kwargs) # type: ignore
|
||||
return self.PrintingOptionsSerializer(*args, **kwargs)
|
||||
|
||||
# --- helper functions
|
||||
@property
|
||||
|
||||
@@ -138,7 +138,7 @@ class MachineSettingSerializer(GenericReferencedSettingSerializer):
|
||||
"""Custom init method to make the config_type field read only."""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.Meta.read_only_fields = ['config_type'] # type: ignore
|
||||
self.Meta.read_only_fields = ['config_type']
|
||||
|
||||
|
||||
class BaseMachineClassSerializer(serializers.Serializer):
|
||||
|
||||
@@ -232,10 +232,10 @@ class TestLabelPrinterMachineType(InvenTreeAPITestCase):
|
||||
machine = self.create_machine()
|
||||
|
||||
# setup the label app
|
||||
apps.get_app_config('report').create_default_labels() # type: ignore
|
||||
apps.get_app_config('report').create_default_labels()
|
||||
plg_registry.reload_plugins()
|
||||
|
||||
config = cast(PluginConfig, plg_registry.get_plugin(plugin_ref).plugin_config()) # type: ignore
|
||||
config = cast(PluginConfig, plg_registry.get_plugin(plugin_ref).plugin_config())
|
||||
config.active = True
|
||||
config.save()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Order model definitions."""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Optional, TypedDict
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -179,7 +179,7 @@ class TotalPriceMixin(models.Model):
|
||||
return total
|
||||
|
||||
|
||||
class BaseOrderReportContext(report.mixins.BaseReportContext):
|
||||
class BaseOrderReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Base context for all order models.
|
||||
|
||||
Attributes:
|
||||
@@ -199,7 +199,7 @@ class BaseOrderReportContext(report.mixins.BaseReportContext):
|
||||
title: str
|
||||
|
||||
|
||||
class PurchaseOrderReportContext(report.mixins.BaseReportContext):
|
||||
class PurchaseOrderReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Context for the purchase order model.
|
||||
|
||||
Attributes:
|
||||
@@ -221,7 +221,7 @@ class PurchaseOrderReportContext(report.mixins.BaseReportContext):
|
||||
supplier: Optional[Company]
|
||||
|
||||
|
||||
class SalesOrderReportContext(report.mixins.BaseReportContext):
|
||||
class SalesOrderReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Context for the sales order model.
|
||||
|
||||
Attributes:
|
||||
@@ -243,7 +243,7 @@ class SalesOrderReportContext(report.mixins.BaseReportContext):
|
||||
customer: Optional[Company]
|
||||
|
||||
|
||||
class ReturnOrderReportContext(report.mixins.BaseReportContext):
|
||||
class ReturnOrderReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Context for the return order model.
|
||||
|
||||
Attributes:
|
||||
@@ -570,7 +570,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
|
||||
def report_context(self) -> PurchaseOrderReportContext:
|
||||
"""Return report context data for this PurchaseOrder."""
|
||||
return {**super().report_context(), 'supplier': self.supplier}
|
||||
return_ctx = super().report_context()
|
||||
return_ctx.update({'supplier': self.supplier})
|
||||
return return_ctx
|
||||
|
||||
def get_absolute_url(self) -> str:
|
||||
"""Get the 'web' URL for this order."""
|
||||
@@ -1269,7 +1271,9 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
|
||||
def report_context(self) -> SalesOrderReportContext:
|
||||
"""Generate report context data for this SalesOrder."""
|
||||
return {**super().report_context(), 'customer': self.customer}
|
||||
return_ctx = super().report_context()
|
||||
return_ctx.update({'customer': self.customer})
|
||||
return return_ctx
|
||||
|
||||
def get_absolute_url(self) -> str:
|
||||
"""Get the 'web' URL for this order."""
|
||||
@@ -2197,7 +2201,7 @@ class SalesOrderLineItem(OrderLineItem):
|
||||
return self.shipped >= self.quantity
|
||||
|
||||
|
||||
class SalesOrderShipmentReportContext(report.mixins.BaseReportContext):
|
||||
class SalesOrderShipmentReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Context for the SalesOrderShipment model.
|
||||
|
||||
Attributes:
|
||||
@@ -2662,7 +2666,9 @@ class ReturnOrder(TotalPriceMixin, Order):
|
||||
|
||||
def report_context(self) -> ReturnOrderReportContext:
|
||||
"""Generate report context data for this ReturnOrder."""
|
||||
return {**super().report_context(), 'customer': self.customer}
|
||||
return_ctx = super().report_context()
|
||||
return_ctx.update({'customer': self.customer})
|
||||
return return_ctx
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""Get the 'web' URL for this order."""
|
||||
|
||||
@@ -10,7 +10,7 @@ import os
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import cast
|
||||
from typing import TypedDict, cast
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
@@ -427,7 +427,7 @@ class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
|
||||
)
|
||||
|
||||
|
||||
class PartReportContext(report.mixins.BaseReportContext):
|
||||
class PartReportContext(report.mixins.BaseReportContext, TypedDict):
|
||||
"""Report context for the Part model.
|
||||
|
||||
Attributes:
|
||||
@@ -4216,7 +4216,7 @@ class BomItem(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel):
|
||||
if n <= 0:
|
||||
return 0.0
|
||||
|
||||
return int(available_stock / n)
|
||||
return int(Decimal(available_stock) / n)
|
||||
|
||||
def get_required_quantity(self, build_quantity: float) -> float:
|
||||
"""Calculate the required part quantity, based on the supplied build_quantity.
|
||||
|
||||
@@ -12,6 +12,7 @@ from opentelemetry import trace
|
||||
|
||||
import common.currency
|
||||
import common.notifications
|
||||
import InvenTree.helpers
|
||||
import InvenTree.helpers_model
|
||||
from common.settings import get_global_setting
|
||||
from InvenTree.tasks import (
|
||||
|
||||
@@ -50,8 +50,8 @@ class StaleStockNotificationTests(InvenTreeTestCase):
|
||||
set_global_setting('STOCK_STALE_DAYS', 7, self.user)
|
||||
|
||||
# Clear notifications
|
||||
NotificationEntry.objects.all().delete() # type: ignore[attr-defined]
|
||||
NotificationMessage.objects.all().delete() # type: ignore[attr-defined]
|
||||
NotificationEntry.objects.all().delete()
|
||||
NotificationMessage.objects.all().delete()
|
||||
|
||||
def create_stock_items_with_expiry(self):
|
||||
"""Create stock items with various expiry dates for testing."""
|
||||
@@ -101,7 +101,7 @@ class StaleStockNotificationTests(InvenTreeTestCase):
|
||||
part.tasks.notify_stale_stock(self.user, [])
|
||||
|
||||
# No notifications should be created
|
||||
self.assertEqual(NotificationMessage.objects.count(), 0) # type: ignore[attr-defined]
|
||||
self.assertEqual(NotificationMessage.objects.count(), 0)
|
||||
|
||||
def test_notify_stale_stock_single_item(self):
|
||||
"""Test notify_stale_stock with a single stale item."""
|
||||
|
||||
@@ -60,7 +60,7 @@ class BarcodeMixin:
|
||||
"""Does this plugin support barcode generation."""
|
||||
try:
|
||||
# Attempt to call the generate method
|
||||
self.generate(None) # type: ignore
|
||||
self.generate(None)
|
||||
except NotImplementedError:
|
||||
# If a NotImplementedError is raised, then barcode generation is not supported
|
||||
return False
|
||||
|
||||
@@ -161,7 +161,7 @@ class SupplierMixin(SettingsMixin, Generic[PartData]):
|
||||
|
||||
# assign parent_part to root_part if root_part has no variant of already
|
||||
if root_part and not root_part.is_template and not root_part.variant_of:
|
||||
root_part.variant_of = parent_part # type: ignore
|
||||
root_part.variant_of = parent_part
|
||||
root_part.save()
|
||||
|
||||
return parent_part
|
||||
|
||||
@@ -83,7 +83,7 @@ class UserInterfaceMixin:
|
||||
def __init__(self):
|
||||
"""Register mixin."""
|
||||
super().__init__()
|
||||
self.add_mixin(PluginMixinEnum.USER_INTERFACE, True, __class__) # type: ignore
|
||||
self.add_mixin(PluginMixinEnum.USER_INTERFACE, True, __class__)
|
||||
|
||||
def get_ui_features(
|
||||
self, feature_type: FeatureType, context: dict, request: Request, **kwargs
|
||||
|
||||
@@ -7,4 +7,4 @@ class BrokenFileIntegrationPlugin(InvenTreePlugin):
|
||||
"""An very broken plugin."""
|
||||
|
||||
|
||||
aaa = bb # noqa: F821 # type: ignore[unresolved-reference]
|
||||
aaa = bb # noqa: F821
|
||||
|
||||
@@ -192,7 +192,7 @@ def get_modules(pkg, path=None):
|
||||
if sys.version_info < (3, 12):
|
||||
module = finder.find_module(name).load_module(name)
|
||||
else:
|
||||
spec = finder.find_spec(name)
|
||||
spec = finder.find_spec(name) # type: ignore[missing-argument]
|
||||
module = module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import inspect
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from distutils.sysconfig import get_python_lib # type: ignore[import]
|
||||
from distutils.sysconfig import get_python_lib # type: ignore[unresolved-import]
|
||||
from importlib.metadata import PackageNotFoundError, metadata
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
@@ -92,16 +92,16 @@ class MetaBase:
|
||||
TITLE = None
|
||||
|
||||
@mark_final
|
||||
def get_meta_value(self, key: str, old_key: Optional[str] = None, __default=None):
|
||||
def get_meta_value(self, key: str, old_key: Optional[str] = None, default=None):
|
||||
"""Reference a meta item with a key.
|
||||
|
||||
Args:
|
||||
key (str): key for the value
|
||||
old_key (str, optional): deprecated key - will throw warning
|
||||
__default (optional): Value if nothing with key can be found. Defaults to None.
|
||||
default (optional): Value if nothing with key can be found. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Value referenced with key, old_key or __default if set and not value found
|
||||
Value referenced with key, old_key or default if set and not value found
|
||||
"""
|
||||
value = getattr(self, key, None)
|
||||
|
||||
@@ -117,9 +117,9 @@ class MetaBase:
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
# Use __default if still nothing set
|
||||
if (value is None) and __default:
|
||||
return __default
|
||||
# Use default if still nothing set
|
||||
if value is None and default is not None:
|
||||
return default
|
||||
return value
|
||||
|
||||
@mark_final
|
||||
|
||||
@@ -781,9 +781,9 @@ class PluginsRegistry:
|
||||
f"Plugin '{p}' is not compatible with the current InvenTree version {v}"
|
||||
)
|
||||
if v := plg_i.MIN_VERSION:
|
||||
_msg += _(f'Plugin requires at least version {v}') # type: ignore[unsupported-operator]
|
||||
_msg += _(f'Plugin requires at least version {v}') # ty:ignore[unsupported-operator]
|
||||
if v := plg_i.MAX_VERSION:
|
||||
_msg += _(f'Plugin requires at most version {v}') # type: ignore[unsupported-operator]
|
||||
_msg += _(f'Plugin requires at most version {v}') # ty:ignore[unsupported-operator]
|
||||
# Log to error stack
|
||||
log_registry_error(_msg, reference=f'{p}:init_plugin')
|
||||
else:
|
||||
|
||||
@@ -152,7 +152,7 @@ class SampleSupplierPlugin(SupplierMixin, InvenTreePlugin):
|
||||
# after the template part was created, we need to refresh the part from the db because its tree id may have changed
|
||||
# which results in an error if saved directly
|
||||
part.refresh_from_db()
|
||||
part.variant_of = parent_part # type: ignore
|
||||
part.variant_of = parent_part
|
||||
part.save()
|
||||
|
||||
return part
|
||||
|
||||
@@ -254,7 +254,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
|
||||
def test_folder_loading(self):
|
||||
"""Test that plugins in folders outside of BASE_DIR get loaded."""
|
||||
# Run in temporary directory -> always a new random name
|
||||
with tempfile.TemporaryDirectory() as tmp: # type: ignore[no-matching-overload]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
# Fill directory with sample data
|
||||
new_dir = Path(tmp).joinpath('mock')
|
||||
shutil.copytree(self.mockDir(), new_dir)
|
||||
|
||||
@@ -704,7 +704,7 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
def get_context(self, instance, request=None, **kwargs):
|
||||
"""Supply context data to the label template for rendering."""
|
||||
base_context = super().get_context(instance, request, **kwargs)
|
||||
label_context: LabelContextExtension = { # type: ignore[invalid-assignment]
|
||||
label_context: LabelContextExtension = {
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
'page_style': None,
|
||||
|
||||
@@ -710,25 +710,24 @@ tomli==2.4.0 \
|
||||
--hash=sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa \
|
||||
--hash=sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087
|
||||
# via coverage
|
||||
ty==0.0.1a21 \
|
||||
--hash=sha256:0efba2e52b58f536f4198ba5c4a36cac2ba67d83ec6f429ebc7704233bcda4c3 \
|
||||
--hash=sha256:1474d883129bb63da3b2380fc7ead824cd3baf6a9551e6aa476ffefc58057af3 \
|
||||
--hash=sha256:1f276ceab23a1410aec09508248c76ae0989c67fb7a0c287e0d4564994295531 \
|
||||
--hash=sha256:218d53e7919e885bd98e9196d9cb952d82178b299aa36da6f7f39333eb7400ed \
|
||||
--hash=sha256:21f708d02b6588323ffdbfdba38830dd0ecfd626db50aa6006b296b5470e52f9 \
|
||||
--hash=sha256:334d2a212ebf42a0e55d57561926af7679fe1e878175e11dcb81ad8df892844e \
|
||||
--hash=sha256:3c3bc66fcae41eff133cfe326dd65d82567a2fb5d4efe2128773b10ec2766819 \
|
||||
--hash=sha256:5dfc73299d441cc6454e36ed0a976877415024143dfca6592dc36f7701424383 \
|
||||
--hash=sha256:7505aeb8bf2a62f00f12cfa496f6c965074d75c8126268776565284c8a12d5dd \
|
||||
--hash=sha256:84243455f295ed850bd53f7089819321807d4e6ee3b1cbff6086137ae0259466 \
|
||||
--hash=sha256:87a200c21e02962e8a27374d9d152582331d57d709672431be58f4f898bf6cad \
|
||||
--hash=sha256:9463cac96b8f1bb5ba740fe1d42cd6bd152b43c5b159b2f07f8fd629bcdded34 \
|
||||
--hash=sha256:a8c769987d00fbc33054ff7e342633f475ea10dc43bc60fb9fb056159d48cb90 \
|
||||
--hash=sha256:ba13d03b9e095216ceb4e4d554a308517f28ab0a6e4dcd07cfe94563e4c2c489 \
|
||||
--hash=sha256:be8f457d7841b7ead2a3f6b65ba668abc172a1150a0f1f6c0958af3725dbb61a \
|
||||
--hash=sha256:cc0880ec344fbdf736b05d8d0da01f0caaaa02409bd9a24b68d18d0127a79b0e \
|
||||
--hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \
|
||||
--hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a
|
||||
ty==0.0.21 \
|
||||
--hash=sha256:210e7568c9f886c4d01308d751949ee714ad7ad9d7d928d2ba90d329dd880367 \
|
||||
--hash=sha256:53508e345b11569f78b21ba8e2b4e61df38a9754947fb3cd9f2ef574367338fb \
|
||||
--hash=sha256:553e43571f4a35604c36cfd07d8b61a5eb7a714e3c67f8c4ff2cf674fefbaef9 \
|
||||
--hash=sha256:56b01fd2519637a4ca88344f61c96225f540c98ff18bca321d4eaa7bb0f7aa2f \
|
||||
--hash=sha256:56d3b198b64dd0a19b2b66e257deaed2ecea568e722ae5352f3c6fb62027f89d \
|
||||
--hash=sha256:62f7f5b235c4f7876db305c36997aea07b7af29b1a068f373d0e2547e25f32ff \
|
||||
--hash=sha256:666f6822e3b9200abfa7e95eb0ddd576460adb8d66b550c0ad2c70abc84a2048 \
|
||||
--hash=sha256:7bdf2f572378de78e1f388d24691c89db51b7caf07cf90f2bfcc1d6b18b70a76 \
|
||||
--hash=sha256:7e9613994610431ab8625025bd2880dbcb77c5c9fabdd21134cda12d840a529d \
|
||||
--hash=sha256:a0854d008347ce4a5fb351af132f660a390ab2a1163444d075251d43e6f74b9b \
|
||||
--hash=sha256:a4c2ba5d67d64df8fcdefd8b280ac1149d24a73dbda82fa953a0dff9d21400ed \
|
||||
--hash=sha256:a709d576e5bea84b745d43058d8b9cd4f27f74a0b24acb4b0cbb7d3d41e0d050 \
|
||||
--hash=sha256:bef3ab4c7b966bcc276a8ac6c11b63ba222d21355b48d471ea782c4104eee4e0 \
|
||||
--hash=sha256:d23d2c34f7a77d974bb08f0860ef700addc8a683d81a0319f71c08f87506cfd0 \
|
||||
--hash=sha256:e9de7e11c63c6afc40f3e9ba716374add171aee7fabc70b5146a510705c6d41b \
|
||||
--hash=sha256:ee8399f7c453a425291e6688efe430cfae7ab0ac4ffd50eba9f872bf878b54f6 \
|
||||
--hash=sha256:f72047996598ac20553fb7e21ba5741e3c82dee4e9eadf10d954551a5fe09391
|
||||
# via -r src/backend/requirements-dev.in
|
||||
types-psycopg2==2.9.21.20260223 \
|
||||
--hash=sha256:78ed70de2e56bc6b5c26c8c1da8e9af54e49fdc3c94d1504609f3519e2b84f02 \
|
||||
|
||||
Reference in New Issue
Block a user