refactor: remove blank lines after docstring (#5736)

There shouldn't be any blank lines after the function docstring.
Remove the blank lines to fix this issue.

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
This commit is contained in:
Matthias Mair 2023-10-17 22:28:57 +02:00 committed by GitHub
parent 158a209a0f
commit faac6b6bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
136 changed files with 0 additions and 757 deletions

View File

@ -30,7 +30,6 @@ class InvenTreeResource(ModelResource):
**kwargs
):
"""Override the default import_data_inner function to provide better error handling"""
if len(dataset) > self.MAX_IMPORT_ROWS:
raise ImportExportError(f"Dataset contains too many rows (max {self.MAX_IMPORT_ROWS})")
@ -71,7 +70,6 @@ class InvenTreeResource(ModelResource):
def get_fields(self, **kwargs):
"""Return fields, with some common exclusions"""
fields = super().get_fields(**kwargs)
fields_to_exclude = [

View File

@ -35,7 +35,6 @@ class InfoView(AjaxView):
def worker_pending_tasks(self):
"""Return the current number of outstanding background tasks"""
return OrmQ.objects.count()
def get(self, request, *args, **kwargs):
@ -256,7 +255,6 @@ class APISearchView(APIView):
def get_result_types(self):
"""Construct a list of search types we can return"""
import build.api
import company.api
import order.api
@ -279,7 +277,6 @@ class APISearchView(APIView):
def post(self, request, *args, **kwargs):
"""Perform search query against available models"""
data = request.data
results = {}

View File

@ -79,7 +79,6 @@ class InvenTreeConfig(AppConfig):
def start_background_tasks(self):
"""Start all background tests for InvenTree."""
logger.info("Starting background tasks...")
from django_q.models import Schedule
@ -140,7 +139,6 @@ class InvenTreeConfig(AppConfig):
def collect_tasks(self):
"""Collect all background tasks."""
for app_name, app in apps.app_configs.items():
if app_name == 'InvenTree':
continue

View File

@ -23,7 +23,6 @@ def to_list(value, delimiter=','):
However, the same setting may be specified via an environment variable,
using a comma delimited string!
"""
if type(value) in [list, tuple]:
return value
@ -70,7 +69,6 @@ def ensure_dir(path: Path) -> None:
If it does not exist, create it.
"""
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
@ -143,7 +141,6 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
"""
def try_typecasting(value, source: str):
"""Attempt to typecast the value"""
# Force 'list' of strings
if typecast is list:
value = to_list(value)
@ -201,13 +198,11 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
def get_boolean_setting(env_var=None, config_key=None, default_value=False):
"""Helper function for retrieving a boolean configuration setting"""
return is_true(get_setting(env_var, config_key, default_value))
def get_media_dir(create=True):
"""Return the absolute path for the 'media' directory (where uploaded files are stored)"""
md = get_setting('INVENTREE_MEDIA_ROOT', 'media_root')
if not md:
@ -223,7 +218,6 @@ def get_media_dir(create=True):
def get_static_dir(create=True):
"""Return the absolute path for the 'static' directory (where static files are stored)"""
sd = get_setting('INVENTREE_STATIC_ROOT', 'static_root')
if not sd:
@ -239,7 +233,6 @@ def get_static_dir(create=True):
def get_backup_dir(create=True):
"""Return the absolute path for the backup directory"""
bd = get_setting('INVENTREE_BACKUP_DIR', 'backup_dir')
if not bd:
@ -258,7 +251,6 @@ def get_plugin_file():
Note: It will be created if it does not already exist!
"""
# Check if the plugin.txt file (specifying required plugins) is specified
plugin_file = get_setting('INVENTREE_PLUGIN_FILE', 'plugin_file')
@ -283,7 +275,6 @@ def get_plugin_file():
def get_plugin_dir():
"""Returns the path of the custom plugins directory"""
return get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
@ -297,7 +288,6 @@ def get_secret_key():
C) Look for default key file "secret_key.txt"
D) Create "secret_key.txt" if it does not exist
"""
# Look for environment variable
if secret_key := get_setting('INVENTREE_SECRET_KEY', 'secret_key'):
logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover

View File

@ -15,7 +15,6 @@ logger = logging.getLogger('inventree')
def get_unit_registry():
"""Return a custom instance of the Pint UnitRegistry."""
global _unit_registry
# Cache the unit registry for speedier access
@ -30,7 +29,6 @@ def reload_unit_registry():
This function is called at startup, and whenever the database is updated.
"""
import time
t_start = time.time()
@ -84,7 +82,6 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
Returns:
The converted quantity, in the specified units
"""
original = str(value).strip()
# Ensure that the value is a string

View File

@ -52,7 +52,6 @@ def is_email_configured():
def send_email(subject, body, recipients, from_email=None, html_message=None):
"""Send an email with the specified subject and body, to the specified recipients list."""
if isinstance(recipients, str):
recipients = [recipients]

View File

@ -31,7 +31,6 @@ def log_error(path):
Arguments:
path: The 'path' (most likely a URL) associated with this error (optional)
"""
kind, info, data = sys.exc_info()
# Check if the error is on the ignore list

View File

@ -22,7 +22,6 @@ class InvenTreeExchange(SimpleExchangeBackend):
def get_rates(self, **kwargs) -> None:
"""Set the requested currency codes and get rates."""
from common.models import InvenTreeSetting
from plugin import registry
@ -74,7 +73,6 @@ class InvenTreeExchange(SimpleExchangeBackend):
@atomic
def update_rates(self, base_currency=None, **kwargs):
"""Call to update all exchange rates"""
backend, _ = ExchangeBackend.objects.update_or_create(name=self.name, defaults={"base_currency": base_currency})
if base_currency is None:

View File

@ -22,7 +22,6 @@ class InvenTreeRestURLField(RestURLField):
def __init__(self, **kwargs):
"""Update schemes."""
# Enforce 'max length' parameter in form validation
if 'max_length' not in kwargs:
kwargs['max_length'] = 200
@ -38,7 +37,6 @@ class InvenTreeURLField(models.URLField):
def __init__(self, **kwargs):
"""Initialization method for InvenTreeURLField"""
# Max length for InvenTreeURLField is set to 200
kwargs['max_length'] = 200
super().__init__(**kwargs)
@ -117,7 +115,6 @@ class InvenTreeMoneyField(MoneyField):
def __init__(self, *args, **kwargs):
"""Override initial values with the real info from database."""
kwargs = money_kwargs(**kwargs)
super().__init__(*args, **kwargs)
@ -150,7 +147,6 @@ class DatePickerFormField(forms.DateField):
def round_decimal(value, places, normalize=False):
"""Round value to the specified number of places."""
if type(value) in [Decimal, float]:
value = round(value, places)
@ -187,7 +183,6 @@ class RoundingDecimalField(models.DecimalField):
def formfield(self, **kwargs):
"""Return a Field instance for this field."""
kwargs['form_class'] = RoundingDecimalFormField
return super().formfield(**kwargs)

View File

@ -15,7 +15,6 @@ class InvenTreeSearchFilter(filters.SearchFilter):
The following query params are available to 'augment' the search (in decreasing order of priority)
- search_regex: If True, search is performed on 'regex' comparison
"""
regex = InvenTree.helpers.str2bool(request.query_params.get('search_regex', False))
search_fields = super().get_search_fields(view, request)
@ -36,7 +35,6 @@ class InvenTreeSearchFilter(filters.SearchFilter):
Depending on the request parameters, we may "augment" these somewhat
"""
whole = InvenTree.helpers.str2bool(request.query_params.get('search_whole', False))
terms = []

View File

@ -11,7 +11,6 @@ def parse_format_string(fmt_string: str) -> dict:
Returns a dict object which contains structured information about the format groups
"""
groups = string.Formatter().parse(fmt_string)
info = {}
@ -62,7 +61,6 @@ def construct_format_regex(fmt_string: str) -> str:
Raises:
ValueError: Format string is invalid
"""
pattern = "^"
for group in string.Formatter().parse(fmt_string):
@ -121,7 +119,6 @@ def validate_string(value: str, fmt_string: str) -> str:
Raises:
ValueError: The provided format string is invalid
"""
pattern = construct_format_regex(fmt_string)
result = re.match(pattern, value)
@ -145,7 +142,6 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str:
NameError: named value does not exist in the format string
IndexError: named value could not be found in the provided entry
"""
info = parse_format_string(fmt_string)
if name not in info.keys():

View File

@ -176,7 +176,6 @@ class CustomLoginForm(LoginForm):
First check that:
- A valid user has been supplied
"""
if not self.user:
# No user supplied - redirect to the login page
return HttpResponseRedirect(reverse('account_login'))
@ -313,7 +312,6 @@ class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, Default
def get_email_confirmation_url(self, request, emailconfirmation):
"""Construct the email confirmation url"""
from InvenTree.helpers_model import construct_absolute_url
url = super().get_email_confirmation_url(request, emailconfirmation)

View File

@ -51,7 +51,6 @@ def constructPathString(path, max_chars=250):
path: A list of strings e.g. ['path', 'to', 'location']
max_chars: Maximum number of characters
"""
pathstring = '/'.join(path)
# Replace middle elements to limit the pathstring
@ -93,7 +92,6 @@ def getBlankThumbnail():
def getLogoImage(as_file=False, custom=True):
"""Return the InvenTree logo image, or a custom logo if available."""
"""Return the path to the logo-file."""
if custom and settings.CUSTOM_LOGO:
@ -122,7 +120,6 @@ def getLogoImage(as_file=False, custom=True):
def getSplashScreen(custom=True):
"""Return the InvenTree splash screen, or a custom splash if available"""
static_storage = StaticFilesStorage()
if custom and settings.CUSTOM_SPLASH:
@ -338,7 +335,6 @@ def MakeBarcode(cls_name, object_pk: int, object_data=None, **kwargs):
Returns:
json string of the supplied data plus some other data
"""
if object_data is None:
object_data = {}
@ -415,7 +411,6 @@ def increment_serial_number(serial: str):
Returns:
incremented value, or None if incrementing could not be performed.
"""
from plugin.registry import registry
# Ensure we start with a string value
@ -452,7 +447,6 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
expected_quantity: The number of (unique) serial numbers we expect
starting_value: Provide a starting value for the sequence (or None)
"""
if starting_value is None:
starting_value = increment_serial_number(None)
@ -724,7 +718,6 @@ def strip_html_tags(value: str, raise_error=True, field_name=None):
If raise_error is True, a ValidationError will be thrown if HTML tags are detected
"""
cleaned = clean(
value,
strip=True,
@ -756,7 +749,6 @@ def strip_html_tags(value: str, raise_error=True, field_name=None):
def remove_non_printable_characters(value: str, remove_newline=True, remove_ascii=True, remove_unicode=True):
"""Remove non-printable / control characters from the provided string"""
cleaned = value
if remove_ascii:
@ -787,7 +779,6 @@ def hash_barcode(barcode_data):
We first remove any non-printable characters from the barcode data,
as some browsers have issues scanning characters in.
"""
barcode_data = str(barcode_data).strip()
barcode_data = remove_non_printable_characters(barcode_data)
@ -813,7 +804,6 @@ def get_objectreference(obj, type_ref: str = 'content_type', object_ref: str = '
The method name must always be the name of the field prefixed by 'get_'
"""
model_cls = getattr(obj, type_ref)
obj_id = getattr(obj, object_ref)

View File

@ -41,7 +41,6 @@ def construct_absolute_url(*arg, **kwargs):
2. If the InvenTree setting INVENTREE_BASE_URL is set, use that
3. Otherwise, use the current request URL (if available)
"""
relative_url = '/'.join(arg)
# If a site URL is provided, use that
@ -96,7 +95,6 @@ def download_image_from_url(remote_url, timeout=2.5):
ValueError: Server responded with invalid 'Content-Length' value
TypeError: Response is not a valid image
"""
# Check that the provided URL at least looks valid
validator = URLValidator()
validator(remote_url)
@ -180,7 +178,6 @@ def render_currency(money, decimal_places=None, currency=None, include_symbol=Tr
min_decimal_places: The minimum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES_MIN setting.
max_decimal_places: The maximum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
"""
if money in [None, '']:
return '-'
@ -234,7 +231,6 @@ def getModelsWithMixin(mixin_class) -> list:
Returns:
List of models that inherit from the given mixin class
"""
from django.contrib.contenttypes.models import ContentType
db_models = [x.model_class() for x in ContentType.objects.all() if x is not None]

View File

@ -12,7 +12,6 @@ from django.utils.translation import override as lang_over
def render_file(file_name, source, target, locales, ctx):
"""Renders a file into all provided locales."""
for locale in locales:
# Enforce lower-case for locale names

View File

@ -125,7 +125,6 @@ class Check2FAMiddleware(BaseRequire2FAMiddleware):
"""Check if user is required to have MFA enabled."""
def require_2fa(self, request):
"""Use setting to check if MFA should be enforced for frontend page."""
from common.models import InvenTreeSetting
try:

View File

@ -49,7 +49,6 @@ class CleanMixin():
Ref: https://github.com/mozilla/bleach/issues/192
"""
cleaned = strip_html_tags(data, field_name=field)
# By default, newline characters are removed
@ -93,7 +92,6 @@ class CleanMixin():
Returns:
dict: Provided data Sanitized; still in the same order.
"""
clean_data = {}
for k, v in data.items():

View File

@ -73,7 +73,6 @@ class MetadataMixin(models.Model):
def validate_metadata(self):
"""Validate the metadata field."""
# Ensure that the 'metadata' field is a valid dict object
if self.metadata is None:
self.metadata = {}
@ -202,7 +201,6 @@ class ReferenceIndexingMixin(models.Model):
This is defined by a global setting object, specified by the REFERENCE_PATTERN_SETTING attribute
"""
# By default, we return an empty string
if cls.REFERENCE_PATTERN_SETTING is None:
return ''
@ -218,7 +216,6 @@ class ReferenceIndexingMixin(models.Model):
- Returns a python dict object which contains the context data for formatting the reference string.
- The default implementation provides some default context information
"""
return {
'ref': cls.get_next_reference(),
'date': datetime.now(),
@ -230,7 +227,6 @@ class ReferenceIndexingMixin(models.Model):
In practice, this means the item with the highest reference value
"""
query = cls.objects.all().order_by('-reference_int', '-pk')
if query.exists():
@ -241,7 +237,6 @@ class ReferenceIndexingMixin(models.Model):
@classmethod
def get_next_reference(cls):
"""Return the next available reference value for this particular class."""
# Find the "most recent" item
latest = cls.get_most_recent_item()
@ -270,7 +265,6 @@ class ReferenceIndexingMixin(models.Model):
@classmethod
def generate_reference(cls):
"""Generate the next 'reference' field based on specified pattern"""
fmt = cls.get_reference_pattern()
ctx = cls.get_reference_context()
@ -310,7 +304,6 @@ class ReferenceIndexingMixin(models.Model):
@classmethod
def validate_reference_pattern(cls, pattern):
"""Ensure that the provided pattern is valid"""
ctx = cls.get_reference_context()
try:
@ -336,7 +329,6 @@ class ReferenceIndexingMixin(models.Model):
@classmethod
def validate_reference_field(cls, value):
"""Check that the provided 'reference' value matches the requisite pattern"""
pattern = cls.get_reference_pattern()
value = str(value).strip()
@ -368,7 +360,6 @@ class ReferenceIndexingMixin(models.Model):
If we cannot extract using the pattern for some reason, fallback to the entire reference
"""
try:
# Extract named group based on provided pattern
reference = InvenTree.format.extract_named_group('ref', reference, cls.get_reference_pattern())
@ -390,7 +381,6 @@ class ReferenceIndexingMixin(models.Model):
def extract_int(reference, clip=0x7fffffff, allow_negative=False):
"""Extract an integer out of reference."""
# Default value if we cannot convert to an integer
ref_int = 0
@ -571,7 +561,6 @@ class InvenTreeAttachment(models.Model):
- If the attachment is a link to an external resource, return the link
- If the attachment is an uploaded file, return the fully qualified media URL
"""
if self.link:
return self.link
@ -608,7 +597,6 @@ class InvenTreeTree(MPTTModel):
Note that a 'unique_together' requirement for ('name', 'parent') is insufficient,
as it ignores cases where parent=None (i.e. top-level items)
"""
super().validate_unique(exclude)
results = self.__class__.objects.filter(
@ -631,7 +619,6 @@ class InvenTreeTree(MPTTModel):
def save(self, *args, **kwargs):
"""Custom save method for InvenTreeTree abstract model"""
try:
super().save(*args, **kwargs)
except InvalidMove:
@ -769,7 +756,6 @@ class InvenTreeTree(MPTTModel):
name: <name>,
}
"""
return [
{
'pk': item.pk,
@ -839,13 +825,11 @@ class InvenTreeBarcodeMixin(models.Model):
@classmethod
def barcode_model_type(cls):
"""Return the model 'type' for creating a custom QR code."""
# By default, use the name of the class
return cls.__name__.lower()
def format_barcode(self, **kwargs):
"""Return a JSON string for formatting a QR code for this model instance."""
return InvenTree.helpers.MakeBarcode(
self.__class__.barcode_model_type(),
self.pk,
@ -855,18 +839,15 @@ class InvenTreeBarcodeMixin(models.Model):
@property
def barcode(self):
"""Format a minimal barcode string (e.g. for label printing)"""
return self.format_barcode(brief=True)
@classmethod
def lookup_barcode(cls, barcode_hash):
"""Check if a model instance exists with the specified third-party barcode hash."""
return cls.objects.filter(barcode_hash=barcode_hash).first()
def assign_barcode(self, barcode_hash=None, barcode_data=None, raise_error=True, save=True):
"""Assign an external (third-party) barcode to this object."""
# Must provide either barcode_hash or barcode_data
if barcode_hash is None and barcode_data is None:
raise ValueError("Provide either 'barcode_hash' or 'barcode_data'")
@ -894,7 +875,6 @@ class InvenTreeBarcodeMixin(models.Model):
def unassign_barcode(self):
"""Unassign custom barcode from this model"""
self.barcode_data = ''
self.barcode_hash = ''
@ -919,7 +899,6 @@ def after_error_logged(sender, instance: Error, created: bool, **kwargs):
- Send a UI notification to all users with staff status
"""
if created:
try:
import common.models

View File

@ -9,7 +9,6 @@ import users.models
def get_model_for_view(view, raise_error=True):
"""Attempt to introspect the 'model' type for an API view"""
if hasattr(view, 'get_permission_model'):
return view.get_permission_model()

View File

@ -55,7 +55,6 @@ def sanitize_svg(file_data, strip: bool = True, elements: str = ALLOWED_ELEMENTS
Returns:
str: Sanitzied SVG file.
"""
# Handle byte-encoded data
if isinstance(file_data, bytes):
file_data = file_data.decode('utf-8')

View File

@ -17,7 +17,6 @@ logger = logging.getLogger('inventree')
def default_sentry_dsn():
"""Return the default Sentry.io DSN for InvenTree"""
return 'https://3928ccdba1d34895abde28031fd00100@o378676.ingest.sentry.io/6494600'
@ -26,7 +25,6 @@ def sentry_ignore_errors():
These error types will *not* be reported to sentry.io.
"""
return [
Http404,
ValidationError,
@ -39,7 +37,6 @@ def sentry_ignore_errors():
def init_sentry(dsn, sample_rate, tags):
"""Initialize sentry.io error reporting"""
logger.info("Initializing sentry.io integration")
sentry_sdk.init(
@ -64,7 +61,6 @@ 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()):

View File

@ -43,7 +43,6 @@ class InvenTreeMoneySerializer(MoneyField):
def get_value(self, data):
"""Test that the returned amount is a valid Decimal."""
amount = super(DecimalField, self).get_value(data)
# Convert an empty string to None
@ -73,7 +72,6 @@ class InvenTreeCurrencySerializer(serializers.ChoiceField):
def __init__(self, *args, **kwargs):
"""Initialize the currency serializer"""
choices = currency_code_mappings()
allow_blank = kwargs.get('allow_blank', False) or kwargs.get('allow_null', False)
@ -197,7 +195,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
def create(self, validated_data):
"""Custom create method which supports field adjustment"""
initial_data = validated_data.copy()
# Remove any fields which do not exist on the model
@ -221,7 +218,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
In addition to running validators on the serializer fields,
this class ensures that the underlying model is also validated.
"""
# Run any native validation checks first (may raise a ValidationError)
data = super().run_validation(data)
@ -705,7 +701,6 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
def skip_create_fields(self):
"""Ensure the 'remote_image' field is skipped when creating a new instance"""
return [
'remote_image',
]
@ -724,7 +719,6 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
- Attempt to download the image and store it against this object instance
- Catches and re-throws any errors
"""
if not url:
return

View File

@ -31,7 +31,6 @@ class GenericOAuth2ApiConnectView(GenericOAuth2ApiLoginView):
def dispatch(self, request, *args, **kwargs):
"""Dispatch the connect request directly."""
# Override the request method be in connection mode
request.GET = request.GET.copy()
request.GET['process'] = 'connect'

View File

@ -89,7 +89,6 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
Note that this function creates some *hidden* global settings (designated with the _ prefix),
which are used to keep a running track of when the particular task was was last run.
"""
from common.models import InvenTreeSetting
from InvenTree.ready import isInTestMode
@ -146,7 +145,6 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
def record_task_attempt(task_name: str):
"""Record that a multi-day task has been attempted *now*"""
from common.models import InvenTreeSetting
logger.info("Logging task attempt for '%s'", task_name)
@ -156,7 +154,6 @@ def record_task_attempt(task_name: str):
def record_task_success(task_name: str):
"""Record that a multi-day task was successful *now*"""
from common.models import InvenTreeSetting
InvenTreeSetting.set_setting(f'_{task_name}_SUCCESS', datetime.now().isoformat(), None)
@ -168,7 +165,6 @@ def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs)
If workers are not running or force_sync flag
is set then the task is ran synchronously.
"""
try:
import importlib
@ -353,7 +349,6 @@ def delete_successful_tasks():
@scheduled_task(ScheduledTask.DAILY)
def delete_failed_tasks():
"""Delete failed task logs which are older than a specified period"""
try:
from django_q.models import Failure
@ -402,7 +397,6 @@ def delete_old_error_logs():
@scheduled_task(ScheduledTask.DAILY)
def delete_old_notifications():
"""Delete old notification logs"""
try:
from common.models import (InvenTreeSetting, NotificationEntry,
NotificationMessage)
@ -503,7 +497,6 @@ def update_exchange_rates(force: bool = False):
Arguments:
force: If True, force the update to run regardless of the last update time
"""
try:
from djmoney.contrib.exchange.models import Rate
@ -547,7 +540,6 @@ def update_exchange_rates(force: bool = False):
@scheduled_task(ScheduledTask.DAILY)
def run_backup():
"""Run the backup command."""
from common.models import InvenTreeSetting
if not InvenTreeSetting.get_setting('INVENTREE_BACKUP_ENABLE', False, cache=False):
@ -582,7 +574,6 @@ def check_for_migrations():
If the setting auto_update is enabled we will start updating.
"""
from common.models import InvenTreeSetting
from plugin import registry

View File

@ -16,7 +16,6 @@ class InvenTreeTemplateLoader(CachedLoader):
Any custom report or label templates will be forced to reload (without cache).
This ensures that generated PDF reports / labels are always up-to-date.
"""
# List of template patterns to skip cache for
skip_cache_dirs = [
os.path.abspath(os.path.join(settings.MEDIA_ROOT, 'report')),

View File

@ -267,7 +267,6 @@ class BulkDeleteTests(InvenTreeAPITestCase):
def test_errors(self):
"""Test that the correct errors are thrown"""
url = reverse('api-stock-test-result-list')
# DELETE without any of the required fields
@ -318,7 +317,6 @@ class SearchTests(InvenTreeAPITestCase):
def test_empty(self):
"""Test empty request"""
data = [
'',
None,
@ -331,7 +329,6 @@ class SearchTests(InvenTreeAPITestCase):
def test_results(self):
"""Test individual result types"""
response = self.post(
reverse('api-search'),
{
@ -374,7 +371,6 @@ class SearchTests(InvenTreeAPITestCase):
def test_permissions(self):
"""Test that users with insufficient permissions are handled correctly"""
# First, remove all roles
for ruleset in self.group.rule_sets.all():
ruleset.can_view = False

View File

@ -45,7 +45,6 @@ class ViewTests(InvenTreeTestCase):
def test_settings_page(self):
"""Test that the 'settings' page loads correctly"""
# Settings page loads
url = reverse('settings')
@ -122,7 +121,6 @@ class ViewTests(InvenTreeTestCase):
def test_url_login(self):
"""Test logging in via arguments"""
# Log out
self.client.logout()
response = self.client.get("/index/")

View File

@ -44,7 +44,6 @@ class ConversionTest(TestCase):
def test_prefixes(self):
"""Test inputs where prefixes are used"""
tests = {
"3": 3,
"3m": 3,
@ -78,7 +77,6 @@ class ConversionTest(TestCase):
def test_dimensionless_units(self):
"""Tests for 'dimensionless' unit quantities"""
# Test some dimensionless units
tests = {
'ea': 1,
@ -106,7 +104,6 @@ class ConversionTest(TestCase):
def test_invalid_units(self):
"""Test conversion with bad units"""
tests = {
'3': '10',
'13': '-?-',
@ -121,7 +118,6 @@ class ConversionTest(TestCase):
def test_invalid_values(self):
"""Test conversion of invalid inputs"""
inputs = [
'-x',
'1/0',
@ -140,7 +136,6 @@ class ConversionTest(TestCase):
def test_custom_units(self):
"""Tests for custom unit conversion"""
# Start with an empty set of units
CustomUnit.objects.all().delete()
InvenTree.conversion.reload_unit_registry()
@ -214,7 +209,6 @@ class FormatTest(TestCase):
def test_parse(self):
"""Tests for the 'parse_format_string' function"""
# Extract data from a valid format string
fmt = "PO-{abc:02f}-{ref:04d}-{date}-???"
@ -236,7 +230,6 @@ class FormatTest(TestCase):
def test_create_regex(self):
"""Test function for creating a regex from a format string"""
tests = {
"PO-123-{ref:04f}": r"^PO\-123\-(?P<ref>.+)$",
"{PO}-???-{ref}-{date}-22": r"^(?P<PO>.+)\-...\-(?P<ref>.+)\-(?P<date>.+)\-22$",
@ -249,7 +242,6 @@ class FormatTest(TestCase):
def test_validate_format(self):
"""Test that string validation works as expected"""
# These tests should pass
for value, pattern in {
"ABC-hello-123": "???-{q}-###",
@ -270,7 +262,6 @@ class FormatTest(TestCase):
def test_extract_value(self):
"""Test that we can extract named values based on a format string"""
# Simple tests based on a straight-forward format string
fmt = "PO-###-{ref:04d}"
@ -345,7 +336,6 @@ class TestHelpers(TestCase):
def test_absolute_url(self):
"""Test helper function for generating an absolute URL"""
base = "https://demo.inventree.org:12345"
InvenTreeSetting.set_setting('INVENTREE_BASE_URL', base, change_user=None)
@ -418,9 +408,7 @@ class TestHelpers(TestCase):
def test_logo_image(self):
"""Test for retrieving logo image"""
# By default, there is no custom logo provided
logo = helpers.getLogoImage()
self.assertEqual(logo, '/static/img/inventree.png')
@ -429,7 +417,6 @@ class TestHelpers(TestCase):
def test_download_image(self):
"""Test function for downloading image from remote URL"""
# Run check with a sequence of bad URLs
for url in [
"blog",
@ -489,7 +476,6 @@ class TestHelpers(TestCase):
def test_model_mixin(self):
"""Test the getModelsWithMixin function"""
from InvenTree.models import InvenTreeBarcodeMixin
models = InvenTree.helpers_model.getModelsWithMixin(InvenTreeBarcodeMixin)
@ -829,7 +815,6 @@ class CurrencyTests(TestCase):
def test_rates(self):
"""Test exchange rate update."""
# Initially, there will not be any exchange rate information
rates = Rate.objects.all()
@ -1083,7 +1068,6 @@ class TestOffloadTask(InvenTreeTestCase):
Ref: https://github.com/inventree/InvenTree/pull/3273
"""
offload_task(
'dummy_tasks.parts',
part=Part.objects.get(pk=1),
@ -1106,7 +1090,6 @@ class TestOffloadTask(InvenTreeTestCase):
def test_daily_holdoff(self):
"""Tests for daily task holdoff helper functions"""
import InvenTree.tasks
with self.assertLogs(logger='inventree', level='INFO') as cm:
@ -1162,7 +1145,6 @@ class BarcodeMixinTest(InvenTreeTestCase):
def test_barcode_model_type(self):
"""Test that the barcode_model_type property works for each class"""
from part.models import Part
from stock.models import StockItem, StockLocation
@ -1172,7 +1154,6 @@ class BarcodeMixinTest(InvenTreeTestCase):
def test_barcode_hash(self):
"""Test that the barcode hashing function provides correct results"""
# Test multiple values for the hashing function
# This is to ensure that the hash function is always "backwards compatible"
hashing_tests = {
@ -1208,7 +1189,6 @@ class MagicLoginTest(InvenTreeTestCase):
def test_generation(self):
"""Test that magic login tokens are generated correctly"""
# User does not exists
resp = self.client.post(reverse('sesame-generate'), {'email': 1})
self.assertEqual(resp.status_code, 200)

View File

@ -40,7 +40,6 @@ def reload_translation_stats():
def get_translation_percent(lang_code):
"""Return the translation percentage for the given language code"""
if _translation_stats is None:
reload_translation_stats()

View File

@ -143,7 +143,6 @@ class UserMixin:
def setUp(self):
"""Run setup for individual test methods"""
if self.auto_login:
self.client.login(username=self.username, password=self.password)
@ -156,7 +155,6 @@ class UserMixin:
assign_all: Set to True to assign *all* roles
group: The group to assign roles to (or leave None to use the group assigned to this class)
"""
if group is None:
group = cls.group
@ -207,7 +205,6 @@ class ExchangeRateMixin:
def generate_exchange_rates(self):
"""Helper function which generates some exchange rates to work with"""
rates = {
'AUD': 1.5,
'CAD': 1.7,
@ -271,7 +268,6 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
def checkResponse(self, url, method, expected_code, response):
"""Debug output for an unexpected response"""
# No expected code, return
if expected_code is None:
return
@ -318,7 +314,6 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
def post(self, url, data=None, expected_code=None, format='json'):
"""Issue a POST request."""
# Set default value - see B006
if data is None:
data = {}
@ -331,7 +326,6 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
def delete(self, url, data=None, expected_code=None, format='json'):
"""Issue a DELETE request."""
if data is None:
data = {}

View File

@ -17,7 +17,6 @@ import InvenTree.conversion
def validate_physical_units(unit):
"""Ensure that a given unit is a valid physical unit."""
unit = unit.strip()
# Ignore blank units
@ -69,7 +68,6 @@ class AllowedURLValidator(validators.URLValidator):
def validate_purchase_order_reference(value):
"""Validate the 'reference' field of a PurchaseOrder."""
from order.models import PurchaseOrder
# If we get to here, run the "default" validation routine
@ -78,7 +76,6 @@ def validate_purchase_order_reference(value):
def validate_sales_order_reference(value):
"""Validate the 'reference' field of a SalesOrder."""
from order.models import SalesOrder
# If we get to here, run the "default" validation routine
@ -140,7 +137,6 @@ def validate_part_name_format(value):
Make sure that each template container has a field of Part Model
"""
# Make sure that the field_name exists in Part model
from part.models import Part

View File

@ -178,5 +178,4 @@ def inventreeTarget():
def inventreePlatform():
"""Returns the platform for the instance."""
return platform.platform(aliased=True)

View File

@ -100,7 +100,6 @@ class BuildFilter(rest_filters.FilterSet):
def filter_has_project_code(self, queryset, name, value):
"""Filter by whether or not the order has a project code"""
if str2bool(value):
return queryset.exclude(project_code=None)
else:
@ -235,7 +234,6 @@ class BuildDetail(RetrieveUpdateDestroyAPI):
def destroy(self, request, *args, **kwargs):
"""Only allow deletion of a BuildOrder if the build status is CANCELLED"""
build = self.get_object()
if build.status != BuildStatus.CANCELLED:
@ -292,7 +290,6 @@ class BuildLineFilter(rest_filters.FilterSet):
def filter_allocated(self, queryset, name, value):
"""Filter by whether each BuildLine is fully allocated"""
if str2bool(value):
return queryset.filter(allocated__gte=F('quantity'))
else:
@ -309,7 +306,6 @@ class BuildLineFilter(rest_filters.FilterSet):
- The quantity available for each BuildLine
- The quantity allocated for each BuildLine
"""
flt = Q(quantity__lte=F('total_available_stock') + F('allocated'))
if str2bool(value):

View File

@ -348,7 +348,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
@property
def tracked_line_items(self):
"""Returns the "trackable" BOM lines for this BuildOrder."""
return self.build_lines.filter(bom_item__sub_part__trackable=True)
def has_tracked_line_items(self):
@ -358,7 +357,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
@property
def untracked_line_items(self):
"""Returns the "non trackable" BOM items for this BuildOrder."""
return self.build_lines.filter(bom_item__sub_part__trackable=False)
@property
@ -432,7 +430,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
def is_partially_allocated(self):
"""Test is this build order has any stock allocated against it"""
return self.allocated_stock.count() > 0
@property
@ -497,7 +494,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
- Completed count must meet the required quantity
- Untracked parts must be allocated
"""
if self.incomplete_count > 0:
return False
@ -780,7 +776,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
@transaction.atomic
def trim_allocated_stock(self):
"""Called after save to reduce allocated stock if the build order is now overallocated."""
# Only need to worry about untracked stock here
for build_line in self.untracked_line_items:
@ -817,7 +812,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
@transaction.atomic
def subtract_allocated_stock(self, user):
"""Called when the Build is marked as "complete", this function removes the allocated untracked items from stock."""
# Find all BuildItem objects which point to this build
items = self.allocated_stock.filter(
build_line__bom_item__sub_part__trackable=False
@ -839,7 +833,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
- Set the item status to "scrapped"
- Add a transaction entry to the stock item history
"""
if not output:
raise ValidationError(_("No build output specified"))
@ -1069,7 +1062,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
def unallocated_lines(self, tracked=None):
"""Returns a list of BuildLine objects which have not been fully allocated."""
lines = self.build_lines.all()
if tracked is True:
@ -1096,7 +1088,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
Returns:
True if the BuildOrder has been fully allocated, otherwise False
"""
lines = self.unallocated_lines(tracked=tracked)
return len(lines) == 0
@ -1109,7 +1100,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
To determine if the output has been fully allocated,
we need to test all "trackable" BuildLine objects
"""
for line in self.build_lines.filter(bom_item__sub_part__trackable=True):
# Grab all BuildItem objects which point to this output
allocations = BuildItem.objects.filter(
@ -1134,7 +1124,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
Returns:
True if any BuildLine has been over-allocated.
"""
for line in self.build_lines.all():
if line.is_overallocated():
return True
@ -1159,7 +1148,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
@transaction.atomic
def create_build_line_items(self, prevent_duplicates=True):
"""Create BuildLine objects for each BOM line in this BuildOrder."""
lines = []
bom_items = self.part.get_bom_items()
@ -1192,7 +1180,6 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
@transaction.atomic
def update_build_line_items(self):
"""Rebuild required quantity field for each BuildLine object"""
lines_to_update = []
for line in self.build_lines.all():
@ -1296,7 +1283,6 @@ class BuildLine(models.Model):
def allocated_quantity(self):
"""Calculate the total allocated quantity for this BuildLine"""
# Queryset containing all BuildItem objects allocated against this BuildLine
allocations = self.allocations.all()
@ -1312,7 +1298,6 @@ class BuildLine(models.Model):
def is_fully_allocated(self):
"""Return True if this BuildLine is fully allocated"""
if self.bom_item.consumable:
return True

View File

@ -129,7 +129,6 @@ class BuildSerializer(InvenTreeModelSerializer):
def validate_reference(self, reference):
"""Custom validation for the Build reference field"""
# Ensure the reference matches the required pattern
Build.validate_reference_field(reference)
@ -209,7 +208,6 @@ class BuildOutputQuantitySerializer(BuildOutputSerializer):
def validate(self, data):
"""Validate the serializer data"""
data = super().validate(data)
output = data.get('output')
@ -450,7 +448,6 @@ class BuildOutputScrapSerializer(serializers.Serializer):
def save(self):
"""Save the serializer to scrap the build outputs"""
build = self.context['build']
request = self.context['request']
data = self.validated_data
@ -625,7 +622,6 @@ class BuildCompleteSerializer(serializers.Serializer):
This is so we can determine (at run time) whether the build is ready to be completed.
"""
build = self.context['build']
return {
@ -1095,7 +1091,6 @@ class BuildLineSerializer(InvenTreeModelSerializer):
- available: Total stock available for allocation against this build line
- on_order: Total stock on order for this build line
"""
queryset = queryset.select_related(
'build', 'bom_item',
)

View File

@ -29,7 +29,6 @@ def update_build_order_lines(bom_item_pk: int):
This task is triggered when a BomItem is created or updated.
"""
logger.info("Updating build order lines for BomItem %s", bom_item_pk)
bom_item = part_models.BomItem.objects.filter(pk=bom_item_pk).first()
@ -156,7 +155,6 @@ def check_build_stock(build: build.models.Build):
def notify_overdue_build_order(bo: build.models.Build):
"""Notify appropriate users that a Build has just become 'overdue'"""
targets = []
if bo.issued_by:
@ -202,7 +200,6 @@ def check_overdue_build_orders():
- Look at the 'target_date' of any outstanding BuildOrder objects
- If the 'target_date' expired *yesterday* then the order is just out of date
"""
yesterday = datetime.now().date() - timedelta(days=1)
overdue_orders = build.models.Build.objects.filter(

View File

@ -279,7 +279,6 @@ class BuildTest(BuildAPITest):
def test_delete(self):
"""Test that we can delete a BuildOrder via the API"""
bo = Build.objects.get(pk=1)
url = reverse('api-build-detail', kwargs={'pk': bo.pk})
@ -684,9 +683,7 @@ class BuildAllocationTest(BuildAPITest):
def test_invalid_bom_item(self):
"""Test by passing an invalid BOM item."""
# Find the right (in this case, wrong) BuildLine instance
si = StockItem.objects.get(pk=11)
lines = self.build.build_lines.all()
@ -718,7 +715,6 @@ class BuildAllocationTest(BuildAPITest):
This should result in creation of a new BuildItem object
"""
# Find the correct BuildLine
si = StockItem.objects.get(pk=2)
@ -758,7 +754,6 @@ class BuildAllocationTest(BuildAPITest):
This should increment the quantity of the existing BuildItem object
"""
# Find the correct BuildLine
si = StockItem.objects.get(pk=2)
@ -875,7 +870,6 @@ class BuildOverallocationTest(BuildAPITest):
def test_setup(self):
"""Validate expected state after set-up."""
self.assertEqual(self.build.incomplete_outputs.count(), 0)
self.assertEqual(self.build.complete_outputs.count(), 1)
self.assertEqual(self.build.completed, self.build.quantity)
@ -1040,7 +1034,6 @@ class BuildOutputScrapTest(BuildAPITest):
def scrap(self, build_id, data, expected_code=None):
"""Helper method to POST to the scrap API"""
url = reverse('api-build-output-scrap', kwargs={'pk': build_id})
response = self.post(url, data, expected_code=expected_code)
@ -1049,7 +1042,6 @@ class BuildOutputScrapTest(BuildAPITest):
def test_invalid_scraps(self):
"""Test that invalid scrap attempts are rejected"""
# Test with missing required fields
response = self.scrap(1, {}, expected_code=400)
@ -1113,7 +1105,6 @@ class BuildOutputScrapTest(BuildAPITest):
def test_valid_scraps(self):
"""Test that valid scrap attempts succeed"""
# Create a build output
build = Build.objects.get(pk=1)

View File

@ -45,7 +45,6 @@ class BuildTestBase(TestCase):
- 7 x output_2
"""
super().setUpTestData()
# Create a base "Part"
@ -145,7 +144,6 @@ class BuildTest(BuildTestBase):
def test_ref_int(self):
"""Test the "integer reference" field used for natural sorting"""
# Set build reference to new value
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref}-???', change_user=None)
@ -174,9 +172,7 @@ class BuildTest(BuildTestBase):
def test_ref_validation(self):
"""Test that the reference field validation works as expected"""
# Default reference pattern = 'BO-{ref:04d}
# These patterns should fail
for ref in [
'BO-1234x',
@ -223,7 +219,6 @@ class BuildTest(BuildTestBase):
def test_next_ref(self):
"""Test that the next reference is automatically generated"""
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'XYZ-{ref:06d}', change_user=None)
build = Build.objects.create(
@ -250,7 +245,6 @@ class BuildTest(BuildTestBase):
def test_init(self):
"""Perform some basic tests before we start the ball rolling"""
self.assertEqual(StockItem.objects.count(), 10)
# Build is PENDING
@ -272,7 +266,6 @@ class BuildTest(BuildTestBase):
def test_build_item_clean(self):
"""Ensure that dodgy BuildItem objects cannot be created"""
stock = StockItem.objects.create(part=self.assembly, quantity=99)
# Create a BuiltItem which points to an invalid StockItem
@ -299,7 +292,6 @@ class BuildTest(BuildTestBase):
def test_duplicate_bom_line(self):
"""Try to add a duplicate BOM item - it should be allowed"""
BomItem.objects.create(
part=self.assembly,
sub_part=self.sub_part_1,
@ -313,7 +305,6 @@ class BuildTest(BuildTestBase):
output: StockItem object (or None)
allocations: Map of {StockItem: quantity}
"""
items_to_create = []
for item, quantity in allocations.items():
@ -335,7 +326,6 @@ class BuildTest(BuildTestBase):
def test_partial_allocation(self):
"""Test partial allocation of stock"""
# Fully allocate tracked stock against build output 1
self.allocate_stock(
self.output_1,
@ -409,7 +399,6 @@ class BuildTest(BuildTestBase):
def test_overallocation_and_trim(self):
"""Test overallocation of stock and trim function"""
# Fully allocate tracked stock (not eligible for trimming)
self.allocate_stock(
self.output_1,
@ -484,9 +473,7 @@ class BuildTest(BuildTestBase):
def test_cancel(self):
"""Test cancellation of the build"""
# TODO
"""
self.allocate_stock(50, 50, 200, self.output_1)
self.build.cancel_build(None)
@ -497,7 +484,6 @@ class BuildTest(BuildTestBase):
def test_complete(self):
"""Test completion of a build output"""
self.stock_1_1.quantity = 1000
self.stock_1_1.save()
@ -567,7 +553,6 @@ class BuildTest(BuildTestBase):
def test_overdue_notification(self):
"""Test sending of notifications when a build order is overdue."""
self.build.target_date = datetime.now().date() - timedelta(days=1)
self.build.save()
@ -583,7 +568,6 @@ class BuildTest(BuildTestBase):
def test_new_build_notification(self):
"""Test that a notification is sent when a new build is created"""
Build.objects.create(
reference='BO-9999',
title='Some new build',
@ -609,7 +593,6 @@ class BuildTest(BuildTestBase):
def test_metadata(self):
"""Unit tests for the metadata field."""
# Make sure a BuildItem exists before trying to run this test
b = BuildItem(stock_item=self.stock_1_2, build_line=self.line_1, install_into=self.output_1, quantity=10)
b.save()
@ -664,7 +647,6 @@ class AutoAllocationTests(BuildTestBase):
A "fully auto" allocation should allocate *all* of these stock items to the build
"""
# No build item allocations have been made against the build
self.assertEqual(self.build.allocated_stock.count(), 0)
@ -717,7 +699,6 @@ class AutoAllocationTests(BuildTestBase):
def test_fully_auto(self):
"""We should be able to auto-allocate against a build in a single go"""
self.build.auto_allocate_stock(
interchangeable=True,
substitutes=True,

View File

@ -111,7 +111,6 @@ class TestReferencePatternMigration(MigratorTestCase):
def prepare(self):
"""Create some initial data prior to migration"""
Setting = self.old_state.apps.get_model('common', 'inventreesetting')
# Create a custom existing prefix so we can confirm the operation is working
@ -141,7 +140,6 @@ class TestReferencePatternMigration(MigratorTestCase):
def test_reference_migration(self):
"""Test that the reference fields have been correctly updated"""
Build = self.new_state.apps.get_model('build', 'build')
for build in Build.objects.all():
@ -170,7 +168,6 @@ class TestBuildLineCreation(MigratorTestCase):
def prepare(self):
"""Create data to work with"""
# Model references
Part = self.old_state.apps.get_model('part', 'part')
BomItem = self.old_state.apps.get_model('part', 'bomitem')
@ -235,7 +232,6 @@ class TestBuildLineCreation(MigratorTestCase):
def test_build_line_creation(self):
"""Test that the BuildLine objects have been created correctly"""
Build = self.new_state.apps.get_model('build', 'build')
BomItem = self.new_state.apps.get_model('part', 'bomitem')
BuildLine = self.new_state.apps.get_model('build', 'buildline')

View File

@ -3,7 +3,6 @@
def generate_next_build_reference():
"""Generate the next available BuildOrder reference"""
from build.models import Build
return Build.generate_reference()
@ -11,7 +10,6 @@ def generate_next_build_reference():
def validate_build_order_reference_pattern(pattern):
"""Validate the BuildOrder reference 'pattern' setting"""
from build.models import Build
Build.validate_reference_pattern(pattern)
@ -19,7 +17,6 @@ def validate_build_order_reference_pattern(pattern):
def validate_build_order_reference(value):
"""Validate that the BuildOrder reference field matches the required pattern."""
from build.models import Build
# If we get to here, run the "default" validation routine

View File

@ -113,7 +113,6 @@ class CurrencyExchangeView(APIView):
def get(self, request, format=None):
"""Return information on available currency conversions"""
# Extract a list of all available rates
try:
rates = Rate.objects.all()
@ -157,7 +156,6 @@ class CurrencyRefreshView(APIView):
def post(self, request, *args, **kwargs):
"""Performing a POST request will update currency exchange rates"""
from InvenTree.tasks import update_exchange_rates
update_exchange_rates(force=True)
@ -194,7 +192,6 @@ class GlobalSettingsList(SettingsList):
def list(self, request, *args, **kwargs):
"""Ensure all global settings are created"""
common.models.InvenTreeSetting.build_default_values()
return super().list(request, *args, **kwargs)
@ -253,7 +250,6 @@ class UserSettingsList(SettingsList):
def list(self, request, *args, **kwargs):
"""Ensure all user settings are created"""
common.models.InvenTreeUserSetting.build_default_values(user=request.user)
return super().list(request, *args, **kwargs)
@ -385,7 +381,6 @@ class NotificationList(NotificationMessageMixin, BulkDeleteMixin, ListAPI):
def filter_delete_queryset(self, queryset, request):
"""Ensure that the user can only delete their *own* notifications"""
queryset = queryset.filter(user=request.user)
return queryset

View File

@ -84,7 +84,6 @@ class BaseURLValidator(URLValidator):
def __init__(self, schemes=None, **kwargs):
"""Custom init routine"""
super().__init__(schemes, **kwargs)
# Override default host_re value - allow optional tld regex
@ -204,7 +203,6 @@ class BaseInvenTreeSetting(models.Model):
If a particular setting is not present, create it with the default value
"""
cache_key = f"BUILD_DEFAULT_VALUES:{str(cls.__name__)}"
if InvenTree.helpers.str2bool(cache.get(cache_key, False)):
@ -255,7 +253,6 @@ class BaseInvenTreeSetting(models.Model):
def save_to_cache(self):
"""Save this setting object to cache"""
ckey = self.cache_key
# skip saving to cache if no pk is set
@ -283,7 +280,6 @@ class BaseInvenTreeSetting(models.Model):
- The unique KEY string
- Any key:value kwargs associated with the particular setting type (e.g. user-id)
"""
key = f"{str(cls.__name__)}:{setting_key}"
for k, v in kwargs.items():
@ -992,7 +988,6 @@ def validate_email_domains(setting):
def currency_exchange_plugins():
"""Return a set of plugin choices which can be used for currency exchange"""
try:
from plugin import registry
plugs = registry.with_mixin('currencyexchange', active=True)
@ -1006,7 +1001,6 @@ def currency_exchange_plugins():
def update_exchange_rates(setting):
"""Update exchange rates when base currency is changed"""
if InvenTree.ready.isImportingData():
return
@ -1018,7 +1012,6 @@ def update_exchange_rates(setting):
def reload_plugin_registry(setting):
"""When a core plugin setting is changed, reload the plugin registry"""
from plugin import registry
logger.info("Reloading plugin registry due to change in setting '%s'", setting.key)
@ -2891,7 +2884,6 @@ class NewsFeedEntry(models.Model):
def rename_notes_image(instance, filename):
"""Function for renaming uploading image file. Will store in the 'notes' directory."""
fname = os.path.basename(filename)
return os.path.join('notes', fname)
@ -2936,7 +2928,6 @@ class CustomUnit(models.Model):
def clean(self):
"""Validate that the provided custom unit is indeed valid"""
super().clean()
from InvenTree.conversion import get_unit_registry
@ -2994,7 +2985,6 @@ class CustomUnit(models.Model):
@receiver(post_delete, sender=CustomUnit, dispatch_uid='custom_unit_deleted')
def after_custom_unit_updated(sender, instance, **kwargs):
"""Callback when a custom unit is updated or deleted"""
# Force reload of the unit registry
from InvenTree.conversion import reload_unit_registry
reload_unit_registry()

View File

@ -242,7 +242,6 @@ class UIMessageNotification(SingleNotificationMethod):
def get_targets(self):
"""Only send notifications for active users"""
return [target for target in self.targets if target.is_active]
def send(self, target):

View File

@ -192,7 +192,6 @@ class NotificationMessageSerializer(InvenTreeModelSerializer):
def get_target(self, obj):
"""Function to resolve generic object reference to target."""
target = get_objectreference(obj, 'target_content_type', 'target_object_id')
if target and 'link' not in target:

View File

@ -12,7 +12,6 @@ logger = logging.getLogger('inventree')
def currency_code_default():
"""Returns the default currency code (or USD if not specified)"""
from common.models import InvenTreeSetting
cached_value = cache.get('currency_code_default', '')

View File

@ -268,7 +268,6 @@ class SettingsTest(InvenTreeTestCase):
def test_global_setting_caching(self):
"""Test caching operations for the global settings class"""
key = 'PART_NAME_FORMAT'
cache_key = InvenTreeSetting.create_cache_key(key)
@ -290,7 +289,6 @@ class SettingsTest(InvenTreeTestCase):
def test_user_setting_caching(self):
"""Test caching operation for the user settings class"""
cache.clear()
# Generate a number of new users
@ -610,7 +608,6 @@ class NotificationUserSettingsApiTest(InvenTreeAPITestCase):
def test_setting(self):
"""Test the string name for NotificationUserSetting."""
NotificationUserSetting.set_setting('NOTIFICATION_METHOD_MAIL', True, change_user=self.user, user=self.user)
test_setting = NotificationUserSetting.get_setting_object('NOTIFICATION_METHOD_MAIL', user=self.user)
self.assertEqual(str(test_setting), 'NOTIFICATION_METHOD_MAIL (for testuser): True')
@ -823,7 +820,6 @@ class NotificationTest(InvenTreeAPITestCase):
def test_api_list(self):
"""Test list URL."""
url = reverse('api-notifications-list')
self.get(url, expected_code=200)
@ -843,7 +839,6 @@ class NotificationTest(InvenTreeAPITestCase):
def test_bulk_delete(self):
"""Tests for bulk deletion of user notifications"""
from error_report.models import Error
# Create some notification messages by throwing errors
@ -1019,7 +1014,6 @@ class CurrencyAPITests(InvenTreeAPITestCase):
def test_exchange_endpoint(self):
"""Test that the currency exchange endpoint works as expected"""
response = self.get(reverse('api-currency-exchange'), expected_code=200)
self.assertIn('base_currency', response.data)
@ -1027,7 +1021,6 @@ class CurrencyAPITests(InvenTreeAPITestCase):
def test_refresh_endpoint(self):
"""Call the 'refresh currencies' endpoint"""
from djmoney.contrib.exchange.models import Rate
# Delete any existing exchange rate data
@ -1053,7 +1046,6 @@ class NotesImageTest(InvenTreeAPITestCase):
def test_invalid_files(self):
"""Test that invalid files are rejected."""
n = NotesImage.objects.count()
# Test upload of a simple text file
@ -1085,7 +1077,6 @@ class NotesImageTest(InvenTreeAPITestCase):
def test_valid_image(self):
"""Test upload of a valid image file"""
n = NotesImage.objects.count()
# Construct a simple image file
@ -1132,13 +1123,11 @@ class ProjectCodesTest(InvenTreeAPITestCase):
def test_list(self):
"""Test that the list endpoint works as expected"""
response = self.get(self.url, expected_code=200)
self.assertEqual(len(response.data), ProjectCode.objects.count())
def test_delete(self):
"""Test we can delete a project code via the API"""
n = ProjectCode.objects.count()
# Get the first project code
@ -1155,7 +1144,6 @@ class ProjectCodesTest(InvenTreeAPITestCase):
def test_duplicate_code(self):
"""Test that we cannot create two project codes with the same code"""
# Create a new project code
response = self.post(
self.url,
@ -1170,7 +1158,6 @@ class ProjectCodesTest(InvenTreeAPITestCase):
def test_write_access(self):
"""Test that non-staff users have read-only access"""
# By default user has staff access, can create a new project code
response = self.post(
self.url,
@ -1240,13 +1227,11 @@ class CustomUnitAPITest(InvenTreeAPITestCase):
def test_list(self):
"""Test API list functionality"""
response = self.get(self.url, expected_code=200)
self.assertEqual(len(response.data), CustomUnit.objects.count())
def test_edit(self):
"""Test edit permissions for CustomUnit model"""
unit = CustomUnit.objects.first()
# Try to edit without permission
@ -1278,7 +1263,6 @@ class CustomUnitAPITest(InvenTreeAPITestCase):
def test_validation(self):
"""Test that validation works as expected"""
unit = CustomUnit.objects.first()
self.user.is_staff = True

View File

@ -382,7 +382,6 @@ class SupplierPartList(ListCreateDestroyAPIView):
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint"""
# Do we wish to include extra detail?
try:
params = self.request.query_params
@ -489,7 +488,6 @@ class SupplierPriceBreakList(ListCreateAPI):
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint"""
try:
params = self.request.query_params

View File

@ -160,7 +160,6 @@ class Company(InvenTreeNotesMixin, MetadataMixin, models.Model):
This property exists for backwards compatibility
"""
addr = self.primary_address
return str(addr) if addr is not None else None
@ -287,7 +286,6 @@ class Address(models.Model):
def __init__(self, *args, **kwargs):
"""Custom init function"""
super().__init__(*args, **kwargs)
def __str__(self):
@ -312,7 +310,6 @@ class Address(models.Model):
- If this address is marked as "primary", ensure that all other addresses for this company are marked as non-primary
"""
others = list(Address.objects.filter(company=self.company).exclude(pk=self.pk).all())
# If this is the *only* address for this company, make it the primary one
@ -755,7 +752,6 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin
def base_quantity(self, quantity=1) -> Decimal:
"""Calculate the base unit quantiy for a given quantity."""
q = Decimal(quantity) * Decimal(self.pack_quantity_native)
q = round(q, 10).normalize()
@ -780,7 +776,6 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin
def update_available_quantity(self, quantity):
"""Update the available quantity for this SupplierPart"""
self.available = quantity
self.availability_updated = datetime.now()
self.save()
@ -918,7 +913,6 @@ class SupplierPriceBreak(common.models.PriceBreak):
@receiver(post_save, sender=SupplierPriceBreak, dispatch_uid='post_save_supplier_price_break')
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:
@ -928,7 +922,6 @@ def after_save_supplier_price(sender, instance, created, **kwargs):
@receiver(post_delete, sender=SupplierPriceBreak, dispatch_uid='post_delete_supplier_price_break')
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:

View File

@ -342,7 +342,6 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
def __init__(self, *args, **kwargs):
"""Initialize this serializer with extra detail fields as required"""
# Check if 'available' quantity was supplied
self.has_available_quantity = 'available' in kwargs.get('data', {})
@ -402,7 +401,6 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
Fields:
in_stock: Current stock quantity for each SupplierPart
"""
queryset = queryset.annotate(
in_stock=part.filters.annotate_total_stock()
)
@ -411,7 +409,6 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
def update(self, supplier_part, data):
"""Custom update functionality for the serializer"""
available = data.pop('available', None)
response = super().update(supplier_part, data)
@ -423,7 +420,6 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
def create(self, validated_data):
"""Extract manufacturer data and process ManufacturerPart."""
# Extract 'available' quantity from the serializer
available = validated_data.pop('available', None)
@ -468,7 +464,6 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
def __init__(self, *args, **kwargs):
"""Initialize this serializer with extra fields as required"""
supplier_detail = kwargs.pop('supplier_detail', False)
part_detail = kwargs.pop('part_detail', False)

View File

@ -20,7 +20,6 @@ class CompanyTest(InvenTreeAPITestCase):
@classmethod
def setUpTestData(cls):
"""Perform initialization for the unit test class"""
super().setUpTestData()
# Create some company objects to work with
@ -148,7 +147,6 @@ class ContactTest(InvenTreeAPITestCase):
@classmethod
def setUpTestData(cls):
"""Perform init for this test class"""
super().setUpTestData()
# Create some companies
@ -178,7 +176,6 @@ class ContactTest(InvenTreeAPITestCase):
def test_list(self):
"""Test company list API endpoint"""
# List all results
response = self.get(self.url, {}, expected_code=200)
@ -202,7 +199,6 @@ class ContactTest(InvenTreeAPITestCase):
def test_create(self):
"""Test that we can create a new Contact object via the API"""
n = Contact.objects.count()
company = Company.objects.first()
@ -232,7 +228,6 @@ class ContactTest(InvenTreeAPITestCase):
def test_edit(self):
"""Test that we can edit a Contact via the API"""
# Get the first contact
contact = Contact.objects.first()
# Use this contact in the tests
@ -268,7 +263,6 @@ class ContactTest(InvenTreeAPITestCase):
def test_delete(self):
"""Tests that we can delete a Contact via the API"""
# Get the last contact
contact = Contact.objects.first()
url = reverse('api-contact-detail', kwargs={'pk': contact.pk})
@ -292,7 +286,6 @@ class AddressTest(InvenTreeAPITestCase):
@classmethod
def setUpTestData(cls):
"""Perform initialization for this test class"""
super().setUpTestData()
cls.num_companies = 3
cls.num_addr = 3
@ -323,14 +316,12 @@ class AddressTest(InvenTreeAPITestCase):
def test_list(self):
"""Test listing all addresses without filtering"""
response = self.get(self.url, expected_code=200)
self.assertEqual(len(response.data), self.num_companies * self.num_addr)
def test_filter_list(self):
"""Test listing addresses filtered on company"""
company = Company.objects.first()
response = self.get(self.url, {'company': company.pk}, expected_code=200)
@ -339,7 +330,6 @@ class AddressTest(InvenTreeAPITestCase):
def test_create(self):
"""Test creating a new address"""
company = Company.objects.first()
self.post(self.url,
@ -360,7 +350,6 @@ class AddressTest(InvenTreeAPITestCase):
def test_get(self):
"""Test that objects are properly returned from a get"""
addr = Address.objects.first()
url = reverse('api-address-detail', kwargs={'pk': addr.pk})
@ -373,7 +362,6 @@ class AddressTest(InvenTreeAPITestCase):
def test_edit(self):
"""Test editing an object"""
addr = Address.objects.first()
url = reverse('api-address-detail', kwargs={'pk': addr.pk})
@ -402,7 +390,6 @@ class AddressTest(InvenTreeAPITestCase):
def test_delete(self):
"""Test deleting an object"""
addr = Address.objects.first()
url = reverse('api-address-detail', kwargs={'pk': addr.pk})
@ -567,7 +554,6 @@ class SupplierPartTest(InvenTreeAPITestCase):
def test_available(self):
"""Tests for updating the 'available' field"""
url = reverse('api-supplier-part-list')
# Should fail when sending an invalid 'available' field
@ -651,7 +637,6 @@ class CompanyMetadataAPITest(InvenTreeAPITestCase):
def metatester(self, apikey, model):
"""Generic tester"""
modeldata = model.objects.first()
# Useless test unless a model object is found
@ -680,7 +665,6 @@ class CompanyMetadataAPITest(InvenTreeAPITestCase):
def test_metadata(self):
"""Test all endpoints"""
for apikey, model in {
'api-manufacturer-part-metadata': ManufacturerPart,
'api-supplier-part-metadata': SupplierPart,

View File

@ -293,7 +293,6 @@ class TestAddressMigration(MigratorTestCase):
def prepare(self):
"""Set up some companies with addresses"""
Company = self.old_state.apps.get_model('company', 'company')
Company.objects.create(name='Company 1', address=self.short_l1)
@ -301,7 +300,6 @@ class TestAddressMigration(MigratorTestCase):
def test_address_migration(self):
"""Test database state after applying the migration"""
Address = self.new_state.apps.get_model('company', 'address')
Company = self.new_state.apps.get_model('company', 'company')
@ -329,7 +327,6 @@ class TestSupplierPartQuantity(MigratorTestCase):
def prepare(self):
"""Prepare a number of SupplierPart objects"""
Part = self.old_state.apps.get_model('part', 'part')
Company = self.old_state.apps.get_model('company', 'company')
SupplierPart = self.old_state.apps.get_model('company', 'supplierpart')
@ -356,7 +353,6 @@ class TestSupplierPartQuantity(MigratorTestCase):
def test_supplier_part_quantity(self):
"""Test that the supplier part quantity is correctly migrated."""
SupplierPart = self.new_state.apps.get_model('company', 'supplierpart')
for i, sp in enumerate(SupplierPart.objects.all()):

View File

@ -14,7 +14,6 @@ class SupplierPartPackUnitsTests(InvenTreeTestCase):
def test_pack_quantity_dimensionless(self):
"""Test valid values for the 'pack_quantity' field"""
# Create a part without units (dimensionless)
part = Part.objects.create(name='Test Part', description='Test part description', component=True)
@ -59,7 +58,6 @@ class SupplierPartPackUnitsTests(InvenTreeTestCase):
def test_pack_quantity(self):
"""Test pack_quantity for a part with a specified dimension"""
# Create a part with units 'm'
part = Part.objects.create(name='Test Part', description='Test part description', component=True, units='m')

View File

@ -29,7 +29,6 @@ class CompanySimpleTest(TestCase):
@classmethod
def setUpTestData(cls):
"""Perform initialization for the tests in this class"""
super().setUpTestData()
Company.objects.create(name='ABC Co.',
@ -194,7 +193,6 @@ class AddressTest(TestCase):
def test_primary_constraint(self):
"""Test that there can only be one company-'primary=true' pair"""
Address.objects.create(company=self.c, primary=True)
Address.objects.create(company=self.c, primary=False)
@ -211,7 +209,6 @@ class AddressTest(TestCase):
def test_first_address_is_primary(self):
"""Test that first address related to company is always set to primary"""
addr = Address.objects.create(company=self.c)
self.assertTrue(addr.primary)
@ -255,7 +252,6 @@ class ManufacturerPartSimpleTest(TestCase):
def setUp(self):
"""Initialization for the unit tests in this class"""
# Create a manufacturer part
self.part = Part.objects.get(pk=1)
manufacturer = Company.objects.get(pk=1)

View File

@ -21,7 +21,6 @@ class CompanyIndex(InvenTreeRoleMixin, ListView):
def get_context_data(self, **kwargs):
"""Add extra context data to the company index page"""
ctx = super().get_context_data(**kwargs)
# Provide custom context data to the template,

View File

@ -27,7 +27,6 @@ class StatusView(APIView):
def get_status_model(self, *args, **kwargs):
"""Return the StatusCode moedl based on extra parameters passed to the view"""
status_model = self.kwargs.get(self.MODEL_REF, None)
if status_model is None:
@ -37,7 +36,6 @@ class StatusView(APIView):
def get(self, request, *args, **kwargs):
"""Perform a GET request to learn information about status codes"""
status_class = self.get_status_model()
if not inspect.isclass(status_class):

View File

@ -163,7 +163,6 @@ class StatusCode(BaseEnum):
@classmethod
def template_context(cls):
"""Return a dict representation containing all required information for templates."""
ret = {x.name: x.value for x in cls.values()}
ret['list'] = cls.list()

View File

@ -41,7 +41,6 @@ class LabelFilterMixin:
def get_items(self):
"""Return a list of database objects from query parameter"""
ids = []
# Construct a list of possible query parameter value options
@ -73,7 +72,6 @@ class LabelListView(LabelFilterMixin, ListAPI):
As each 'label' instance may optionally define its own filters,
the resulting queryset is the 'union' of the two.
"""
queryset = super().filter_queryset(queryset)
items = self.get_items()

View File

@ -399,7 +399,6 @@ class BuildLineLabel(LabelTemplate):
def get_context_data(self, request):
"""Generate context data for each provided BuildLine object."""
build_line = self.object_to_print
return {

View File

@ -13,7 +13,6 @@ class LabelSerializerBase(InvenTreeModelSerializer):
@staticmethod
def label_fields():
"""Generic serializer fields for a label template"""
return [
'pk',
'name',

View File

@ -11,6 +11,5 @@ from label.models import LabelOutput
@scheduled_task(ScheduledTask.DAILY)
def cleanup_old_label_outputs():
"""Remove old label outputs from the database"""
# Remove any label outputs which are older than 30 days
LabelOutput.objects.filter(created__lte=timezone.now() - timedelta(days=5)).delete()

View File

@ -94,7 +94,6 @@ class LabelTest(InvenTreeAPITestCase):
def test_print_part_label(self):
"""Actually 'print' a label, and ensure that the correct information is contained."""
label_data = """
{% load barcode %}
{% load report %}

View File

@ -173,7 +173,6 @@ class PurchaseOrderLineItemResource(PriceResourceMixin, InvenTreeResource):
def dehydrate_purchase_price(self, line):
"""Return a string value of the 'purchase_price' field, rather than the 'Money' object"""
if line.purchase_price:
return line.purchase_price.amount
else:

View File

@ -92,7 +92,6 @@ class OrderFilter(rest_filters.FilterSet):
def filter_status(self, queryset, name, value):
"""Filter by integer status code"""
return queryset.filter(status=value)
# Exact match for reference
@ -106,7 +105,6 @@ class OrderFilter(rest_filters.FilterSet):
def filter_assigned_to_me(self, queryset, name, value):
"""Filter by orders which are assigned to the current user."""
# Work out who "me" is!
owners = Owner.get_owners_matching_user(self.request.user)
@ -122,7 +120,6 @@ class OrderFilter(rest_filters.FilterSet):
Note that the overdue_filter() classmethod must be defined for the model
"""
if str2bool(value):
return queryset.filter(self.Meta.model.overdue_filter())
else:
@ -132,7 +129,6 @@ class OrderFilter(rest_filters.FilterSet):
def filter_outstanding(self, queryset, name, value):
"""Generic filter for determining if an order is 'outstanding'"""
if str2bool(value):
return queryset.filter(status__in=self.Meta.model.get_status_class().OPEN)
else:
@ -147,7 +143,6 @@ class OrderFilter(rest_filters.FilterSet):
def filter_has_project_code(self, queryset, name, value):
"""Filter by whether or not the order has a project code"""
if str2bool(value):
return queryset.exclude(project_code=None)
else:
@ -227,7 +222,6 @@ class PurchaseOrderList(PurchaseOrderMixin, APIDownloadMixin, ListCreateAPI):
def create(self, request, *args, **kwargs):
"""Save user information on create."""
data = self.clean_data(request.data)
duplicate_order = data.pop('duplicate_order', None)
@ -275,7 +269,6 @@ class PurchaseOrderList(PurchaseOrderMixin, APIDownloadMixin, ListCreateAPI):
def download_queryset(self, queryset, export_format):
"""Download the filtered queryset as a file"""
dataset = PurchaseOrderResource().export(queryset=queryset)
filedata = dataset.export(export_format)
@ -432,7 +425,6 @@ class PurchaseOrderLineItemFilter(LineItemFilter):
def filter_pending(self, queryset, name, value):
"""Filter by "pending" status (order status = pending)"""
if str2bool(value):
return queryset.filter(order__status__in=PurchaseOrderStatusGroups.OPEN)
else:
@ -511,7 +503,6 @@ class PurchaseOrderLineItemList(PurchaseOrderLineItemMixin, APIDownloadMixin, Li
def download_queryset(self, queryset, export_format):
"""Download the requested queryset as a file"""
dataset = PurchaseOrderLineItemResource().export(queryset=queryset)
filedata = dataset.export(export_format)
@ -562,7 +553,6 @@ class PurchaseOrderExtraLineList(GeneralExtraLineList, ListCreateAPI):
def download_queryset(self, queryset, export_format):
"""Download this queryset as a file"""
dataset = PurchaseOrderExtraLineResource().export(queryset=queryset)
filedata = dataset.export(export_format)
filename = f"InvenTree_ExtraPurchaseOrderLines.{export_format}"
@ -662,7 +652,6 @@ class SalesOrderList(SalesOrderMixin, APIDownloadMixin, ListCreateAPI):
def download_queryset(self, queryset, export_format):
"""Download this queryset as a file"""
dataset = SalesOrderResource().export(queryset=queryset)
filedata = dataset.export(export_format)
@ -812,7 +801,6 @@ class SalesOrderLineItemList(SalesOrderLineItemMixin, APIDownloadMixin, ListCrea
def download_queryset(self, queryset, export_format):
"""Download the requested queryset as a file"""
dataset = SalesOrderLineItemResource().export(queryset=queryset)
filedata = dataset.export(export_format)
@ -849,7 +837,6 @@ class SalesOrderExtraLineList(GeneralExtraLineList, ListCreateAPI):
def download_queryset(self, queryset, export_format):
"""Download this queryset as a file"""
dataset = SalesOrderExtraLineResource().export(queryset=queryset)
filedata = dataset.export(export_format)
filename = f"InvenTree_ExtraSalesOrderLines.{export_format}"
@ -1151,7 +1138,6 @@ class ReturnOrderList(ReturnOrderMixin, APIDownloadMixin, ListCreateAPI):
def download_queryset(self, queryset, export_format):
"""Download this queryset as a file"""
dataset = ReturnOrderResource().export(queryset=queryset)
filedata = dataset.export(export_format)
filename = f"InvenTree_ReturnOrders.{export_format}"
@ -1252,7 +1238,6 @@ class ReturnOrderLineItemFilter(LineItemFilter):
def filter_received(self, queryset, name, value):
"""Filter by 'received' field"""
if str2bool(value):
return queryset.exclude(received_date=None)
else:
@ -1267,7 +1252,6 @@ class ReturnOrderLineItemMixin:
def get_serializer(self, *args, **kwargs):
"""Return serializer for this endpoint with extra data as requested"""
try:
params = self.request.query_params
@ -1283,7 +1267,6 @@ class ReturnOrderLineItemMixin:
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint"""
queryset = super().get_queryset(*args, **kwargs)
queryset = queryset.prefetch_related(
@ -1302,7 +1285,6 @@ class ReturnOrderLineItemList(ReturnOrderLineItemMixin, APIDownloadMixin, ListCr
def download_queryset(self, queryset, export_format):
"""Download the requested queryset as a file"""
raise NotImplementedError("download_queryset not yet implemented for this endpoint")
filter_backends = SEARCH_ORDER_FILTER
@ -1334,7 +1316,6 @@ class ReturnOrderExtraLineList(GeneralExtraLineList, ListCreateAPI):
def download_queryset(self, queryset, export_format):
"""Download this queryset as a file"""
raise NotImplementedError("download_queryset not yet implemented")
@ -1389,7 +1370,6 @@ class OrderCalendarExport(ICalFeed):
https://stackoverflow.com/questions/152248/can-i-use-http-basic-authentication-with-django
https://www.djangosnippets.org/snippets/243/
"""
import base64
if request.user.is_authenticated:
@ -1435,7 +1415,6 @@ class OrderCalendarExport(ICalFeed):
def title(self, obj):
"""Return calendar title."""
if obj["ordertype"] == 'purchase-order':
ordertype_title = _('Purchase Order')
elif obj["ordertype"] == 'sales-order':
@ -1514,7 +1493,6 @@ class OrderCalendarExport(ICalFeed):
def item_link(self, item):
"""Set the item link."""
return construct_absolute_url(item.get_absolute_url())

View File

@ -62,7 +62,6 @@ class TotalPriceMixin(models.Model):
def save(self, *args, **kwargs):
"""Update the total_price field when saved"""
# Recalculate total_price for this order
self.update_total_price(commit=False)
super().save(*args, **kwargs)
@ -90,7 +89,6 @@ class TotalPriceMixin(models.Model):
- Otherwise, return the currency associated with the company
- Finally, return the default currency code
"""
if self.order_currency:
return self.order_currency
@ -102,7 +100,6 @@ class TotalPriceMixin(models.Model):
def update_total_price(self, commit=True):
"""Recalculate and save the total_price for this order"""
self.total_price = self.calculate_total_price(target_currency=self.currency)
if commit:
@ -200,7 +197,6 @@ class Order(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, Reference
def clean(self):
"""Custom clean method for the generic order class"""
super().clean()
# Check that the referenced 'contact' matches the correct 'company'
@ -216,7 +212,6 @@ class Order(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, Reference
It requires any subclasses to implement the get_status_class() class method
"""
today = datetime.now().date()
return Q(status__in=cls.get_status_class().OPEN) & ~Q(target_date=None) & Q(target_date__lt=today)
@ -226,7 +221,6 @@ class Order(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, Reference
Makes use of the overdue_filter() method to avoid code duplication
"""
return self.__class__.objects.filter(pk=self.pk).filter(self.__class__.overdue_filter()).exists()
description = models.CharField(max_length=250, blank=True, verbose_name=_('Description'), help_text=_('Order description (optional)'))
@ -284,7 +278,6 @@ class Order(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, Reference
@classmethod
def get_status_class(cls):
"""Return the enumeration class which represents the 'status' field for this model"""
raise NotImplementedError(f"get_status_class() not implemented for {__class__}")
@ -315,7 +308,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
@classmethod
def api_defaults(cls, request):
"""Return default values for this model when issuing an API OPTIONS request"""
defaults = {
'reference': order.validators.generate_next_purchase_order_reference(),
}
@ -362,7 +354,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
def __str__(self):
"""Render a string representation of this PurchaseOrder"""
return f"{self.reference} - {self.supplier.name if self.supplier else _('deleted')}"
reference = models.CharField(
@ -768,7 +759,6 @@ class SalesOrder(TotalPriceMixin, Order):
def __str__(self):
"""Render a string representation of this SalesOrder"""
return f"{self.reference} - {self.customer.name if self.customer else _('deleted')}"
reference = models.CharField(
@ -895,7 +885,6 @@ class SalesOrder(TotalPriceMixin, Order):
@transaction.atomic
def issue_order(self):
"""Change this order from 'PENDING' to 'IN_PROGRESS'"""
if self.status == SalesOrderStatus.PENDING:
self.status = SalesOrderStatus.IN_PROGRESS.value
self.issue_date = datetime.now().date()
@ -1069,7 +1058,6 @@ class OrderLineItem(MetadataMixin, models.Model):
Calls save method on the linked order
"""
super().save(*args, **kwargs)
self.order.save()
@ -1078,7 +1066,6 @@ class OrderLineItem(MetadataMixin, models.Model):
Calls save method on the linked order
"""
super().delete(*args, **kwargs)
self.order.save()
@ -1093,7 +1080,6 @@ class OrderLineItem(MetadataMixin, models.Model):
@property
def total_line_price(self):
"""Return the total price for this line item"""
if self.price:
return self.quantity * self.price
@ -1295,7 +1281,6 @@ class SalesOrderLineItem(OrderLineItem):
def clean(self):
"""Perform extra validation steps for this SalesOrderLineItem instance"""
super().clean()
if self.part:
@ -1729,7 +1714,6 @@ class ReturnOrder(TotalPriceMixin, Order):
def __str__(self):
"""Render a string representation of this ReturnOrder"""
return f"{self.reference} - {self.customer.name if self.customer else _('no customer')}"
reference = models.CharField(
@ -1810,7 +1794,6 @@ class ReturnOrder(TotalPriceMixin, Order):
@transaction.atomic
def complete_order(self):
"""Complete this ReturnOrder (if not already completed)"""
if self.status == ReturnOrderStatus.IN_PROGRESS:
self.status = ReturnOrderStatus.COMPLETE.value
self.complete_date = datetime.now().date()
@ -1825,7 +1808,6 @@ class ReturnOrder(TotalPriceMixin, Order):
@transaction.atomic
def issue_order(self):
"""Issue this ReturnOrder (if currently pending)"""
if self.status == ReturnOrderStatus.PENDING:
self.status = ReturnOrderStatus.IN_PROGRESS.value
self.issue_date = datetime.now().date()
@ -1842,7 +1824,6 @@ class ReturnOrder(TotalPriceMixin, Order):
- Adds a tracking entry to the StockItem
- Removes the 'customer' reference from the StockItem
"""
# Prevent an item from being "received" multiple times
if line.received_date is not None:
logger.warning("receive_line_item called with item already returned")
@ -1908,7 +1889,6 @@ class ReturnOrderLineItem(OrderLineItem):
def clean(self):
"""Perform extra validation steps for the ReturnOrderLineItem model"""
super().clean()
if self.item and not self.item.serialized:
@ -1977,7 +1957,6 @@ class ReturnOrderAttachment(InvenTreeAttachment):
@staticmethod
def get_api_url():
"""Return the API URL associated with the ReturnOrderAttachment class"""
return reverse('api-return-order-attachment-list')
def getSubdir(self):

View File

@ -86,14 +86,12 @@ class AbstractOrderSerializer(serializers.Serializer):
def validate_reference(self, reference):
"""Custom validation for the reference field"""
self.Meta.model.validate_reference_field(reference)
return reference
@staticmethod
def annotate_queryset(queryset):
"""Add extra information to the queryset"""
queryset = queryset.annotate(
line_items=SubqueryCount('lines')
)
@ -103,7 +101,6 @@ class AbstractOrderSerializer(serializers.Serializer):
@staticmethod
def order_fields(extra_fields):
"""Construct a set of fields for this serializer"""
return [
'pk',
'creation_date',
@ -272,7 +269,6 @@ class PurchaseOrderCompleteSerializer(serializers.Serializer):
def validate_accept_incomplete(self, value):
"""Check if the 'accept_incomplete' field is required"""
order = self.context['order']
if not value and not order.is_complete:
@ -910,7 +906,6 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer):
- "overdue" status (boolean field)
- "available_quantity"
"""
queryset = queryset.annotate(
overdue=Case(
When(
@ -1160,7 +1155,6 @@ class SalesOrderCompleteSerializer(serializers.Serializer):
def validate_accept_incomplete(self, value):
"""Check if the 'accept_incomplete' field is required"""
order = self.context['order']
if not value and not order.is_completed():
@ -1170,7 +1164,6 @@ class SalesOrderCompleteSerializer(serializers.Serializer):
def get_context_data(self):
"""Custom context data for this serializer"""
order = self.context['order']
return {
@ -1498,7 +1491,6 @@ class ReturnOrderSerializer(AbstractOrderSerializer, TotalPriceMixin, InvenTreeM
def __init__(self, *args, **kwargs):
"""Initialization routine for the serializer"""
customer_detail = kwargs.pop('customer_detail', False)
super().__init__(*args, **kwargs)
@ -1509,7 +1501,6 @@ class ReturnOrderSerializer(AbstractOrderSerializer, TotalPriceMixin, InvenTreeM
@staticmethod
def annotate_queryset(queryset):
"""Custom annotation for the serializer queryset"""
queryset = AbstractOrderSerializer.annotate_queryset(queryset)
queryset = queryset.annotate(
@ -1585,7 +1576,6 @@ class ReturnOrderLineItemReceiveSerializer(serializers.Serializer):
def validate_line_item(self, item):
"""Validation for a single line item"""
if item.order != self.context['order']:
raise ValidationError(_("Line item does not match return order"))
@ -1619,7 +1609,6 @@ class ReturnOrderReceiveSerializer(serializers.Serializer):
def validate(self, data):
"""Perform data validation for this serializer"""
order = self.context['order']
if order.status != ReturnOrderStatus.IN_PROGRESS:
raise ValidationError(_("Items can only be received against orders which are in progress"))
@ -1636,7 +1625,6 @@ class ReturnOrderReceiveSerializer(serializers.Serializer):
@transaction.atomic
def save(self):
"""Saving this serializer marks the returned items as received"""
order = self.context['order']
request = self.context['request']
@ -1682,7 +1670,6 @@ class ReturnOrderLineItemSerializer(InvenTreeModelSerializer):
def __init__(self, *args, **kwargs):
"""Initialization routine for the serializer"""
order_detail = kwargs.pop('order_detail', False)
item_detail = kwargs.pop('item_detail', False)
part_detail = kwargs.pop('part_detail', False)

View File

@ -15,7 +15,6 @@ from plugin.events import trigger_event
def notify_overdue_purchase_order(po: order.models.PurchaseOrder):
"""Notify users that a PurchaseOrder has just become 'overdue'"""
targets = []
if po.created_by:
@ -64,7 +63,6 @@ def check_overdue_purchase_orders():
- Look at the 'target_date' of any outstanding PurchaseOrder objects
- If the 'target_date' expired *yesterday* then the order is just out of date
"""
yesterday = datetime.now().date() - timedelta(days=1)
overdue_orders = order.models.PurchaseOrder.objects.filter(
@ -78,7 +76,6 @@ def check_overdue_purchase_orders():
def notify_overdue_sales_order(so: order.models.SalesOrder):
"""Notify appropriate users that a SalesOrder has just become 'overdue'"""
targets = []
if so.created_by:
@ -127,7 +124,6 @@ def check_overdue_sales_orders():
- Look at the 'target_date' of any outstanding SalesOrder objects
- If the 'target_date' expired *yesterday* then the order is just out of date
"""
yesterday = datetime.now().date() - timedelta(days=1)
overdue_orders = order.models.SalesOrder.objects.filter(

View File

@ -62,7 +62,6 @@ class PurchaseOrderTest(OrderTest):
def test_options(self):
"""Test the PurchaseOrder OPTIONS endpoint."""
self.assignRole('purchase_order.add')
response = self.options(self.LIST_URL, expected_code=200)
@ -144,7 +143,6 @@ class PurchaseOrderTest(OrderTest):
def test_total_price(self):
"""Unit tests for the 'total_price' field"""
# Ensure we have exchange rate data
self.generate_exchange_rates()
@ -360,7 +358,6 @@ class PurchaseOrderTest(OrderTest):
def test_po_duplicate(self):
"""Test that we can duplicate a PurchaseOrder via the API"""
self.assignRole('purchase_order.add')
po = models.PurchaseOrder.objects.get(pk=1)
@ -511,7 +508,6 @@ class PurchaseOrderTest(OrderTest):
def test_po_calendar(self):
"""Test the calendar export endpoint"""
# Create required purchase orders
self.assignRole('purchase_order.add')
@ -1120,7 +1116,6 @@ class SalesOrderTest(OrderTest):
def test_total_price(self):
"""Unit tests for the 'total_price' field"""
# Ensure we have exchange rate data
self.generate_exchange_rates()
@ -1359,7 +1354,6 @@ class SalesOrderTest(OrderTest):
def test_so_calendar(self):
"""Test the calendar export endpoint"""
# Create required sales orders
self.assignRole('sales_order.add')
@ -1420,7 +1414,6 @@ class SalesOrderTest(OrderTest):
def test_export(self):
"""Test we can export the SalesOrder list"""
n = models.SalesOrder.objects.count()
# Check there are some sales orders
@ -1940,7 +1933,6 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_options(self):
"""Test the OPTIONS endpoint"""
self.assignRole('return_order.add')
data = self.options(reverse('api-return-order-list'), expected_code=200).data
@ -1958,7 +1950,6 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_list(self):
"""Tests for the list endpoint"""
url = reverse('api-return-order-list')
response = self.get(url, expected_code=200)
@ -2024,7 +2015,6 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_create(self):
"""Test creation of ReturnOrder via the API"""
url = reverse('api-return-order-list')
# Do not have required permissions yet
@ -2055,7 +2045,6 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_update(self):
"""Test that we can update a ReturnOrder via the API"""
url = reverse('api-return-order-detail', kwargs={'pk': 1})
# Test detail endpoint
@ -2087,7 +2076,6 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_ro_issue(self):
"""Test the 'issue' order for a ReturnOrder"""
order = models.ReturnOrder.objects.get(pk=1)
self.assertEqual(order.status, ReturnOrderStatus.PENDING)
self.assertIsNone(order.issue_date)
@ -2106,7 +2094,6 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_receive(self):
"""Test that we can receive items against a ReturnOrder"""
customer = Company.objects.get(pk=4)
# Create an order
@ -2209,7 +2196,6 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_ro_calendar(self):
"""Test the calendar export endpoint"""
# Full test is in test_po_calendar. Since these use the same backend, test only
# that the endpoint is available
url = reverse('api-po-so-calendar', kwargs={'ordertype': 'return-order'})
@ -2243,7 +2229,6 @@ class OrderMetadataAPITest(InvenTreeAPITestCase):
def metatester(self, apikey, model):
"""Generic tester"""
modeldata = model.objects.first()
# Useless test unless a model object is found
@ -2272,7 +2257,6 @@ class OrderMetadataAPITest(InvenTreeAPITestCase):
def test_metadata(self):
"""Test all endpoints"""
for apikey, model in {
'api-po-metadata': models.PurchaseOrder,
'api-po-line-metadata': models.PurchaseOrderLineItem,

View File

@ -72,7 +72,6 @@ class SalesOrderTest(TestCase):
def test_so_reference(self):
"""Unit tests for sales order generation"""
# Test that a good reference is created when we have no existing orders
SalesOrder.objects.all().delete()
@ -80,7 +79,6 @@ class SalesOrderTest(TestCase):
def test_rebuild_reference(self):
"""Test that the 'reference_int' field gets rebuilt when the model is saved"""
self.assertEqual(self.order.reference_int, 1234)
self.order.reference = '999'
@ -121,7 +119,6 @@ class SalesOrderTest(TestCase):
def test_add_duplicate_line_item(self):
"""Adding a duplicate line item to a SalesOrder is accepted"""
for ii in range(1, 5):
SalesOrderLineItem.objects.create(order=self.order, part=self.part, quantity=ii)
@ -283,14 +280,12 @@ class SalesOrderTest(TestCase):
def test_shipment_delivery(self):
"""Test the shipment delivery settings"""
# Shipment delivery date should be empty before setting date
self.assertIsNone(self.shipment.delivery_date)
self.assertFalse(self.shipment.is_delivered())
def test_overdue_notification(self):
"""Test overdue sales order notification"""
self.order.created_by = get_user_model().objects.get(pk=3)
self.order.responsible = Owner.create(obj=Group.objects.get(pk=2))
self.order.target_date = datetime.now().date() - timedelta(days=1)
@ -311,7 +306,6 @@ class SalesOrderTest(TestCase):
- The responsible user should receive a notification
- The creating user should *not* receive a notification
"""
SalesOrder.objects.create(
customer=self.customer,
reference='1234567',

View File

@ -39,7 +39,6 @@ class OrderTest(TestCase):
def test_basics(self):
"""Basic tests e.g. repr functions etc."""
for pk in range(1, 8):
order = PurchaseOrder.objects.get(pk=pk)
@ -53,7 +52,6 @@ class OrderTest(TestCase):
def test_rebuild_reference(self):
"""Test that the reference_int field is correctly updated when the model is saved"""
order = PurchaseOrder.objects.get(pk=1)
order.save()
self.assertEqual(order.reference_int, 1)
@ -219,7 +217,6 @@ class OrderTest(TestCase):
def test_receive_pack_size(self):
"""Test receiving orders from suppliers with different pack_size values"""
prt = Part.objects.get(pk=1)
sup = Company.objects.get(pk=1)
@ -366,7 +363,6 @@ class OrderTest(TestCase):
- The responsible user(s) should receive a notification
- The creating user should *not* receive a notification
"""
po = PurchaseOrder.objects.create(
supplier=Company.objects.get(pk=1),
reference='XYZABC',

View File

@ -3,7 +3,6 @@
def generate_next_sales_order_reference():
"""Generate the next available SalesOrder reference"""
from order.models import SalesOrder
return SalesOrder.generate_reference()
@ -11,7 +10,6 @@ def generate_next_sales_order_reference():
def generate_next_purchase_order_reference():
"""Generate the next available PurchasesOrder reference"""
from order.models import PurchaseOrder
return PurchaseOrder.generate_reference()
@ -19,7 +17,6 @@ def generate_next_purchase_order_reference():
def generate_next_return_order_reference():
"""Generate the next available ReturnOrder reference"""
from order.models import ReturnOrder
return ReturnOrder.generate_reference()
@ -27,7 +24,6 @@ def generate_next_return_order_reference():
def validate_sales_order_reference_pattern(pattern):
"""Validate the SalesOrder reference 'pattern' setting"""
from order.models import SalesOrder
SalesOrder.validate_reference_pattern(pattern)
@ -35,7 +31,6 @@ def validate_sales_order_reference_pattern(pattern):
def validate_purchase_order_reference_pattern(pattern):
"""Validate the PurchaseOrder reference 'pattern' setting"""
from order.models import PurchaseOrder
PurchaseOrder.validate_reference_pattern(pattern)
@ -43,7 +38,6 @@ def validate_purchase_order_reference_pattern(pattern):
def validate_return_order_reference_pattern(pattern):
"""Validate the ReturnOrder reference 'pattern' setting"""
from order.models import ReturnOrder
ReturnOrder.validate_reference_pattern(pattern)
@ -51,7 +45,6 @@ def validate_return_order_reference_pattern(pattern):
def validate_sales_order_reference(value):
"""Validate that the SalesOrder reference field matches the required pattern"""
from order.models import SalesOrder
SalesOrder.validate_reference_field(value)
@ -59,7 +52,6 @@ def validate_sales_order_reference(value):
def validate_purchase_order_reference(value):
"""Validate that the PurchaseOrder reference field matches the required pattern"""
from order.models import PurchaseOrder
PurchaseOrder.validate_reference_field(value)
@ -67,7 +59,6 @@ def validate_purchase_order_reference(value):
def validate_return_order_reference(value):
"""Validate that the ReturnOrder reference field matches the required pattern"""
from order.models import ReturnOrder
ReturnOrder.validate_reference_field(value)

View File

@ -68,7 +68,6 @@ class PartResource(InvenTreeResource):
def dehydrate_min_cost(self, part):
"""Render minimum cost value for this Part"""
min_cost = part.pricing.overall_min if part.pricing else None
if min_cost is not None:
@ -76,7 +75,6 @@ class PartResource(InvenTreeResource):
def dehydrate_max_cost(self, part):
"""Render maximum cost value for this Part"""
max_cost = part.pricing.overall_max if part.pricing else None
if max_cost is not None:
@ -97,7 +95,6 @@ class PartResource(InvenTreeResource):
def after_import(self, dataset, result, using_transactions, dry_run, **kwargs):
"""Rebuild MPTT tree structure after importing Part data"""
super().after_import(dataset, result, using_transactions, dry_run, **kwargs)
# Rebuild the Part tree(s)
@ -203,7 +200,6 @@ class PartCategoryResource(InvenTreeResource):
def after_import(self, dataset, result, using_transactions, dry_run, **kwargs):
"""Rebuild MPTT tree structure after importing PartCategory data"""
super().after_import(dataset, result, using_transactions, dry_run, **kwargs)
# Rebuild the PartCategory tree(s)
@ -284,7 +280,6 @@ class BomItemResource(InvenTreeResource):
def dehydrate_min_cost(self, item):
"""Render minimum cost value for the BOM line item"""
min_price = item.sub_part.pricing.overall_min if item.sub_part.pricing else None
if min_price is not None:
@ -292,7 +287,6 @@ class BomItemResource(InvenTreeResource):
def dehydrate_max_cost(self, item):
"""Render maximum cost value for the BOM line item"""
max_price = item.sub_part.pricing.overall_max if item.sub_part.pricing else None
if max_price is not None:
@ -307,7 +301,6 @@ class BomItemResource(InvenTreeResource):
def before_export(self, queryset, *args, **kwargs):
"""Perform before exporting data"""
self.is_importing = kwargs.get('importing', False)
self.include_pricing = kwargs.pop('include_pricing', False)

View File

@ -50,7 +50,6 @@ class CategoryMixin:
def get_queryset(self, *args, **kwargs):
"""Return an annotated queryset for the CategoryDetail endpoint"""
queryset = super().get_queryset(*args, **kwargs)
queryset = part_serializers.CategorySerializer.annotate_queryset(queryset)
return queryset
@ -77,7 +76,6 @@ class CategoryList(CategoryMixin, APIDownloadMixin, ListCreateAPI):
def download_queryset(self, queryset, export_format):
"""Download the filtered queryset as a data file"""
dataset = PartCategoryResource().export(queryset=queryset)
filedata = dataset.export(export_format)
filename = f"InvenTree_Categories.{export_format}"
@ -192,7 +190,6 @@ class CategoryDetail(CategoryMixin, CustomRetrieveUpdateDestroyAPI):
def get_serializer(self, *args, **kwargs):
"""Add additional context based on query parameters"""
try:
params = self.request.query_params
@ -466,7 +463,6 @@ class PartScheduling(RetrieveAPI):
def retrieve(self, request, *args, **kwargs):
"""Return scheduling information for the referenced Part instance"""
part = self.get_object()
schedule = []
@ -674,7 +670,6 @@ class PartRequirements(RetrieveAPI):
def retrieve(self, request, *args, **kwargs):
"""Construct a response detailing Part requirements"""
part = self.get_object()
data = {
@ -700,13 +695,11 @@ class PartPricingDetail(RetrieveUpdateAPI):
def get_object(self):
"""Return the PartPricing object associated with the linked Part"""
part = super().get_object()
return part.pricing
def _get_serializer(self, *args, **kwargs):
"""Return a part pricing serializer object"""
part = self.get_object()
kwargs['instance'] = part.pricing
@ -825,7 +818,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_has_units(self, queryset, name, value):
"""Filter by whether the Part has units or not"""
if str2bool(value):
return queryset.exclude(units='')
else:
@ -836,7 +828,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_has_ipn(self, queryset, name, value):
"""Filter by whether the Part has an IPN (internal part number) or not"""
if str2bool(value):
return queryset.exclude(IPN='')
else:
@ -860,7 +851,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_low_stock(self, queryset, name, value):
"""Filter by "low stock" status."""
if str2bool(value):
# Ignore any parts which do not have a specified 'minimum_stock' level
# Filter items which have an 'in_stock' level lower than 'minimum_stock'
@ -874,7 +864,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_has_stock(self, queryset, name, value):
"""Filter by whether the Part has any stock"""
if str2bool(value):
return queryset.filter(Q(in_stock__gt=0))
else:
@ -885,7 +874,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_unallocated_stock(self, queryset, name, value):
"""Filter by whether the Part has unallocated stock"""
if str2bool(value):
return queryset.filter(Q(unallocated_stock__gt=0))
else:
@ -905,7 +893,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_exclude_tree(self, queryset, name, part):
"""Exclude all parts and variants 'down' from the specified part from the queryset"""
children = part.get_descendants(include_self=True)
return queryset.exclude(id__in=children)
@ -914,7 +901,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_ancestor(self, queryset, name, part):
"""Limit queryset to descendants of the specified ancestor part"""
descendants = part.get_descendants(include_self=False)
return queryset.filter(id__in=descendants)
@ -922,14 +908,12 @@ class PartFilter(rest_filters.FilterSet):
def filter_variant_of(self, queryset, name, part):
"""Limit queryset to direct children (variants) of the specified part"""
return queryset.filter(id__in=part.get_children())
in_bom_for = rest_filters.ModelChoiceFilter(label='In BOM Of', queryset=Part.objects.all(), method='filter_in_bom')
def filter_in_bom(self, queryset, name, part):
"""Limit queryset to parts in the BOM for the specified part"""
bom_parts = part.get_parts_in_bom()
return queryset.filter(id__in=[p.pk for p in bom_parts])
@ -937,7 +921,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_has_pricing(self, queryset, name, value):
"""Filter the queryset based on whether pricing information is available for the sub_part"""
q_a = Q(pricing_data=None)
q_b = Q(pricing_data__overall_min=None, pricing_data__overall_max=None)
@ -950,7 +933,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_has_stocktake(self, queryset, name, value):
"""Filter the queryset based on whether stocktake data is available"""
if str2bool(value):
return queryset.exclude(last_stocktake=None)
else:
@ -960,7 +942,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_stock_to_build(self, queryset, name, value):
"""Filter the queryset based on whether part stock is required for a pending BuildOrder"""
if str2bool(value):
# Return parts which are required for a build order, but have not yet been allocated
return queryset.filter(required_for_build_orders__gt=F('allocated_to_build_orders'))
@ -972,7 +953,6 @@ class PartFilter(rest_filters.FilterSet):
def filter_depleted_stock(self, queryset, name, value):
"""Filter the queryset based on whether the part is fully depleted of stock"""
if str2bool(value):
return queryset.filter(Q(in_stock=0) & ~Q(stock_item_count=0))
else:
@ -1234,7 +1214,6 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
- Only parts which have a matching parameter are returned
- Queryset is ordered based on parameter value
"""
# Extract "ordering" parameter from query args
ordering = self.request.query_params.get('ordering', None)
@ -1379,7 +1358,6 @@ class PartParameterTemplateFilter(rest_filters.FilterSet):
def filter_has_choices(self, queryset, name, value):
"""Filter queryset to include only PartParameterTemplates with choices."""
if str2bool(value):
return queryset.exclude(Q(choices=None) | Q(choices=''))
else:
@ -1392,7 +1370,6 @@ class PartParameterTemplateFilter(rest_filters.FilterSet):
def filter_has_units(self, queryset, name, value):
"""Filter queryset to include only PartParameterTemplates with units."""
if str2bool(value):
return queryset.exclude(Q(units=None) | Q(units=''))
else:
@ -1488,7 +1465,6 @@ class PartParameterAPIMixin:
- part_detail
- template_detail
"""
try:
kwargs['part_detail'] = str2bool(self.request.GET.get('part_detail', False))
kwargs['template_detail'] = str2bool(self.request.GET.get('template_detail', True))
@ -1515,7 +1491,6 @@ class PartParameterFilter(rest_filters.FilterSet):
If 'include_variants' query parameter is provided, filter against variant parts also
"""
try:
include_variants = str2bool(self.request.GET.get('include_variants', False))
except AttributeError:
@ -1679,7 +1654,6 @@ class BomFilter(rest_filters.FilterSet):
def filter_available_stock(self, queryset, name, value):
"""Filter the queryset based on whether each line item has any available stock"""
if str2bool(value):
return queryset.filter(available_stock__gt=0)
else:
@ -1689,7 +1663,6 @@ class BomFilter(rest_filters.FilterSet):
def filter_on_order(self, queryset, name, value):
"""Filter the queryset based on whether each line item has any stock on order"""
if str2bool(value):
return queryset.filter(on_order__gt=0)
else:
@ -1699,7 +1672,6 @@ class BomFilter(rest_filters.FilterSet):
def filter_has_pricing(self, queryset, name, value):
"""Filter the queryset based on whether pricing information is available for the sub_part"""
q_a = Q(sub_part__pricing_data=None)
q_b = Q(sub_part__pricing_data__overall_min=None, sub_part__pricing_data__overall_max=None)
@ -1722,7 +1694,6 @@ class BomMixin:
- part_detail
- sub_part_detail
"""
# Do we wish to include extra detail?
try:
kwargs['part_detail'] = str2bool(self.request.GET.get('part_detail', None))
@ -1760,7 +1731,6 @@ class BomList(BomMixin, ListCreateDestroyAPIView):
def list(self, request, *args, **kwargs):
"""Return serialized list response for this endpoint"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)

View File

@ -49,7 +49,6 @@ class PartConfig(AppConfig):
Prevents issues with state machine if the server is restarted mid-update
"""
from .models import PartPricing
if isImportingData():

View File

@ -63,7 +63,6 @@ def ExportBom(part: Part, fmt='csv', cascade: bool = False, max_levels: int = No
Returns:
StreamingHttpResponse: Response that can be passed to the endpoint
"""
parameter_data = str2bool(kwargs.get('parameter_data', False))
stock_data = str2bool(kwargs.get('stock_data', False))
supplier_data = str2bool(kwargs.get('supplier_data', False))

View File

@ -43,7 +43,6 @@ def annotate_on_order_quantity(reference: str = ''):
Note that in addition to the 'quantity' on order, we must also take into account 'pack_quantity'.
"""
# Filter only 'active' purhase orders
# Filter only line with outstanding quantity
order_filter = Q(
@ -85,7 +84,6 @@ def annotate_total_stock(reference: str = ''):
reference: The relationship reference of the part from the current model e.g. 'part'
stock_filter: Q object which defines how to filter the stock items
"""
# Stock filter only returns 'in stock' items
stock_filter = stock.models.StockItem.IN_STOCK_FILTER
@ -107,7 +105,6 @@ def annotate_build_order_requirements(reference: str = ''):
- We are interested in the 'quantity' of each BuildLine item
"""
# Active build orders only
build_filter = Q(build__status__in=BuildStatusGroups.ACTIVE_CODES)
@ -132,7 +129,6 @@ def annotate_build_order_allocations(reference: str = ''):
reference: The relationship reference of the part from the current model
build_filter: Q object which defines how to filter the allocation items
"""
# Build filter only returns 'active' build orders
build_filter = Q(build_line__build__status__in=BuildStatusGroups.ACTIVE_CODES)
@ -157,7 +153,6 @@ def annotate_sales_order_allocations(reference: str = ''):
reference: The relationship reference of the part from the current model
order_filter: Q object which defines how to filter the allocation items
"""
# Order filter only returns incomplete shipments for open orders
order_filter = Q(
line__order__status__in=SalesOrderStatusGroups.OPEN,
@ -183,7 +178,6 @@ def variant_stock_query(reference: str = '', filter: Q = stock.models.StockItem.
reference: The relationship reference of the part from the current model
filter: Q object which defines how to filter the returned StockItem instances
"""
return stock.models.StockItem.objects.filter(
part__tree_id=OuterRef(f'{reference}tree_id'),
part__lft__gt=OuterRef(f'{reference}lft'),
@ -198,7 +192,6 @@ def annotate_variant_quantity(subquery: Q, reference: str = 'quantity'):
subquery: A 'variant_stock_query' Q object
reference: The relationship reference of the variant stock items from the current queryset
"""
return Coalesce(
Subquery(
subquery.annotate(
@ -216,7 +209,6 @@ def annotate_category_parts():
- Includes parts in subcategories also
- Requires subquery to perform annotation
"""
# Construct a subquery to provide all parts in this category and any subcategories:
subquery = part.models.Part.objects.exclude(category=None).filter(
category__tree_id=OuterRef('tree_id'),
@ -250,9 +242,7 @@ def filter_by_parameter(queryset, template_id: int, value: str, func: str = ''):
Returns:
A queryset of Part objects filtered by the given parameter
"""
# TODO
return queryset
@ -268,7 +258,6 @@ def order_by_parameter(queryset, template_id: int, ascending=True):
Returns:
A queryset of Part objects ordered by the given parameter
"""
template_filter = part.models.PartParameter.objects.filter(
template__id=template_id,
part_id=OuterRef('id'),

View File

@ -453,7 +453,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
If the part image has been updated, then check if the "old" (previous) image is still used by another part.
If not, it is considered "orphaned" and will be deleted.
"""
if self.pk:
try:
previous = Part.objects.get(pk=self.pk)
@ -556,7 +555,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
This function is exposed to any Validation plugins, and thus can be customized.
"""
from plugin.registry import registry
for plugin in registry.with_mixin('validation'):
@ -579,7 +577,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
- Validation is handled by custom plugins
- By default, no validation checks are performed
"""
from plugin.registry import registry
for plugin in registry.with_mixin('validation'):
@ -626,7 +623,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
Raises:
ValidationError if serial number is invalid and raise_error = True
"""
serial = str(serial).strip()
# First, throw the serial number against each of the loaded validation plugins
@ -682,7 +678,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
def find_conflicting_serial_numbers(self, serials: list):
"""For a provided list of serials, return a list of those which are conflicting."""
conflicts = []
for serial in serials:
@ -704,7 +699,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
Returns:
The latest serial number specified for this part, or None
"""
stock = StockModels.StockItem.objects.all().exclude(serial=None).exclude(serial='')
# Generate a query for any stock items for this part variant tree with non-empty serial numbers
@ -1237,7 +1231,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
@property
def can_build(self):
"""Return the number of units that can be build with available stock."""
import part.filters
# If this part does NOT have a BOM, result is simply the currently available stock
@ -1436,7 +1429,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
def allocation_count(self, **kwargs):
"""Return the total quantity of stock allocated for this part, against both build orders and sales orders."""
if self.id is None:
# If this instance has not been saved, foreign-key lookups will fail
return 0
@ -1562,7 +1554,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
So we construct a query for each case, and combine them...
"""
# Cache all *parent* parts
try:
parents = self.get_ancestors(include_self=False)
@ -1599,7 +1590,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
Includes consideration of inherited BOMs
"""
# Grab a queryset of all BomItem objects which "require" this part
bom_items = BomItem.objects.filter(
self.get_used_in_bom_item_filter(
@ -1738,7 +1728,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
def update_pricing(self):
"""Recalculate cached pricing for this Part instance"""
self.pricing.update_pricing()
@property
@ -1748,7 +1737,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
If there is no PartPricing database entry defined for this Part,
it will first be created, and then returned.
"""
try:
pricing = PartPricing.objects.get(part=self)
except PartPricing.DoesNotExist:
@ -1768,7 +1756,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
create: Whether or not a new PartPricing object should be created if it does not already exist
test: Whether or not the pricing update is allowed during unit tests
"""
try:
self.refresh_from_db()
except Part.DoesNotExist:
@ -2102,7 +2089,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
def getTestTemplateMap(self, **kwargs):
"""Return a map of all test templates associated with this Part"""
templates = {}
for template in self.getTestTemplates(**kwargs):
@ -2160,7 +2146,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
Note that some supplier parts may have a different pack_quantity attribute,
and this needs to be taken into account!
"""
quantity = 0
# Iterate through all supplier parts
@ -2213,7 +2198,6 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
@property
def latest_stocktake(self):
"""Return the latest PartStocktake object associated with this part (if one exists)"""
return self.stocktakes.order_by('-pk').first()
@property
@ -2356,7 +2340,6 @@ class PartPricing(common.models.MetaMixin):
@property
def is_valid(self):
"""Return True if the cached pricing is valid"""
return self.updated is not None
def convert(self, money):
@ -2364,7 +2347,6 @@ class PartPricing(common.models.MetaMixin):
If a MissingRate error is raised, ignore it and return None
"""
if money is None:
return None
@ -2380,7 +2362,6 @@ class PartPricing(common.models.MetaMixin):
def schedule_for_update(self, counter: int = 0, test: bool = False):
"""Schedule this pricing to be updated"""
import InvenTree.ready
# If we are running within CI, only schedule the update if the test flag is set
@ -2446,7 +2427,6 @@ class PartPricing(common.models.MetaMixin):
def update_pricing(self, counter: int = 0, cascade: bool = True):
"""Recalculate all cost data for the referenced Part instance"""
# If importing data, skip pricing update
if InvenTree.ready.isImportingData():
return
@ -2485,7 +2465,6 @@ class PartPricing(common.models.MetaMixin):
def update_assemblies(self, counter: int = 0):
"""Schedule updates for any assemblies which use this part"""
# If the linked Part is used in any assemblies, schedule a pricing update for those assemblies
used_in_parts = self.part.get_used_in()
@ -2494,7 +2473,6 @@ class PartPricing(common.models.MetaMixin):
def update_templates(self, counter: int = 0):
"""Schedule updates for any template parts above this part"""
templates = self.part.get_ancestors(include_self=False)
for p in templates:
@ -2502,7 +2480,6 @@ class PartPricing(common.models.MetaMixin):
def save(self, *args, **kwargs):
"""Whenever pricing model is saved, automatically update overall prices"""
# Update the currency which was used to perform the calculation
self.currency = currency_code_default()
@ -2524,7 +2501,6 @@ class PartPricing(common.models.MetaMixin):
Note: The cumulative costs are calculated based on the specified default currency
"""
if not self.part.assembly:
# Not an assembly - no BOM pricing
self.bom_cost_min = None
@ -2603,7 +2579,6 @@ class PartPricing(common.models.MetaMixin):
Purchase history only takes into account "completed" purchase orders.
"""
# Find all line items for completed orders which reference this part
line_items = OrderModels.PurchaseOrderLineItem.objects.filter(
order__status=PurchaseOrderStatus.COMPLETE.value,
@ -2670,7 +2645,6 @@ class PartPricing(common.models.MetaMixin):
def update_internal_cost(self, save=True):
"""Recalculate internal cost for the referenced Part instance"""
min_int_cost = None
max_int_cost = None
@ -2704,7 +2678,6 @@ class PartPricing(common.models.MetaMixin):
- The limits are simply the lower and upper bounds of available SupplierPriceBreaks
- We do not take "quantity" into account here
"""
min_sup_cost = None
max_sup_cost = None
@ -2745,7 +2718,6 @@ class PartPricing(common.models.MetaMixin):
Here we track the min/max costs of any variant parts.
"""
variant_min = None
variant_max = None
@ -2785,7 +2757,6 @@ class PartPricing(common.models.MetaMixin):
Here we simply take the minimum / maximum values of the other calculated fields.
"""
overall_min = None
overall_max = None
@ -2851,7 +2822,6 @@ class PartPricing(common.models.MetaMixin):
def update_sale_cost(self, save=True):
"""Recalculate sale cost data"""
# Iterate through the sell price breaks
min_sell_price = None
max_sell_price = None
@ -3091,7 +3061,6 @@ class PartStocktake(models.Model):
@receiver(post_save, sender=PartStocktake, dispatch_uid='post_save_stocktake')
def update_last_stocktake(sender, instance, created, **kwargs):
"""Callback function when a PartStocktake instance is created / edited"""
# When a new PartStocktake instance is create, update the last_stocktake date for the Part
if created:
try:
@ -3104,7 +3073,6 @@ def update_last_stocktake(sender, instance, created, **kwargs):
def save_stocktake_report(instance, filename):
"""Save stocktake reports to the correct subdirectory"""
filename = os.path.basename(filename)
return os.path.join('stocktake', 'report', filename)
@ -3397,7 +3365,6 @@ class PartParameterTemplate(MetadataMixin, models.Model):
- A 'checkbox' field cannot have 'choices' set
- A 'checkbox' field cannot have 'units' set
"""
super().clean()
# Check that checkbox parameters do not have units or choices
@ -3450,7 +3417,6 @@ class PartParameterTemplate(MetadataMixin, models.Model):
def get_choices(self):
"""Return a list of choices for this parameter template"""
if not self.choices:
return []
@ -3496,7 +3462,6 @@ class PartParameterTemplate(MetadataMixin, models.Model):
@receiver(post_save, sender=PartParameterTemplate, dispatch_uid='post_save_part_parameter_template')
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():
@ -3540,7 +3505,6 @@ class PartParameter(MetadataMixin, models.Model):
def save(self, *args, **kwargs):
"""Custom save method for the PartParameter model."""
# Validate the PartParameter before saving
self.calculate_numeric_value()
@ -3553,7 +3517,6 @@ class PartParameter(MetadataMixin, models.Model):
def clean(self):
"""Validate the PartParameter before saving to the database."""
super().clean()
# Validate the parameter data against the template units
@ -3597,7 +3560,6 @@ class PartParameter(MetadataMixin, models.Model):
- If a 'units' field is provided, then the data will be converted to the base SI unit.
- Otherwise, we'll try to do a simple float cast
"""
if self.template.units:
try:
self.data_numeric = InvenTree.conversion.convert_physical_value(self.data, self.template.units)
@ -3775,7 +3737,6 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model):
def get_assemblies(self):
"""Return a list of assemblies which use this BomItem"""
assemblies = [self.part]
if self.inherited:
@ -4087,7 +4048,6 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model):
@receiver(post_save, sender=BomItem, dispatch_uid='update_bom_build_lines')
def update_bom_build_lines(sender, instance, created, **kwargs):
"""Update existing build orders when a BomItem is created or edited"""
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
import build.tasks
InvenTree.tasks.offload_task(
@ -4101,7 +4061,6 @@ def update_bom_build_lines(sender, instance, created, **kwargs):
@receiver(post_save, sender=PartInternalPriceBreak, dispatch_uid='post_save_internal_price_break')
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():
instance.part.schedule_pricing_update(create=True)
@ -4112,7 +4071,6 @@ def update_pricing_after_edit(sender, instance, created, **kwargs):
@receiver(post_delete, sender=PartInternalPriceBreak, dispatch_uid='post_delete_internal_price_break')
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():
instance.part.schedule_pricing_update(create=False)
@ -4203,7 +4161,6 @@ class PartRelated(MetadataMixin, models.Model):
def clean(self):
"""Overwrite clean method to check that relation is unique."""
super().clean()
if self.part_1 == self.part_2:

View File

@ -64,7 +64,6 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
def __init__(self, *args, **kwargs):
"""Optionally add or remove extra fields"""
path_detail = kwargs.pop('path_detail', False)
super().__init__(*args, **kwargs)
@ -79,7 +78,6 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
@staticmethod
def annotate_queryset(queryset):
"""Annotate extra information to the queryset"""
# Annotate the number of 'parts' which exist in each category (including subcategories!)
queryset = queryset.annotate(
part_count=part.filters.annotate_category_parts()
@ -274,7 +272,6 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
def __init__(self, *args, **kwargs):
"""Custom initialization routine for the PartBrief serializer"""
pricing = kwargs.pop('pricing', True)
super().__init__(*args, **kwargs)
@ -311,7 +308,6 @@ class PartParameterSerializer(InvenTree.serializers.InvenTreeModelSerializer):
Allows us to optionally include or exclude particular information
"""
template_detail = kwargs.pop('template_detail', True)
part_detail = kwargs.pop('part_detail', False)
@ -360,7 +356,6 @@ class PartSetCategorySerializer(serializers.Serializer):
@transaction.atomic
def save(self):
"""Save the serializer to change the location of the selected parts"""
data = self.validated_data
parts = data['parts']
category = data['category']
@ -453,7 +448,6 @@ class InitialSupplierSerializer(serializers.Serializer):
def validate_supplier(self, company):
"""Validation for the provided Supplier"""
if company and not company.is_supplier:
raise serializers.ValidationError(_('Selected company is not a valid supplier'))
@ -461,7 +455,6 @@ class InitialSupplierSerializer(serializers.Serializer):
def validate_manufacturer(self, company):
"""Validation for the provided Manufacturer"""
if company and not company.is_manufacturer:
raise serializers.ValidationError(_('Selected company is not a valid manufacturer'))
@ -469,7 +462,6 @@ class InitialSupplierSerializer(serializers.Serializer):
def validate(self, data):
"""Extra validation for this serializer"""
if company.models.ManufacturerPart.objects.filter(
manufacturer=data.get('manufacturer', None),
MPN=data.get('mpn', '')
@ -603,7 +595,6 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize
def skip_create_fields(self):
"""Skip these fields when instantiating a new Part instance"""
fields = super().skip_create_fields()
fields += [
@ -621,7 +612,6 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize
Performing database queries as efficiently as possible, to reduce database trips.
"""
# Annotate with the total number of stock items
queryset = queryset.annotate(
stock_item_count=SubqueryCount('stock_items')
@ -759,7 +749,6 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize
@transaction.atomic
def create(self, validated_data):
"""Custom method for creating a new Part instance using this serializer"""
duplicate = validated_data.pop('duplicate', None)
initial_stock = validated_data.pop('initial_stock', None)
initial_supplier = validated_data.pop('initial_supplier', None)
@ -862,7 +851,6 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize
def save(self):
"""Save the Part instance"""
super().save()
part = self.instance
@ -925,7 +913,6 @@ class PartStocktakeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
def save(self):
"""Called when this serializer is saved"""
data = self.validated_data
# Add in user information automatically
@ -997,7 +984,6 @@ class PartStocktakeReportGenerateSerializer(serializers.Serializer):
def validate(self, data):
"""Custom validation for this serializer"""
# Stocktake functionality must be enabled
if not common.models.InvenTreeSetting.get_setting('STOCKTAKE_ENABLE', False):
raise serializers.ValidationError(_("Stocktake functionality is not enabled"))
@ -1010,7 +996,6 @@ class PartStocktakeReportGenerateSerializer(serializers.Serializer):
def save(self):
"""Saving this serializer instance requests generation of a new stocktake report"""
data = self.validated_data
user = self.context['request'].user

View File

@ -41,7 +41,6 @@ def perform_stocktake(target: part.models.Part, user: User, note: str = '', comm
In this case, the stocktake *report* will be limited to the specified location.
"""
# Determine which locations are "valid" for the generated report
location = kwargs.get('location', None)
locations = location.get_descendants(include_self=True) if location else []
@ -158,7 +157,6 @@ def generate_stocktake_report(**kwargs):
generate_report: If True, generate a stocktake report from the calculated data (default=True)
update_parts: If True, save stocktake information against each filtered Part (default = True)
"""
# Determine if external locations should be excluded
exclude_external = kwargs.get(
'exclude_exernal',

View File

@ -74,7 +74,6 @@ def update_part_pricing(pricing: part.models.PartPricing, counter: int = 0):
pricing: The target PartPricing instance to be updated
counter: How many times this function has been called in sequence
"""
logger.info("Updating part pricing for %s", pricing.part)
pricing.update_pricing(counter=counter)
@ -91,7 +90,6 @@ def check_missing_pricing(limit=250):
Arguments:
limit: Maximum number of parts to process at once
"""
# Find parts for which pricing information has never been updated
results = part.models.PartPricing.objects.filter(updated=None)[:limit]
@ -144,7 +142,6 @@ def scheduled_stocktake_reports():
- Delete 'old' stocktake report files after the specified period
- Generate new reports at the specified period
"""
# Sleep a random number of seconds to prevent worker conflict
time.sleep(random.randint(1, 5))
@ -185,7 +182,6 @@ def rebuild_parameters(template_id):
This function is called when a base template is changed,
which may cause the base unit to be adjusted.
"""
try:
template = part.models.PartParameterTemplate.objects.get(pk=template_id)
except part.models.PartParameterTemplate.DoesNotExist:
@ -215,7 +211,6 @@ def rebuild_supplier_parts(part_id):
This function is called when a bart part is changed,
which may cause the native units of any supplier parts to be updated
"""
try:
prt = part.models.Part.objects.get(pk=part_id)
except part.models.Part.DoesNotExist:

View File

@ -18,7 +18,6 @@ register = template.Library()
@register.simple_tag()
def translation_stats(lang_code):
"""Return the translation percentage for the given language code"""
if lang_code is None:
return None
@ -30,7 +29,6 @@ class CustomTranslateNode(TranslateNode):
def render(self, context):
"""Custom render function overrides / extends default behaviour"""
result = super().render(context)
result = bleach.clean(result)
@ -58,7 +56,6 @@ def do_translate(parser, token):
The only difference is that we pass this to our custom rendering node class
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0])

View File

@ -105,7 +105,6 @@ def render_date(context, date_object):
@register.simple_tag
def render_currency(money, **kwargs):
"""Render a currency / Money object"""
return InvenTree.helpers_model.render_currency(money, **kwargs)
@ -211,14 +210,12 @@ def inventree_logo(**kwargs):
Returns a path to an image file, which can be rendered in the web interface
"""
return InvenTree.helpers.getLogoImage(**kwargs)
@register.simple_tag()
def inventree_splash(**kwargs):
"""Return the URL for the InvenTree splash screen, *or* a custom screen if the user has provided one."""
return InvenTree.helpers.getSplashScreen(**kwargs)
@ -344,7 +341,6 @@ def setting_object(key, *args, **kwargs):
(Or return None if the setting does not exist)
if a user-setting was requested return that
"""
cache = kwargs.get('cache', True)
if 'plugin' in kwargs:
@ -499,7 +495,6 @@ def primitive_to_javascript(primitive):
@register.simple_tag()
def js_bool(val):
"""Return a javascript boolean value (true or false)"""
if val:
return 'true'
else:

View File

@ -14,7 +14,6 @@ logger = logging.getLogger('inventree')
@register.simple_tag()
def sso_login_enabled():
"""Return True if single-sign-on is enabled"""
return str2bool(InvenTreeSetting.get_setting('LOGIN_ENABLE_SSO'))
@ -33,7 +32,6 @@ def sso_auto_enabled():
@register.simple_tag()
def sso_check_provider(provider):
"""Return True if the given provider is correctly configured"""
import allauth.app_settings
from allauth.socialaccount.models import SocialApp

View File

@ -109,7 +109,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
def test_part_count(self):
"""Test that the 'part_count' field is annotated correctly"""
url = reverse('api-part-category-list')
# Create a parent category
@ -162,7 +161,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
def test_category_parameters(self):
"""Test that the PartCategoryParameterTemplate API function work"""
url = reverse('api-part-category-parameter-list')
response = self.get(url, {}, expected_code=200)
@ -216,7 +214,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
This helps to protect against XSS injection
"""
url = reverse('api-part-category-detail', kwargs={'pk': 1})
# Invalid values containing tags
@ -258,7 +255,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
def test_invisible_chars(self):
"""Test that invisible characters are removed from the input data"""
url = reverse('api-part-category-detail', kwargs={'pk': 1})
values = [
@ -395,7 +391,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
- Parts cannot be created in structural categories
- Parts cannot be assigned to structural categories
"""
# Create our structural part category
structural_category = PartCategory.objects.create(
name='Structural category',
@ -443,7 +438,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
def test_path_detail(self):
"""Test path_detail information"""
url = reverse('api-part-category-detail', kwargs={'pk': 5})
# First, request without path detail
@ -718,7 +712,6 @@ class PartAPITest(PartAPITestBase):
def test_filter_by_in_bom(self):
"""Test that we can filter part list by the 'in_bom_for' parameter"""
url = reverse('api-part-list')
response = self.get(
@ -755,7 +748,6 @@ class PartAPITest(PartAPITestBase):
def test_filter_by_convert(self):
"""Test that we can correctly filter the Part list by conversion options"""
category = PartCategory.objects.get(pk=3)
# First, construct a set of template / variant parts
@ -1207,7 +1199,6 @@ class PartCreationTests(PartAPITestBase):
def submit(stock_data, expected_code=None):
"""Helper function for submitting with initial stock data"""
data = {
'category': 1,
'name': "My lil' test part",
@ -1252,7 +1243,6 @@ class PartCreationTests(PartAPITestBase):
def submit(supplier_data, expected_code=400):
"""Helper function for submitting with supplier data"""
data = {
'name': 'My test part',
'description': 'A test part thingy',
@ -1355,7 +1345,6 @@ class PartCreationTests(PartAPITestBase):
def test_duplication(self):
"""Test part duplication options"""
# Run a matrix of tests
for bom in [True, False]:
for img in [True, False]:
@ -1384,7 +1373,6 @@ class PartCreationTests(PartAPITestBase):
def test_category_parameters(self):
"""Test that category parameters are correctly applied"""
cat = PartCategory.objects.get(pk=1)
# Add some parameter template to the parent category
@ -1684,7 +1672,6 @@ class PartDetailTests(PartAPITestBase):
def test_path_detail(self):
"""Check that path_detail can be requested against the serializer"""
response = self.get(
reverse('api-part-detail', kwargs={'pk': 1}),
{
@ -1702,7 +1689,6 @@ class PartListTests(PartAPITestBase):
def test_query_count(self):
"""Test that the query count is unchanged, independent of query results"""
queries = [
{'limit': 1},
{'limit': 10},
@ -1768,7 +1754,6 @@ class PartNotesTests(InvenTreeAPITestCase):
def test_long_notes(self):
"""Test that very long notes field is rejected"""
# Ensure that we cannot upload a very long piece of text
url = reverse('api-part-detail', kwargs={'pk': 1})
@ -1784,7 +1769,6 @@ class PartNotesTests(InvenTreeAPITestCase):
def test_multiline_formatting(self):
"""Ensure that markdown formatting is retained"""
url = reverse('api-part-detail', kwargs={'pk': 1})
notes = """
@ -1828,12 +1812,10 @@ class PartPricingDetailTests(InvenTreeAPITestCase):
def url(self, pk):
"""Construct a pricing URL"""
return reverse('api-part-pricing', kwargs={'pk': pk})
def test_pricing_detail(self):
"""Test an empty pricing detail"""
response = self.get(
self.url(1),
expected_code=200
@ -2100,7 +2082,6 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
This queryset annotation takes into account any outstanding line items for active orders,
and should also use the 'pack_size' of the supplier part objects.
"""
supplier = Company.objects.create(
name='Paint Supplies',
description='A supplier of paints',
@ -2284,7 +2265,6 @@ class BomItemTest(InvenTreeAPITestCase):
def test_bom_list_search(self):
"""Test that we can search the BOM list API endpoint"""
url = reverse('api-bom-list')
response = self.get(url, expected_code=200)
@ -2328,7 +2308,6 @@ class BomItemTest(InvenTreeAPITestCase):
def test_bom_list_ordering(self):
"""Test that the BOM list results can be ordered"""
url = reverse('api-bom-list')
# Order by increasing quantity
@ -2698,7 +2677,6 @@ class PartAttachmentTest(InvenTreeAPITestCase):
def test_add_attachment(self):
"""Test that we can create a new PartAttachment via the API"""
url = reverse('api-part-attachment-list')
# Upload without permission
@ -2795,7 +2773,6 @@ class PartInternalPriceBreakTest(InvenTreeAPITestCase):
def test_create_price_breaks(self):
"""Test we can create price breaks at various quantities"""
url = reverse('api-part-internal-price-list')
breaks = [
@ -2859,7 +2836,6 @@ class PartStocktakeTest(InvenTreeAPITestCase):
def test_list_endpoint(self):
"""Test the list endpoint for the stocktake data"""
url = reverse('api-part-stocktake-list')
self.assignRole('part.view')
@ -2911,7 +2887,6 @@ class PartStocktakeTest(InvenTreeAPITestCase):
def test_create_stocktake(self):
"""Test that stocktake entries can be created via the API"""
url = reverse('api-part-stocktake-list')
self.assignRole('stocktake.add')
@ -2948,7 +2923,6 @@ class PartStocktakeTest(InvenTreeAPITestCase):
Note that only 'staff' users can perform these actions.
"""
p = Part.objects.all().first()
st = PartStocktake.objects.create(part=p, quantity=10)
@ -2989,7 +2963,6 @@ class PartStocktakeTest(InvenTreeAPITestCase):
def test_report_list(self):
"""Test for PartStocktakeReport list endpoint"""
from part.stocktake import generate_stocktake_report
# Initially, no stocktake records are available
@ -3021,7 +2994,6 @@ class PartStocktakeTest(InvenTreeAPITestCase):
def test_report_generate(self):
"""Test API functionality for generating a new stocktake report"""
url = reverse('api-part-stocktake-report-generate')
# Permission denied, initially
@ -3064,7 +3036,6 @@ class PartMetadataAPITest(InvenTreeAPITestCase):
def metatester(self, apikey, model):
"""Generic tester"""
modeldata = model.objects.first()
# Useless test unless a model object is found
@ -3093,7 +3064,6 @@ class PartMetadataAPITest(InvenTreeAPITestCase):
def test_metadata(self):
"""Test all endpoints"""
for apikey, model in {
'api-part-category-parameter-metadata': PartCategoryParameterTemplate,
'api-part-category-metadata': PartCategory,
@ -3113,7 +3083,6 @@ class PartSchedulingTest(PartAPITestBase):
def test_get_schedule(self):
"""Test that the scheduling endpoint returns OK"""
part_ids = [
1, 3, 100, 101,
]

View File

@ -202,7 +202,6 @@ class BomItemTest(TestCase):
def test_consumable(self):
"""Tests for the 'consumable' BomItem field"""
# Create an assembly part
assembly = Part.objects.create(name="An assembly", description="Made with parts", assembly=True)

View File

@ -22,7 +22,6 @@ class CategoryTest(TestCase):
@classmethod
def setUpTestData(cls):
"""Extract some interesting categories for time-saving"""
super().setUpTestData()
cls.electronics = PartCategory.objects.get(name='Electronics')
@ -68,7 +67,6 @@ class CategoryTest(TestCase):
def test_path_string(self):
"""Test that the category path string works correctly."""
# Note that due to data migrations, these fields need to be saved first
self.resistors.save()
self.transceivers.save()
@ -137,7 +135,6 @@ class CategoryTest(TestCase):
def test_part_count(self):
"""Test that the Category part count works."""
self.assertEqual(self.fasteners.partcount(), 2)
self.assertEqual(self.capacitors.partcount(), 1)
@ -203,7 +200,6 @@ class CategoryTest(TestCase):
def test_default_locations(self):
"""Test traversal for default locations."""
self.assertIsNotNone(self.fasteners.default_location)
self.fasteners.default_location.save()
self.assertEqual(str(self.fasteners.default_location), 'Office/Drawer_1 - In my desk')

View File

@ -56,7 +56,6 @@ class TestBomItemMigrations(MigratorTestCase):
def prepare(self):
"""Create initial dataset"""
Part = self.old_state.apps.get_model('part', 'part')
BomItem = self.old_state.apps.get_model('part', 'bomitem')
@ -75,7 +74,6 @@ class TestBomItemMigrations(MigratorTestCase):
def test_validated_field(self):
"""Test that the 'validated' field is added to the BomItem objects"""
BomItem = self.new_state.apps.get_model('part', 'bomitem')
self.assertEqual(BomItem.objects.count(), 2)
@ -92,7 +90,6 @@ class TestParameterMigrations(MigratorTestCase):
def prepare(self):
"""Create some parts, and templates with parameters"""
Part = self.old_state.apps.get_model('part', 'part')
PartParameter = self.old_state.apps.get_model('part', 'partparameter')
PartParameterTemlate = self.old_state.apps.get_model('part', 'partparametertemplate')
@ -121,7 +118,6 @@ class TestParameterMigrations(MigratorTestCase):
def test_data_migration(self):
"""Test that the template units and values have been updated correctly"""
Part = self.new_state.apps.get_model('part', 'part')
PartParameter = self.new_state.apps.get_model('part', 'partparameter')
PartParameterTemlate = self.new_state.apps.get_model('part', 'partparametertemplate')
@ -164,7 +160,6 @@ class PartUnitsMigrationTest(MigratorTestCase):
def prepare(self):
"""Prepare some parts with units"""
Part = self.old_state.apps.get_model('part', 'part')
units = ['mm', 'INCH', '', '%']
@ -177,7 +172,6 @@ class PartUnitsMigrationTest(MigratorTestCase):
def test_units_migration(self):
"""Test that the units have migrated OK"""
Part = self.new_state.apps.get_model('part', 'part')
part_1 = Part.objects.get(name='Part 1')
@ -202,7 +196,6 @@ class TestPartParameterTemplateMigration(MigratorTestCase):
def prepare(self):
"""Prepare some parts with units"""
PartParameterTemplate = self.old_state.apps.get_model('part', 'partparametertemplate')
# Create a test template
@ -217,7 +210,6 @@ class TestPartParameterTemplateMigration(MigratorTestCase):
def test_units_migration(self):
"""Test that the new fields have been added correctly"""
PartParameterTemplate = self.new_state.apps.get_model('part', 'partparametertemplate')
template = PartParameterTemplate.objects.get(name='Template 1')

View File

@ -66,7 +66,6 @@ class TestParams(TestCase):
def test_get_parameter(self):
"""Test the Part.get_parameter method"""
prt = Part.objects.get(pk=3)
# Check that we can get a parameter by name
@ -119,7 +118,6 @@ class ParameterTests(TestCase):
def test_choice_validation(self):
"""Test that parameter choices are correctly validated"""
template = PartParameterTemplate.objects.create(
name='My Template',
description='A template with choices',
@ -142,7 +140,6 @@ class ParameterTests(TestCase):
def test_unit_validation(self):
"""Test validation of 'units' field for PartParameterTemplate"""
# Test that valid units pass
for unit in [None, '', '%', 'mm', 'A', 'm^2', 'Pa', 'V', 'C', 'F', 'uF', 'mF', 'millifarad']:
tmp = PartParameterTemplate(name='test', units=unit)
@ -156,7 +153,6 @@ class ParameterTests(TestCase):
def test_param_unit_validation(self):
"""Test that parameters are correctly validated against template units"""
template = PartParameterTemplate.objects.create(
name='My Template',
units='m',
@ -198,7 +194,6 @@ class ParameterTests(TestCase):
def test_param_unit_conversion(self):
"""Test that parameters are correctly converted to template units"""
template = PartParameterTemplate.objects.create(
name='My Template',
units='m',
@ -263,7 +258,6 @@ class PartParameterTest(InvenTreeAPITestCase):
def test_param_template_validation(self):
"""Test that part parameter template validation routines work correctly."""
# Checkbox parameter cannot have "units" specified
with self.assertRaises(django_exceptions.ValidationError):
template = PartParameterTemplate(

View File

@ -137,7 +137,6 @@ class PartTest(TestCase):
@classmethod
def setUpTestData(cls):
"""Create some Part instances as part of init routine"""
super().setUpTestData()
cls.r1 = Part.objects.get(name='R_2K2_0805')
@ -149,7 +148,6 @@ class PartTest(TestCase):
def test_barcode_mixin(self):
"""Test the barcode mixin functionality"""
self.assertEqual(Part.barcode_model_type(), 'part')
p = Part.objects.get(pk=1)
@ -292,7 +290,6 @@ class PartTest(TestCase):
def test_related(self):
"""Unit tests for the PartRelated model"""
# Create a part relationship
# Count before creation
countbefore = PartRelated.objects.count()
@ -341,7 +338,6 @@ class PartTest(TestCase):
def test_stocktake(self):
"""Test for adding stocktake data"""
# Grab a part
p = Part.objects.all().first()
@ -419,7 +415,6 @@ class PartSettingsTest(InvenTreeTestCase):
def make_part(self):
"""Helper function to create a simple part."""
cache.clear()
part = Part.objects.create(
@ -432,7 +427,6 @@ class PartSettingsTest(InvenTreeTestCase):
def test_defaults(self):
"""Test that the default values for the part settings are correct."""
cache.clear()
self.assertTrue(part.settings.part_component_default())
@ -442,7 +436,6 @@ class PartSettingsTest(InvenTreeTestCase):
def test_initial(self):
"""Test the 'initial' default values (no default values have been set)"""
cache.clear()
part = self.make_part()

View File

@ -20,7 +20,6 @@ class PartPricingTests(InvenTreeTestCase):
def setUp(self):
"""Setup routines"""
super().setUp()
self.generate_exchange_rates()
@ -37,7 +36,6 @@ class PartPricingTests(InvenTreeTestCase):
def create_price_breaks(self):
"""Create some price breaks for the part, in various currencies"""
# First supplier part (CAD)
self.supplier_1 = company.models.Company.objects.create(
name='Supplier 1',
@ -104,7 +102,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_pricing_data(self):
"""Test link between Part and PartPricing model"""
# Initially there is no associated Pricing data
with self.assertRaises(ObjectDoesNotExist):
pricing = self.part.pricing_data
@ -130,7 +127,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_simple(self):
"""Tests for hard-coded values"""
pricing = self.part.pricing
# Add internal pricing
@ -162,7 +158,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_supplier_part_pricing(self):
"""Test for supplier part pricing"""
pricing = self.part.pricing
# Initially, no information (not yet calculated)
@ -189,7 +184,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_internal_pricing(self):
"""Tests for internal price breaks"""
# Ensure internal pricing is enabled
common.models.InvenTreeSetting.set_setting('PART_INTERNAL_PRICE', True, None)
@ -225,7 +219,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_stock_item_pricing(self):
"""Test for stock item pricing data"""
# Create a part
p = part.models.Part.objects.create(
name='Test part for pricing',
@ -273,7 +266,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_bom_pricing(self):
"""Unit test for BOM pricing calculations"""
pricing = self.part.pricing
self.assertIsNone(pricing.bom_cost_min)
@ -315,7 +307,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_purchase_pricing(self):
"""Unit tests for historical purchase pricing"""
self.create_price_breaks()
pricing = self.part.pricing
@ -380,7 +371,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_delete_with_pricing(self):
"""Test for deleting a part which has pricing information"""
# Create some pricing data
self.create_price_breaks()
@ -405,7 +395,6 @@ class PartPricingTests(InvenTreeTestCase):
def test_delete_without_pricing(self):
"""Test that we can delete a part which does not have pricing information"""
pricing = self.part.pricing
self.assertIsNone(pricing.pk)
@ -426,7 +415,6 @@ class PartPricingTests(InvenTreeTestCase):
- Create PartPricing objects where there are none
- Schedule pricing calculations for the newly created PartPricing objects
"""
from part.tasks import check_missing_pricing
# Create some parts
@ -453,7 +441,6 @@ class PartPricingTests(InvenTreeTestCase):
Essentially a series of on_delete listeners caused a new PartPricing object to be created,
but it pointed to a Part instance which was slated to be deleted inside an atomic transaction.
"""
p = part.models.Part.objects.create(
name="my part",
description="my part description",

View File

@ -183,7 +183,6 @@ class PluginActivate(UpdateAPI):
def perform_update(self, serializer):
"""Activate the plugin."""
serializer.save()

View File

@ -101,7 +101,6 @@ class BarcodeAssign(APIView):
Checks inputs and assign barcode (hash) to StockItem.
"""
data = request.data
barcode_data = data.get('barcode', None)
@ -180,7 +179,6 @@ class BarcodeUnassign(APIView):
def post(self, request, *args, **kwargs):
"""Respond to a barcode unassign POST request"""
# The following database models support assignment of third-party barcodes
supported_models = InvenTreeInternalBarcodePlugin.get_supported_barcode_models()

View File

@ -35,5 +35,4 @@ class BarcodeMixin:
Default return value is None
"""
return None

View File

@ -99,7 +99,6 @@ def process_event(plugin_slug, event, *args, **kwargs):
This function is run by the background worker process.
This function may queue multiple functions to be handled by the background worker.
"""
plugin = registry.plugins.get(plugin_slug, None)
if plugin is None: # pragma: no cover

View File

@ -14,7 +14,6 @@ class EventMixin:
Return true if you're interested in the given event, false if not.
"""
# Default implementation always returns true (backwards compatibility)
return True

View File

@ -144,7 +144,6 @@ class PanelMixin:
Returns:
Array of panels
"""
panels = []
# Construct an updated context object for template rendering

View File

@ -55,7 +55,6 @@ class LabelPrintingMixin:
def render_to_png(self, label: LabelTemplate, request=None, **kwargs):
"""Render this label to PNG format"""
# Check if pdf data is provided
pdf_data = kwargs.get('pdf_data', None)
@ -85,7 +84,6 @@ class LabelPrintingMixin:
The default implementation simply calls print_label() for each label, producing multiple single label output "jobs"
but this can be overridden by the particular plugin.
"""
try:
user = request.user
except AttributeError:
@ -152,7 +150,6 @@ class LabelPrintingMixin:
Offloads a call to the 'print_label' method (of this plugin) to a background worker.
"""
# Exclude the 'pdf_file' object - cannot be pickled
kwargs.pop('pdf_file', None)

View File

@ -30,12 +30,10 @@ class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin):
@staticmethod
def get_supported_barcode_models():
"""Returns a list of database models which support barcode functionality"""
return getModelsWithMixin(InvenTreeBarcodeMixin)
def format_matched_response(self, label, model, instance):
"""Format a response for the scanned data"""
data = {
'pk': instance.pk
}
@ -65,7 +63,6 @@ class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin):
Here we are looking for a dict object which contains a reference to a particular InvenTree database object
"""
# Create hash from raw barcode data
barcode_hash = hash_barcode(barcode_data)

View File

@ -45,7 +45,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def assign(self, data, expected_code=None):
"""Perform a 'barcode assign' request"""
return self.post(
reverse('api-barcode-link'),
data=data,
@ -54,7 +53,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def unassign(self, data, expected_code=None):
"""Perform a 'barcode unassign' request"""
return self.post(
reverse('api-barcode-unlink'),
data=data,
@ -63,7 +61,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def scan(self, data, expected_code=None):
"""Perform a 'scan' operation"""
return self.post(
reverse('api-barcode-scan'),
data=data,
@ -72,7 +69,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def test_unassign_errors(self):
"""Test various error conditions for the barcode unassign endpoint"""
# Fail without any fields provided
response = self.unassign(
{},
@ -114,7 +110,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def test_assign_to_stock_item(self):
"""Test that we can assign a unique barcode to a StockItem object"""
# Test without providing any fields
response = self.assign(
{
@ -198,7 +193,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def test_assign_to_part(self):
"""Test that we can assign a unique barcode to a Part instance"""
barcode = 'xyz-123'
self.assignRole('part.change')
@ -281,7 +275,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def test_assign_to_location(self):
"""Test that we can assign a unique barcode to a StockLocation instance"""
barcode = '555555555555555555555555'
# Assign random barcode data to a StockLocation instance
@ -338,7 +331,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def test_scan_third_party(self):
"""Test scanning of third-party barcodes"""
# First scanned barcode is for a 'third-party' barcode (which does not exist)
response = self.scan({'barcode': 'blbla=10008'}, expected_code=400)
self.assertEqual(response.data['error'], 'No match found for barcode data')
@ -367,7 +359,6 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
def test_scan_inventree(self):
"""Test scanning of first-party barcodes"""
# Scan a StockItem object (which does not exist)
response = self.scan(
{

View File

@ -133,7 +133,6 @@ class InvenTreeCoreNotificationsPlugin(SettingsContentMixin, SettingsMixin, Inve
def send_bulk(self):
"""Send the notifications out via slack."""
instance = registry.plugins.get(self.get_plugin().NAME.lower())
url = instance.get_setting('NOTIFICATION_SLACK_URL')

View File

@ -26,7 +26,6 @@ class InvenTreeCurrencyExchange(APICallMixin, CurrencyExchangeMixin, InvenTreePl
def update_exchange_rates(self, base_currency: str, symbols: list[str]) -> dict:
"""Request exchange rate data from external API"""
response = self.api_call(
'latest',
url_args={

Some files were not shown because too many files have changed in this diff Show More