mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
remove unneeded branches
fixes SIM102
This commit is contained in:
parent
372e3d417e
commit
f74d41bc07
@ -115,10 +115,9 @@ def exception_handler(exc, context):
|
||||
|
||||
log_error(context['request'].path)
|
||||
|
||||
if response is not None:
|
||||
# Convert errors returned under the label '__all__' to 'non_field_errors'
|
||||
if '__all__' in response.data:
|
||||
response.data['non_field_errors'] = response.data['__all__']
|
||||
del response.data['__all__']
|
||||
# Convert errors returned under the label '__all__' to 'non_field_errors'
|
||||
if response is not None and '__all__' in response.data:
|
||||
response.data['non_field_errors'] = response.data['__all__']
|
||||
del response.data['__all__']
|
||||
|
||||
return response
|
||||
|
@ -184,9 +184,12 @@ def getSplashScreen(custom=True):
|
||||
"""Return the InvenTree splash screen, or a custom splash if available."""
|
||||
static_storage = StaticFilesStorage()
|
||||
|
||||
if custom and settings.CUSTOM_SPLASH:
|
||||
if static_storage.exists(settings.CUSTOM_SPLASH):
|
||||
return static_storage.url(settings.CUSTOM_SPLASH)
|
||||
if (
|
||||
custom
|
||||
and settings.CUSTOM_SPLASH
|
||||
and static_storage.exists(settings.CUSTOM_SPLASH)
|
||||
):
|
||||
return static_storage.url(settings.CUSTOM_SPLASH)
|
||||
|
||||
# No custom splash screen
|
||||
return static_storage.url('img/inventree_splash.jpg')
|
||||
|
@ -34,10 +34,9 @@ class Command(BaseCommand):
|
||||
if x and x.path:
|
||||
img_paths.append(x.path)
|
||||
|
||||
if len(img_paths) > 0:
|
||||
if all(os.path.exists(path) for path in img_paths):
|
||||
# All images exist - skip further work
|
||||
return
|
||||
if len(img_paths) > 0 and all(os.path.exists(path) for path in img_paths):
|
||||
# All images exist - skip further work
|
||||
return
|
||||
|
||||
logger.info("Generating thumbnail image for '%s'", img)
|
||||
|
||||
|
@ -28,10 +28,12 @@ def get_token_from_request(request):
|
||||
if auth_header := request.headers.get(k, None):
|
||||
auth_header = auth_header.strip().lower().split()
|
||||
|
||||
if len(auth_header) > 1:
|
||||
if auth_header[0].strip().lower().replace(':', '') in token_keys:
|
||||
token = auth_header[1]
|
||||
return token
|
||||
if (
|
||||
len(auth_header) > 1
|
||||
and auth_header[0].strip().lower().replace(':', '') in token_keys
|
||||
):
|
||||
token = auth_header[1]
|
||||
return token
|
||||
|
||||
return None
|
||||
|
||||
|
@ -63,11 +63,14 @@ def init_sentry(dsn, sample_rate, tags):
|
||||
|
||||
def report_exception(exc):
|
||||
"""Report an exception to sentry.io."""
|
||||
if settings.SENTRY_ENABLED and settings.SENTRY_DSN:
|
||||
if not any(isinstance(exc, e) for e in sentry_ignore_errors()):
|
||||
logger.info('Reporting exception to sentry.io: %s', exc)
|
||||
if (
|
||||
settings.SENTRY_ENABLED
|
||||
and settings.SENTRY_DSN
|
||||
and not any(isinstance(exc, e) for e in sentry_ignore_errors())
|
||||
):
|
||||
logger.info('Reporting exception to sentry.io: %s', exc)
|
||||
|
||||
try:
|
||||
sentry_sdk.capture_exception(exc)
|
||||
except Exception:
|
||||
logger.warning('Failed to report exception to sentry.io')
|
||||
try:
|
||||
sentry_sdk.capture_exception(exc)
|
||||
except Exception:
|
||||
logger.warning('Failed to report exception to sentry.io')
|
||||
|
@ -50,11 +50,10 @@ def check_provider(provider):
|
||||
if not app:
|
||||
return False
|
||||
|
||||
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)
|
||||
return False
|
||||
# At least one matching site must be specified
|
||||
if allauth.app_settings.SITES_ENABLED and not app.sites.exists():
|
||||
logger.error('SocialApp %s has no sites configured', app)
|
||||
return False
|
||||
|
||||
# At this point, we assume that the provider is correctly configured
|
||||
return True
|
||||
|
@ -136,12 +136,11 @@ def inventree_in_debug_mode(*args, **kwargs):
|
||||
@register.simple_tag()
|
||||
def inventree_show_about(user, *args, **kwargs):
|
||||
"""Return True if the about modal should be shown."""
|
||||
if get_global_setting('INVENTREE_RESTRICT_ABOUT'):
|
||||
# Return False if the user is not a superuser, or no user information is provided
|
||||
if not user or not user.is_superuser:
|
||||
return False
|
||||
|
||||
return True
|
||||
# Return False if the user is not a superuser, or no user information is provided
|
||||
return not (
|
||||
(get_global_setting('INVENTREE_RESTRICT_ABOUT') and not user)
|
||||
or not user.is_superuser
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
|
@ -69,11 +69,10 @@ class AllowedURLValidator(validators.URLValidator):
|
||||
# Determine if 'strict' URL validation is required (i.e. if the URL must have a schema prefix)
|
||||
strict_urls = get_global_setting('INVENTREE_STRICT_URLS', cache=False)
|
||||
|
||||
if not strict_urls:
|
||||
# Allow URLs which do not have a provided schema
|
||||
if '://' not in value:
|
||||
# Validate as if it were http
|
||||
value = 'http://' + value
|
||||
# Allow URLs which do not have a provided schema
|
||||
if not strict_urls and '://' not in value:
|
||||
# Validate as if it were http
|
||||
value = 'http://' + value
|
||||
|
||||
super().__call__(value)
|
||||
|
||||
|
@ -426,20 +426,21 @@ class SetPasswordView(AjaxUpdateView):
|
||||
old_password = request.POST.get('old_password', '')
|
||||
user = self.request.user
|
||||
|
||||
if valid:
|
||||
# Passwords must match
|
||||
# Passwords must match
|
||||
if valid and p1 != p2:
|
||||
error = _('Password fields must match')
|
||||
form.add_error('enter_password', error)
|
||||
form.add_error('confirm_password', error)
|
||||
valid = False
|
||||
|
||||
if p1 != p2:
|
||||
error = _('Password fields must match')
|
||||
form.add_error('enter_password', error)
|
||||
form.add_error('confirm_password', error)
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
# Old password must be correct
|
||||
if user.has_usable_password() and not user.check_password(old_password):
|
||||
form.add_error('old_password', _('Wrong password provided'))
|
||||
valid = False
|
||||
# Old password must be correct
|
||||
if (
|
||||
valid
|
||||
and user.has_usable_password()
|
||||
and not user.check_password(old_password)
|
||||
):
|
||||
form.add_error('old_password', _('Wrong password provided'))
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
try:
|
||||
|
@ -444,11 +444,10 @@ class SettingsTest(InvenTreeTestCase):
|
||||
# Any fields marked as 'boolean' must have a default value specified
|
||||
setting = InvenTreeSetting.get_setting_object(key)
|
||||
|
||||
if setting.is_bool():
|
||||
if setting.default_value not in [True, False]:
|
||||
raise ValueError(
|
||||
f'Non-boolean default value specified for {key}'
|
||||
) # pragma: no cover
|
||||
if setting.is_bool() and setting.default_value not in [True, False]:
|
||||
raise ValueError(
|
||||
f'Non-boolean default value specified for {key}'
|
||||
) # pragma: no cover
|
||||
|
||||
def test_global_setting_caching(self):
|
||||
"""Test caching operations for the global settings class."""
|
||||
|
@ -730,13 +730,16 @@ class SupplierPart(
|
||||
raise ValidationError({'pack_quantity': e.messages})
|
||||
|
||||
# Ensure that the linked manufacturer_part points to the same part!
|
||||
if self.manufacturer_part and self.part:
|
||||
if self.manufacturer_part.part != self.part:
|
||||
raise ValidationError({
|
||||
'manufacturer_part': _(
|
||||
'Linked manufacturer part must reference the same base part'
|
||||
)
|
||||
})
|
||||
if (
|
||||
self.manufacturer_part
|
||||
and self.part
|
||||
and self.manufacturer_part.part != self.part
|
||||
):
|
||||
raise ValidationError({
|
||||
'manufacturer_part': _(
|
||||
'Linked manufacturer part must reference the same base part'
|
||||
)
|
||||
})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Overriding save method to connect an existing ManufacturerPart."""
|
||||
@ -1049,9 +1052,13 @@ class SupplierPriceBreak(common.models.PriceBreak):
|
||||
)
|
||||
def after_save_supplier_price(sender, instance, created, **kwargs):
|
||||
"""Callback function when a SupplierPriceBreak is created or updated."""
|
||||
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
|
||||
if instance.part and instance.part.part:
|
||||
instance.part.part.schedule_pricing_update(create=True)
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase()
|
||||
and not InvenTree.ready.isImportingData()
|
||||
and instance.part
|
||||
and instance.part.part
|
||||
):
|
||||
instance.part.part.schedule_pricing_update(create=True)
|
||||
|
||||
|
||||
@receiver(
|
||||
@ -1061,6 +1068,10 @@ def after_save_supplier_price(sender, instance, created, **kwargs):
|
||||
)
|
||||
def after_delete_supplier_price(sender, instance, **kwargs):
|
||||
"""Callback function when a SupplierPriceBreak is deleted."""
|
||||
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
|
||||
if instance.part and instance.part.part:
|
||||
instance.part.part.schedule_pricing_update(create=False)
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase()
|
||||
and not InvenTree.ready.isImportingData()
|
||||
and instance.part
|
||||
and instance.part.part
|
||||
):
|
||||
instance.part.part.schedule_pricing_update(create=False)
|
||||
|
@ -1350,15 +1350,13 @@ class OrderCalendarExport(ICalFeed):
|
||||
# No login yet - check in headers
|
||||
if 'authorization' in request.headers:
|
||||
auth = request.headers['authorization'].split()
|
||||
if len(auth) == 2:
|
||||
# NOTE: We are only support basic authentication for now.
|
||||
#
|
||||
if auth[0].lower() == 'basic':
|
||||
uname, passwd = base64.b64decode(auth[1]).decode('ascii').split(':')
|
||||
user = authenticate(username=uname, password=passwd)
|
||||
if user is not None and user.is_active:
|
||||
login(request, user)
|
||||
request.user = user
|
||||
# NOTE: We are only support basic authentication for now.
|
||||
if len(auth) == 2 and auth[0].lower() == 'basic':
|
||||
uname, passwd = base64.b64decode(auth[1]).decode('ascii').split(':')
|
||||
user = authenticate(username=uname, password=passwd)
|
||||
if user is not None and user.is_active:
|
||||
login(request, user)
|
||||
request.user = user
|
||||
|
||||
# Check again
|
||||
if request.user.is_authenticated:
|
||||
|
@ -232,19 +232,20 @@ class Order(
|
||||
super().clean()
|
||||
|
||||
# Check if a responsible owner is required for this order type
|
||||
if self.REQUIRE_RESPONSIBLE_SETTING:
|
||||
if get_global_setting(self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False):
|
||||
if not self.responsible:
|
||||
raise ValidationError({
|
||||
'responsible': _('Responsible user or group must be specified')
|
||||
})
|
||||
if (
|
||||
self.REQUIRE_RESPONSIBLE_SETTING
|
||||
and get_global_setting(self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False)
|
||||
and not self.responsible
|
||||
):
|
||||
raise ValidationError({
|
||||
'responsible': _('Responsible user or group must be specified')
|
||||
})
|
||||
|
||||
# Check that the referenced 'contact' matches the correct 'company'
|
||||
if self.company and self.contact:
|
||||
if self.contact.company != self.company:
|
||||
raise ValidationError({
|
||||
'contact': _('Contact does not match selected company')
|
||||
})
|
||||
if self.company and self.contact and self.contact.company != self.company:
|
||||
raise ValidationError({
|
||||
'contact': _('Contact does not match selected company')
|
||||
})
|
||||
|
||||
def report_context(self):
|
||||
"""Generate context data for the reporting interface."""
|
||||
@ -783,10 +784,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
# Extract optional packaging field
|
||||
packaging = kwargs.get('packaging', None)
|
||||
|
||||
if not packaging:
|
||||
# Default to the packaging field for the linked supplier part
|
||||
if line.part:
|
||||
packaging = line.part.packaging
|
||||
# Default to the packaging field for the linked supplier part
|
||||
if not packaging and line.part:
|
||||
packaging = line.part.packaging
|
||||
|
||||
# Extract optional barcode field
|
||||
barcode = kwargs.get('barcode', None)
|
||||
@ -866,10 +866,11 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
line.save()
|
||||
|
||||
# Has this order been completed?
|
||||
if len(self.pending_line_items()) == 0:
|
||||
if get_global_setting('PURCHASEORDER_AUTO_COMPLETE', True):
|
||||
self.received_by = user
|
||||
self.complete_order() # This will save the model
|
||||
if len(self.pending_line_items()) == 0 and get_global_setting(
|
||||
'PURCHASEORDER_AUTO_COMPLETE', True
|
||||
):
|
||||
self.received_by = user
|
||||
self.complete_order() # This will save the model
|
||||
|
||||
# Issue a notification to interested parties, that this order has been "updated"
|
||||
notify_responsible(
|
||||
@ -1455,10 +1456,13 @@ class PurchaseOrderLineItem(OrderLineItem):
|
||||
"""
|
||||
super().clean()
|
||||
|
||||
if self.order.supplier and self.part:
|
||||
# Supplier part *must* point to the same supplier!
|
||||
if self.part.supplier != self.order.supplier:
|
||||
raise ValidationError({'part': _('Supplier part must match supplier')})
|
||||
# Supplier part *must* point to the same supplier!
|
||||
if (
|
||||
self.order.supplier
|
||||
and self.part
|
||||
and self.part.supplier != self.order.supplier
|
||||
):
|
||||
raise ValidationError({'part': _('Supplier part must match supplier')})
|
||||
|
||||
def __str__(self):
|
||||
"""Render a string representation of a PurchaseOrderLineItem instance."""
|
||||
|
@ -91,9 +91,10 @@ def ExportBom(
|
||||
|
||||
bom_items.append(item)
|
||||
|
||||
if cascade and item.sub_part.assembly:
|
||||
if max_levels is None or level < max_levels:
|
||||
add_items(item.sub_part.bom_items.all().order_by('id'), level + 1)
|
||||
if (
|
||||
cascade and item.sub_part.assembly and max_levels is None
|
||||
) or level < max_levels:
|
||||
add_items(item.sub_part.bom_items.all().order_by('id'), level + 1)
|
||||
|
||||
top_level_items = part.get_bom_items().order_by('id')
|
||||
|
||||
|
@ -515,11 +515,13 @@ class Part(
|
||||
if self.active:
|
||||
raise ValidationError(_('Cannot delete this part as it is still active'))
|
||||
|
||||
if not get_global_setting('PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False):
|
||||
if BomItem.objects.filter(sub_part=self).exists():
|
||||
raise ValidationError(
|
||||
_('Cannot delete this part as it is used in an assembly')
|
||||
)
|
||||
if (
|
||||
not get_global_setting('PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False)
|
||||
and BomItem.objects.filter(sub_part=self).exists()
|
||||
):
|
||||
raise ValidationError(
|
||||
_('Cannot delete this part as it is used in an assembly')
|
||||
)
|
||||
|
||||
super().delete()
|
||||
|
||||
@ -711,13 +713,12 @@ class Part(
|
||||
'revision': _('Revision code must be specified')
|
||||
})
|
||||
|
||||
if get_global_setting('PART_REVISION_ASSEMBLY_ONLY'):
|
||||
if not self.assembly or not self.revision_of.assembly:
|
||||
raise ValidationError({
|
||||
'revision_of': _(
|
||||
'Revisions are only allowed for assembly parts'
|
||||
)
|
||||
})
|
||||
if (
|
||||
get_global_setting('PART_REVISION_ASSEMBLY_ONLY') and not self.assembly
|
||||
) or not self.revision_of.assembly:
|
||||
raise ValidationError({
|
||||
'revision_of': _('Revisions are only allowed for assembly parts')
|
||||
})
|
||||
|
||||
# Cannot have a revision of a "template" part
|
||||
if self.revision_of.is_template:
|
||||
@ -2753,13 +2754,15 @@ class PartPricing(common.models.MetaMixin):
|
||||
sub_part_min = self.convert(sub_part_pricing.overall_min)
|
||||
sub_part_max = self.convert(sub_part_pricing.overall_max)
|
||||
|
||||
if sub_part_min is not None:
|
||||
if bom_item_min is None or sub_part_min < bom_item_min:
|
||||
bom_item_min = sub_part_min
|
||||
if (
|
||||
sub_part_min is not None and bom_item_min is None
|
||||
) or sub_part_min < bom_item_min:
|
||||
bom_item_min = sub_part_min
|
||||
|
||||
if sub_part_max is not None:
|
||||
if bom_item_max is None or sub_part_max > bom_item_max:
|
||||
bom_item_max = sub_part_max
|
||||
if (
|
||||
sub_part_max is not None and bom_item_max is None
|
||||
) or sub_part_max > bom_item_max:
|
||||
bom_item_max = sub_part_max
|
||||
|
||||
# Update cumulative totals
|
||||
if bom_item_min is not None:
|
||||
@ -2962,13 +2965,11 @@ class PartPricing(common.models.MetaMixin):
|
||||
v_min = self.convert(v.pricing.overall_min)
|
||||
v_max = self.convert(v.pricing.overall_max)
|
||||
|
||||
if v_min is not None:
|
||||
if variant_min is None or v_min < variant_min:
|
||||
variant_min = v_min
|
||||
if (v_min is not None and variant_min is None) or v_min < variant_min:
|
||||
variant_min = v_min
|
||||
|
||||
if v_max is not None:
|
||||
if variant_max is None or v_max > variant_max:
|
||||
variant_max = v_max
|
||||
if (v_max is not None and variant_max is None) or v_max > variant_max:
|
||||
variant_max = v_max
|
||||
|
||||
if self.variant_cost_min != variant_min or self.variant_cost_max != variant_max:
|
||||
self.price_modified = True
|
||||
@ -3800,12 +3801,15 @@ def post_save_part_parameter_template(sender, instance, created, **kwargs):
|
||||
"""Callback function when a PartParameterTemplate is created or saved."""
|
||||
import part.tasks as part_tasks
|
||||
|
||||
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
|
||||
if not created:
|
||||
# Schedule a background task to rebuild the parameters against this template
|
||||
InvenTree.tasks.offload_task(
|
||||
part_tasks.rebuild_parameters, instance.pk, force_async=True
|
||||
)
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase()
|
||||
and not InvenTree.ready.isImportingData()
|
||||
and not created
|
||||
):
|
||||
# Schedule a background task to rebuild the parameters against this template
|
||||
InvenTree.tasks.offload_task(
|
||||
part_tasks.rebuild_parameters, instance.pk, force_async=True
|
||||
)
|
||||
|
||||
|
||||
class PartParameter(InvenTree.models.InvenTreeMetadataModel):
|
||||
@ -3922,11 +3926,14 @@ class PartParameter(InvenTree.models.InvenTreeMetadataModel):
|
||||
except ValueError:
|
||||
self.data_numeric = None
|
||||
|
||||
if self.data_numeric is not None and type(self.data_numeric) is float:
|
||||
# Prevent out of range numbers, etc
|
||||
# Ref: https://github.com/inventree/InvenTree/issues/7593
|
||||
if math.isnan(self.data_numeric) or math.isinf(self.data_numeric):
|
||||
self.data_numeric = None
|
||||
# Prevent out of range numbers, etc
|
||||
# Ref: https://github.com/inventree/InvenTree/issues/7593
|
||||
if (
|
||||
self.data_numeric is not None
|
||||
and type(self.data_numeric) is float
|
||||
and math.isnan(self.data_numeric)
|
||||
) or math.isinf(self.data_numeric):
|
||||
self.data_numeric = None
|
||||
|
||||
part = models.ForeignKey(
|
||||
Part,
|
||||
@ -4509,9 +4516,12 @@ def update_bom_build_lines(sender, instance, created, **kwargs):
|
||||
def update_pricing_after_edit(sender, instance, created, **kwargs):
|
||||
"""Callback function when a part price break is created or updated."""
|
||||
# Update part pricing *unless* we are importing data
|
||||
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
|
||||
if instance.part:
|
||||
instance.part.schedule_pricing_update(create=True)
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase()
|
||||
and not InvenTree.ready.isImportingData()
|
||||
and instance.part
|
||||
):
|
||||
instance.part.schedule_pricing_update(create=True)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=BomItem, dispatch_uid='post_delete_bom_item')
|
||||
@ -4526,9 +4536,12 @@ def update_pricing_after_edit(sender, instance, created, **kwargs):
|
||||
def update_pricing_after_delete(sender, instance, **kwargs):
|
||||
"""Callback function when a part price break is deleted."""
|
||||
# Update part pricing *unless* we are importing data
|
||||
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
|
||||
if instance.part:
|
||||
instance.part.schedule_pricing_update(create=False)
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase()
|
||||
and not InvenTree.ready.isImportingData()
|
||||
and instance.part
|
||||
):
|
||||
instance.part.schedule_pricing_update(create=False)
|
||||
|
||||
|
||||
class BomItemSubstitute(InvenTree.models.InvenTreeMetadataModel):
|
||||
|
@ -563,10 +563,13 @@ class BarcodeSOAllocate(BarcodeView):
|
||||
sales_order = kwargs['sales_order']
|
||||
shipment = self.get_shipment(**kwargs)
|
||||
|
||||
if stock_item is not None and line_item is not None:
|
||||
if stock_item.part != line_item.part:
|
||||
result['error'] = _('Stock item does not match line item')
|
||||
raise ValidationError(result)
|
||||
if (
|
||||
stock_item is not None
|
||||
and line_item is not None
|
||||
and stock_item.part != line_item.part
|
||||
):
|
||||
result['error'] = _('Stock item does not match line item')
|
||||
raise ValidationError(result)
|
||||
|
||||
quantity = kwargs.get('quantity', None)
|
||||
|
||||
@ -587,10 +590,13 @@ class BarcodeSOAllocate(BarcodeView):
|
||||
'quantity': quantity,
|
||||
}
|
||||
|
||||
if stock_item is not None and quantity is not None:
|
||||
if stock_item.unallocated_quantity() < quantity:
|
||||
response['error'] = _('Insufficient stock available')
|
||||
raise ValidationError(response)
|
||||
if (
|
||||
stock_item is not None
|
||||
and quantity is not None
|
||||
and stock_item.unallocated_quantity() < quantity
|
||||
):
|
||||
response['error'] = _('Insufficient stock available')
|
||||
raise ValidationError(response)
|
||||
|
||||
# If we have sufficient information, we can allocate the stock item
|
||||
if all(x is not None for x in [line_item, sales_order, shipment, quantity]):
|
||||
|
@ -477,9 +477,10 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
||||
location := supplier_part.part.get_default_location()
|
||||
):
|
||||
pass
|
||||
elif StockLocation.objects.count() <= 1:
|
||||
if not (location := StockLocation.objects.first()):
|
||||
no_stock_locations = True
|
||||
elif StockLocation.objects.count() <= 1 and not (
|
||||
location := StockLocation.objects.first()
|
||||
):
|
||||
no_stock_locations = True
|
||||
|
||||
response = {
|
||||
'lineitem': {'pk': line_item.pk, 'purchase_order': purchase_order.pk}
|
||||
|
@ -195,15 +195,13 @@ class PluginsRegistry:
|
||||
|
||||
for plugin in self.plugins.values():
|
||||
if plugin.mixin_enabled(mixin):
|
||||
if active is not None:
|
||||
# Filter by 'active' status of plugin
|
||||
if active != plugin.is_active():
|
||||
continue
|
||||
# Filter by 'active' status of plugin
|
||||
if active is not None and active != plugin.is_active():
|
||||
continue
|
||||
|
||||
if builtin is not None:
|
||||
# Filter by 'builtin' status of plugin
|
||||
if builtin != plugin.is_builtin:
|
||||
continue
|
||||
# Filter by 'builtin' status of plugin
|
||||
if builtin is not None and builtin != plugin.is_builtin:
|
||||
continue
|
||||
|
||||
result.append(plugin)
|
||||
|
||||
@ -396,21 +394,20 @@ class PluginsRegistry:
|
||||
collected_plugins.append(item)
|
||||
|
||||
# From this point any plugins are considered "external" and only loaded if plugins are explicitly enabled
|
||||
if settings.PLUGINS_ENABLED:
|
||||
# Check if not running in testing mode and apps should be loaded from hooks
|
||||
if (not settings.PLUGIN_TESTING) or (
|
||||
settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP
|
||||
):
|
||||
# Collect plugins from setup entry points
|
||||
for entry in get_entrypoints():
|
||||
try:
|
||||
plugin = entry.load()
|
||||
plugin.is_package = True
|
||||
plugin.package_name = getattr(entry.dist, 'name', None)
|
||||
plugin._get_package_metadata()
|
||||
collected_plugins.append(plugin)
|
||||
except Exception as error: # pragma: no cover
|
||||
handle_error(error, do_raise=False, log_name='discovery')
|
||||
# Check if not running in testing mode and apps should be loaded from hooks
|
||||
if (settings.PLUGINS_ENABLED and (not settings.PLUGIN_TESTING)) or (
|
||||
settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP
|
||||
):
|
||||
# Collect plugins from setup entry points
|
||||
for entry in get_entrypoints():
|
||||
try:
|
||||
plugin = entry.load()
|
||||
plugin.is_package = True
|
||||
plugin.package_name = getattr(entry.dist, 'name', None)
|
||||
plugin._get_package_metadata()
|
||||
collected_plugins.append(plugin)
|
||||
except Exception as error: # pragma: no cover
|
||||
handle_error(error, do_raise=False, log_name='discovery')
|
||||
|
||||
# Log collected plugins
|
||||
logger.info('Collected %s plugins', len(collected_plugins))
|
||||
|
@ -66,24 +66,26 @@ class SampleValidatorPlugin(SettingsMixin, ValidationMixin, InvenTreePlugin):
|
||||
# Print debug message to console (intentional)
|
||||
print('Validating model instance:', instance.__class__, f'<{instance.pk}>')
|
||||
|
||||
if isinstance(instance, part.models.BomItem):
|
||||
if self.get_setting('BOM_ITEM_INTEGER'):
|
||||
if float(instance.quantity) != int(instance.quantity):
|
||||
self.raise_error({
|
||||
'quantity': 'Bom item quantity must be an integer'
|
||||
})
|
||||
if (
|
||||
isinstance(instance, part.models.BomItem)
|
||||
and self.get_setting('BOM_ITEM_INTEGER')
|
||||
and float(instance.quantity) != int(instance.quantity)
|
||||
):
|
||||
self.raise_error({'quantity': 'Bom item quantity must be an integer'})
|
||||
|
||||
if isinstance(instance, part.models.Part):
|
||||
# If the part description is being updated, prevent it from being reduced in length
|
||||
# If the part description is being updated, prevent it from being reduced in length
|
||||
if (
|
||||
isinstance(instance, part.models.Part)
|
||||
and deltas
|
||||
and 'description' in deltas
|
||||
):
|
||||
old_desc = deltas['description']['old']
|
||||
new_desc = deltas['description']['new']
|
||||
|
||||
if deltas and 'description' in deltas:
|
||||
old_desc = deltas['description']['old']
|
||||
new_desc = deltas['description']['new']
|
||||
|
||||
if len(new_desc) < len(old_desc):
|
||||
self.raise_error({
|
||||
'description': 'Part description cannot be shortened'
|
||||
})
|
||||
if len(new_desc) < len(old_desc):
|
||||
self.raise_error({
|
||||
'description': 'Part description cannot be shortened'
|
||||
})
|
||||
|
||||
def validate_part_name(self, name: str, part):
|
||||
"""Custom validation for Part name field.
|
||||
@ -126,14 +128,12 @@ class SampleValidatorPlugin(SettingsMixin, ValidationMixin, InvenTreePlugin):
|
||||
|
||||
These examples are silly, but serve to demonstrate how the feature could be used
|
||||
"""
|
||||
if self.get_setting('SERIAL_MUST_BE_PALINDROME'):
|
||||
if serial != serial[::-1]:
|
||||
self.raise_error('Serial must be a palindrome')
|
||||
if self.get_setting('SERIAL_MUST_BE_PALINDROME') and serial != serial[::-1]:
|
||||
self.raise_error('Serial must be a palindrome')
|
||||
|
||||
if self.get_setting('SERIAL_MUST_MATCH_PART'):
|
||||
# Serial must start with the same letter as the linked part, for some reason
|
||||
if serial[0] != part.name[0]:
|
||||
self.raise_error('Serial number must start with same letter as part')
|
||||
# Serial must start with the same letter as the linked part, for some reason
|
||||
if self.get_setting('SERIAL_MUST_MATCH_PART') and serial[0] != part.name[0]:
|
||||
self.raise_error('Serial number must start with same letter as part')
|
||||
|
||||
def validate_batch_code(self, batch_code: str, item):
|
||||
"""Ensure that a particular batch code meets specification.
|
||||
|
@ -700,39 +700,37 @@ class StockItem(
|
||||
|
||||
# The 'supplier_part' field must point to the same part!
|
||||
try:
|
||||
if self.supplier_part is not None:
|
||||
if self.supplier_part.part != self.part:
|
||||
if self.supplier_part is not None and self.supplier_part.part != self.part:
|
||||
raise ValidationError({
|
||||
'supplier_part': _(
|
||||
f"Part type ('{self.supplier_part.part}') must be {self.part}"
|
||||
)
|
||||
})
|
||||
|
||||
# A part with a serial number MUST have the quantity set to 1
|
||||
if self.part is not None and self.serial:
|
||||
if self.quantity > 1:
|
||||
raise ValidationError({
|
||||
'supplier_part': _(
|
||||
f"Part type ('{self.supplier_part.part}') must be {self.part}"
|
||||
'quantity': _(
|
||||
'Quantity must be 1 for item with a serial number'
|
||||
),
|
||||
'serial': _(
|
||||
'Serial number cannot be set if quantity greater than 1'
|
||||
),
|
||||
})
|
||||
|
||||
if self.quantity == 0:
|
||||
self.quantity = 1
|
||||
|
||||
elif self.quantity > 1:
|
||||
raise ValidationError({
|
||||
'quantity': _(
|
||||
'Quantity must be 1 for item with a serial number'
|
||||
)
|
||||
})
|
||||
|
||||
if self.part is not None:
|
||||
# A part with a serial number MUST have the quantity set to 1
|
||||
if self.serial:
|
||||
if self.quantity > 1:
|
||||
raise ValidationError({
|
||||
'quantity': _(
|
||||
'Quantity must be 1 for item with a serial number'
|
||||
),
|
||||
'serial': _(
|
||||
'Serial number cannot be set if quantity greater than 1'
|
||||
),
|
||||
})
|
||||
|
||||
if self.quantity == 0:
|
||||
self.quantity = 1
|
||||
|
||||
elif self.quantity > 1:
|
||||
raise ValidationError({
|
||||
'quantity': _(
|
||||
'Quantity must be 1 for item with a serial number'
|
||||
)
|
||||
})
|
||||
|
||||
# Serial numbered items cannot be deleted on depletion
|
||||
self.delete_on_deplete = False
|
||||
# Serial numbered items cannot be deleted on depletion
|
||||
self.delete_on_deplete = False
|
||||
|
||||
except PartModels.Part.DoesNotExist:
|
||||
pass
|
||||
|
@ -795,14 +795,11 @@ class InstallStockItemSerializer(serializers.Serializer):
|
||||
parent_item = self.context['item']
|
||||
parent_part = parent_item.part
|
||||
|
||||
# Check if the selected part is in the Bill of Materials of the parent item
|
||||
if get_global_setting(
|
||||
'STOCK_ENFORCE_BOM_INSTALLATION', backup_value=True, cache=False
|
||||
):
|
||||
# Check if the selected part is in the Bill of Materials of the parent item
|
||||
if not parent_part.check_if_part_in_bom(stock_item.part):
|
||||
raise ValidationError(
|
||||
_('Selected part is not in the Bill of Materials')
|
||||
)
|
||||
) and not parent_part.check_if_part_in_bom(stock_item.part):
|
||||
raise ValidationError(_('Selected part is not in the Bill of Materials'))
|
||||
|
||||
return stock_item
|
||||
|
||||
|
@ -95,13 +95,12 @@ class OwnerList(ListAPI):
|
||||
if not search_match:
|
||||
continue
|
||||
|
||||
if is_active is not None:
|
||||
# Skip any users which do not match the required *is_active* value
|
||||
if (
|
||||
result.owner_type.name == 'user'
|
||||
and result.owner_id not in matching_user_ids
|
||||
):
|
||||
continue
|
||||
# Skip any users which do not match the required *is_active* value
|
||||
if is_active is not None and (
|
||||
result.owner_type.name == 'user'
|
||||
and result.owner_id not in matching_user_ids
|
||||
):
|
||||
continue
|
||||
|
||||
# If we get here, there is no reason *not* to include this result
|
||||
results.append(result)
|
||||
|
@ -34,9 +34,10 @@ logger = logging.getLogger('inventree')
|
||||
# string representation of a user
|
||||
def user_model_str(self):
|
||||
"""Function to override the default Django User __str__."""
|
||||
if get_global_setting('DISPLAY_FULL_NAMES', cache=True):
|
||||
if self.first_name or self.last_name:
|
||||
return f'{self.first_name} {self.last_name}'
|
||||
if (
|
||||
get_global_setting('DISPLAY_FULL_NAMES', cache=True) and self.first_name
|
||||
) or self.last_name:
|
||||
return f'{self.first_name} {self.last_name}'
|
||||
return self.username
|
||||
|
||||
|
||||
@ -421,19 +422,19 @@ class RuleSet(models.Model):
|
||||
|
||||
# Work out which roles touch the given table
|
||||
for role in cls.RULESET_NAMES:
|
||||
if table in cls.get_ruleset_models()[role]:
|
||||
if check_user_role(user, role, permission):
|
||||
return True
|
||||
if table in cls.get_ruleset_models()[role] and check_user_role(
|
||||
user, role, permission
|
||||
):
|
||||
return True
|
||||
|
||||
# Check for children models which inherits from parent role
|
||||
for parent, child in cls.RULESET_CHANGE_INHERIT:
|
||||
# Get child model name
|
||||
parent_child_string = f'{parent}_{child}'
|
||||
|
||||
if parent_child_string == table:
|
||||
# Check if parent role has change permission
|
||||
if check_user_role(user, parent, 'change'):
|
||||
return True
|
||||
# Check if parent role has change permission
|
||||
if parent_child_string == table and check_user_role(user, parent, 'change'):
|
||||
return True
|
||||
|
||||
# Print message instead of throwing an error
|
||||
name = getattr(user, 'name', user.pk)
|
||||
|
8
tasks.py
8
tasks.py
@ -628,9 +628,11 @@ def export_records(
|
||||
model_name = entry.get('model', None)
|
||||
|
||||
# Ignore any temporary settings (start with underscore)
|
||||
if model_name in ['common.inventreesetting', 'common.inventreeusersetting']:
|
||||
if entry['fields'].get('key', '').startswith('_'):
|
||||
continue
|
||||
if model_name in [
|
||||
'common.inventreesetting',
|
||||
'common.inventreeusersetting',
|
||||
] and entry['fields'].get('key', '').startswith('_'):
|
||||
continue
|
||||
|
||||
if model_name == 'auth.group':
|
||||
entry['fields']['permissions'] = []
|
||||
|
Loading…
Reference in New Issue
Block a user