mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 03:26:45 +00:00
* Squashed commit of the following: commit 52d7ff0f650bbcfa2d93ac96562b44269d3812a7 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 23:03:20 2024 +0100 fixed lookup commit 0d076eaea89dce24f08af247479b3b4dff1b4df3 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 23:03:08 2024 +0100 switched to pathlib for lookup commit 473e75eda205793769946e923748356ffd7e5b4b Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:52:30 2024 +0100 fix wrong url response commit fd74f8d703399c19cb3616ea3b2656a50cd7a6e5 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 21:14:38 2024 +0100 switched to ruff for import sorting commit f83fedbbb8de261ff8c706e179519e58e7a91064 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 21:03:14 2024 +0100 switched to single quotes everywhere commit a92442e60e23be0ff5dcf42d222b0d95823ecb9b Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:58:23 2024 +0100 added autofixes commit cc66c93136fcae8a701810a4f4f38ef3b570be61 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:56:47 2024 +0100 enable autoformat commit 1f343606ec1f2a99acf8a37b9900d78a8fb37282 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:42:14 2024 +0100 Squashed commit of the following: commit f5cf7b2e7872fc19633321713965763d1890b495 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:36:57 2024 +0100 fixed reqs commit 9d845bee98befa4e53c2ac3c783bd704369e3ad2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:32:35 2024 +0100 disable autofix/format commit aff5f271484c3500df7ddde043767c008ce4af21 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:50 2024 +0100 adjust checks commit 47271cf1efa848ec8374a0d83b5646d06fffa6e7 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:22 2024 +0100 reorder order of operations commit e1bf178b40b3f0d2d59ba92209156c43095959d2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:01:09 2024 +0100 adapted ruff settings to better fit code base commit ad7d88a6f4f15c9552522131c4e207256fc2bbf6 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:59:45 2024 +0100 auto fixed docstring commit a2e54a760e17932dbbc2de0dec23906107f2cda9 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:46:35 2024 +0100 fix getattr useage commit cb80c73bc6c0be7f5d2ed3cc9b2ac03fdefd5c41 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:25:09 2024 +0100 fix requirements file commit b7780bbd21a32007f3b0ce495b519bf59bb19bf5 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:42:28 2024 +0100 fix removed sections commit 71f1681f55c15f62c16c1d7f30a745adc496db97 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:41:21 2024 +0100 fix djlint syntax commit a0bcf1bccef8a8ffd482f38e2063bc9066e1d759 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:35:28 2024 +0100 remove flake8 from code base commit 22475b31cc06919785be046e007915e43f356793 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:34:56 2024 +0100 remove flake8 from code base commit 0413350f14773ac6161473e0cfb069713c13c691 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:39 2024 +0100 moved ruff section commit d90c48a0bf98befdfacbbb093ee56cdb28afb40d Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:24 2024 +0100 move djlint config to pyproject commit c5ce55d5119bf2e35e429986f62f875c86178ae1 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:20:39 2024 +0100 added isort again commit 42a41d23afc280d4ee6f0e640148abc6f460f05a Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:19:02 2024 +0100 move config section commit 85692331816348cb1145570340d1f6488a8265cc Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:17:52 2024 +0100 fix codespell error commit 2897c6704d1311a800ce5aa47878d96d6980b377 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 17:29:21 2024 +0100 replaced flake8 with ruff mostly for speed improvements * enable docstring checks * fix docstrings * fixed D417 Missing argument description * Squashed commit of the following: commit d3b795824b5d6d1c0eda67150b45b5cd672b3f6b Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:56:17 2024 +0100 fixed source path commit 0bac0c19b88897a19d5c995e4ff50427718b827e Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:47:53 2024 +0100 fixed req commit 9f61f01d9cc01f1fb7123102f3658c890469b8ce Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:45:18 2024 +0100 added missing toml req commit 91b71ed24a6761b629768d0ad8829fec2819a966 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:49:50 2024 +0100 moved isort config commit 12460b04196b12d0272d40552402476d5492fea5 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:43:22 2024 +0100 remove flake8 section from setup.cfg commit f5cf7b2e7872fc19633321713965763d1890b495 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:36:57 2024 +0100 fixed reqs commit 9d845bee98befa4e53c2ac3c783bd704369e3ad2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:32:35 2024 +0100 disable autofix/format commit aff5f271484c3500df7ddde043767c008ce4af21 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:50 2024 +0100 adjust checks commit 47271cf1efa848ec8374a0d83b5646d06fffa6e7 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:22 2024 +0100 reorder order of operations commit e1bf178b40b3f0d2d59ba92209156c43095959d2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:01:09 2024 +0100 adapted ruff settings to better fit code base commit ad7d88a6f4f15c9552522131c4e207256fc2bbf6 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:59:45 2024 +0100 auto fixed docstring commit a2e54a760e17932dbbc2de0dec23906107f2cda9 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:46:35 2024 +0100 fix getattr useage commit cb80c73bc6c0be7f5d2ed3cc9b2ac03fdefd5c41 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:25:09 2024 +0100 fix requirements file commit b7780bbd21a32007f3b0ce495b519bf59bb19bf5 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:42:28 2024 +0100 fix removed sections commit 71f1681f55c15f62c16c1d7f30a745adc496db97 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:41:21 2024 +0100 fix djlint syntax commit a0bcf1bccef8a8ffd482f38e2063bc9066e1d759 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:35:28 2024 +0100 remove flake8 from code base commit 22475b31cc06919785be046e007915e43f356793 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:34:56 2024 +0100 remove flake8 from code base commit 0413350f14773ac6161473e0cfb069713c13c691 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:39 2024 +0100 moved ruff section commit d90c48a0bf98befdfacbbb093ee56cdb28afb40d Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:24 2024 +0100 move djlint config to pyproject commit c5ce55d5119bf2e35e429986f62f875c86178ae1 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:20:39 2024 +0100 added isort again commit 42a41d23afc280d4ee6f0e640148abc6f460f05a Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:19:02 2024 +0100 move config section commit 85692331816348cb1145570340d1f6488a8265cc Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:17:52 2024 +0100 fix codespell error commit 2897c6704d1311a800ce5aa47878d96d6980b377 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 17:29:21 2024 +0100 replaced flake8 with ruff mostly for speed improvements * fix pyproject * make docstrings more uniform * auto-format * fix order * revert url change
619 lines
18 KiB
Python
619 lines
18 KiB
Python
"""Admin class definitions for the 'part' app."""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from import_export import widgets
|
|
from import_export.admin import ImportExportModelAdmin
|
|
from import_export.fields import Field
|
|
|
|
from company.models import SupplierPart
|
|
from InvenTree.admin import InvenTreeResource
|
|
from part import models
|
|
from stock.models import StockLocation
|
|
|
|
|
|
class PartResource(InvenTreeResource):
|
|
"""Class for managing Part data import/export."""
|
|
|
|
class Meta:
|
|
"""Metaclass options."""
|
|
|
|
model = models.Part
|
|
skip_unchanged = True
|
|
report_skipped = False
|
|
clean_model_instances = True
|
|
exclude = [
|
|
'bom_checksum',
|
|
'bom_checked_by',
|
|
'bom_checked_date',
|
|
'lft',
|
|
'rght',
|
|
'tree_id',
|
|
'level',
|
|
'metadata',
|
|
'barcode_data',
|
|
'barcode_hash',
|
|
]
|
|
|
|
id = Field(attribute='pk', column_name=_('Part ID'), widget=widgets.IntegerWidget())
|
|
name = Field(
|
|
attribute='name', column_name=_('Part Name'), widget=widgets.CharWidget()
|
|
)
|
|
description = Field(
|
|
attribute='description',
|
|
column_name=_('Part Description'),
|
|
widget=widgets.CharWidget(),
|
|
)
|
|
IPN = Field(attribute='IPN', column_name=_('IPN'), widget=widgets.CharWidget())
|
|
revision = Field(
|
|
attribute='revision', column_name=_('Revision'), widget=widgets.CharWidget()
|
|
)
|
|
keywords = Field(
|
|
attribute='keywords', column_name=_('Keywords'), widget=widgets.CharWidget()
|
|
)
|
|
link = Field(attribute='link', column_name=_('Link'), widget=widgets.CharWidget())
|
|
units = Field(
|
|
attribute='units', column_name=_('Units'), widget=widgets.CharWidget()
|
|
)
|
|
notes = Field(attribute='notes', column_name=_('Notes'))
|
|
image = Field(attribute='image', column_name=_('Part Image'), readonly=True)
|
|
category = Field(
|
|
attribute='category',
|
|
column_name=_('Category ID'),
|
|
widget=widgets.ForeignKeyWidget(models.PartCategory),
|
|
)
|
|
category_name = Field(
|
|
attribute='category__name', column_name=_('Category Name'), readonly=True
|
|
)
|
|
default_location = Field(
|
|
attribute='default_location',
|
|
column_name=_('Default Location ID'),
|
|
widget=widgets.ForeignKeyWidget(StockLocation),
|
|
)
|
|
default_supplier = Field(
|
|
attribute='default_supplier',
|
|
column_name=_('Default Supplier ID'),
|
|
widget=widgets.ForeignKeyWidget(SupplierPart),
|
|
)
|
|
variant_of = Field(
|
|
attribute='variant_of',
|
|
column_name=_('Variant Of'),
|
|
widget=widgets.ForeignKeyWidget(models.Part),
|
|
)
|
|
minimum_stock = Field(attribute='minimum_stock', column_name=_('Minimum Stock'))
|
|
|
|
# Part Attributes
|
|
active = Field(
|
|
attribute='active', column_name=_('Active'), widget=widgets.BooleanWidget()
|
|
)
|
|
assembly = Field(
|
|
attribute='assembly', column_name=_('Assembly'), widget=widgets.BooleanWidget()
|
|
)
|
|
component = Field(
|
|
attribute='component',
|
|
column_name=_('Component'),
|
|
widget=widgets.BooleanWidget(),
|
|
)
|
|
purchaseable = Field(
|
|
attribute='purchaseable',
|
|
column_name=_('Purchaseable'),
|
|
widget=widgets.BooleanWidget(),
|
|
)
|
|
salable = Field(
|
|
attribute='salable', column_name=_('Salable'), widget=widgets.BooleanWidget()
|
|
)
|
|
is_template = Field(
|
|
attribute='is_template',
|
|
column_name=_('Template'),
|
|
widget=widgets.BooleanWidget(),
|
|
)
|
|
trackable = Field(
|
|
attribute='trackable',
|
|
column_name=_('Trackable'),
|
|
widget=widgets.BooleanWidget(),
|
|
)
|
|
virtual = Field(
|
|
attribute='virtual', column_name=_('Virtual'), widget=widgets.BooleanWidget()
|
|
)
|
|
|
|
# Extra calculated meta-data (readonly)
|
|
suppliers = Field(
|
|
attribute='supplier_count', column_name=_('Suppliers'), readonly=True
|
|
)
|
|
in_stock = Field(
|
|
attribute='total_stock',
|
|
column_name=_('In Stock'),
|
|
readonly=True,
|
|
widget=widgets.IntegerWidget(),
|
|
)
|
|
on_order = Field(
|
|
attribute='on_order',
|
|
column_name=_('On Order'),
|
|
readonly=True,
|
|
widget=widgets.IntegerWidget(),
|
|
)
|
|
used_in = Field(
|
|
attribute='used_in_count',
|
|
column_name=_('Used In'),
|
|
readonly=True,
|
|
widget=widgets.IntegerWidget(),
|
|
)
|
|
allocated = Field(
|
|
attribute='allocation_count',
|
|
column_name=_('Allocated'),
|
|
readonly=True,
|
|
widget=widgets.IntegerWidget(),
|
|
)
|
|
building = Field(
|
|
attribute='quantity_being_built',
|
|
column_name=_('Building'),
|
|
readonly=True,
|
|
widget=widgets.IntegerWidget(),
|
|
)
|
|
min_cost = Field(
|
|
attribute='pricing__overall_min', column_name=_('Minimum Cost'), readonly=True
|
|
)
|
|
max_cost = Field(
|
|
attribute='pricing__overall_max', column_name=_('Maximum Cost'), readonly=True
|
|
)
|
|
|
|
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:
|
|
return float(min_cost.amount)
|
|
|
|
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:
|
|
return float(max_cost.amount)
|
|
|
|
def get_queryset(self):
|
|
"""Prefetch related data for quicker access."""
|
|
query = super().get_queryset()
|
|
query = query.prefetch_related(
|
|
'category',
|
|
'used_in',
|
|
'builds',
|
|
'supplier_parts__purchase_order_line_items',
|
|
'stock_items__allocations',
|
|
)
|
|
|
|
return query
|
|
|
|
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)
|
|
models.Part.objects.rebuild()
|
|
|
|
|
|
class PartImportResource(InvenTreeResource):
|
|
"""Class for managing Part data import/export."""
|
|
|
|
class Meta(PartResource.Meta):
|
|
"""Metaclass options."""
|
|
|
|
skip_unchanged = True
|
|
report_skipped = False
|
|
clean_model_instances = True
|
|
exclude = [
|
|
'id',
|
|
'category__name',
|
|
'creation_date',
|
|
'creation_user',
|
|
'pricing__overall_min',
|
|
'pricing__overall_max',
|
|
'bom_checksum',
|
|
'bom_checked_by',
|
|
'bom_checked_date',
|
|
'lft',
|
|
'rght',
|
|
'tree_id',
|
|
'level',
|
|
'metadata',
|
|
'barcode_data',
|
|
'barcode_hash',
|
|
]
|
|
|
|
|
|
class PartParameterInline(admin.TabularInline):
|
|
"""Inline for part parameter data."""
|
|
|
|
model = models.PartParameter
|
|
|
|
|
|
class PartAdmin(ImportExportModelAdmin):
|
|
"""Admin class for the Part model."""
|
|
|
|
resource_class = PartResource
|
|
|
|
list_display = ('full_name', 'description', 'total_stock', 'category')
|
|
|
|
list_filter = ('active', 'assembly', 'is_template', 'virtual')
|
|
|
|
search_fields = (
|
|
'name',
|
|
'description',
|
|
'category__name',
|
|
'category__description',
|
|
'IPN',
|
|
)
|
|
|
|
autocomplete_fields = [
|
|
'variant_of',
|
|
'category',
|
|
'default_location',
|
|
'default_supplier',
|
|
]
|
|
|
|
inlines = [PartParameterInline]
|
|
|
|
|
|
class PartPricingAdmin(admin.ModelAdmin):
|
|
"""Admin class for PartPricing model."""
|
|
|
|
list_display = ('part', 'overall_min', 'overall_max')
|
|
|
|
autcomplete_fields = ['part']
|
|
|
|
|
|
class PartStocktakeAdmin(admin.ModelAdmin):
|
|
"""Admin class for PartStocktake model."""
|
|
|
|
list_display = ['part', 'date', 'quantity', 'user']
|
|
|
|
|
|
class PartStocktakeReportAdmin(admin.ModelAdmin):
|
|
"""Admin class for PartStocktakeReport model."""
|
|
|
|
list_display = ['date', 'user']
|
|
|
|
|
|
class PartCategoryResource(InvenTreeResource):
|
|
"""Class for managing PartCategory data import/export."""
|
|
|
|
class Meta:
|
|
"""Metaclass options."""
|
|
|
|
model = models.PartCategory
|
|
skip_unchanged = True
|
|
report_skipped = False
|
|
clean_model_instances = True
|
|
|
|
exclude = [
|
|
# Exclude MPTT internal model fields
|
|
'lft',
|
|
'rght',
|
|
'tree_id',
|
|
'level',
|
|
'metadata',
|
|
'icon',
|
|
]
|
|
|
|
id = Field(
|
|
attribute='pk', column_name=_('Category ID'), widget=widgets.IntegerWidget()
|
|
)
|
|
name = Field(attribute='name', column_name=_('Category Name'))
|
|
description = Field(attribute='description', column_name=_('Description'))
|
|
parent = Field(
|
|
attribute='parent',
|
|
column_name=_('Parent ID'),
|
|
widget=widgets.ForeignKeyWidget(models.PartCategory),
|
|
)
|
|
parent_name = Field(
|
|
attribute='parent__name', column_name=_('Parent Name'), readonly=True
|
|
)
|
|
default_location = Field(
|
|
attribute='default_location',
|
|
column_name=_('Default Location ID'),
|
|
widget=widgets.ForeignKeyWidget(StockLocation),
|
|
)
|
|
default_keywords = Field(attribute='default_keywords', column_name=_('Keywords'))
|
|
pathstring = Field(attribute='pathstring', column_name=_('Category Path'))
|
|
|
|
# Calculated fields
|
|
parts = Field(
|
|
attribute='item_count',
|
|
column_name=_('Parts'),
|
|
widget=widgets.IntegerWidget(),
|
|
readonly=True,
|
|
)
|
|
|
|
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)
|
|
models.PartCategory.objects.rebuild()
|
|
|
|
|
|
class PartCategoryAdmin(ImportExportModelAdmin):
|
|
"""Admin class for the PartCategory model."""
|
|
|
|
resource_class = PartCategoryResource
|
|
|
|
list_display = ('name', 'pathstring', 'description')
|
|
|
|
search_fields = ('name', 'description')
|
|
|
|
autocomplete_fields = ('parent', 'default_location')
|
|
|
|
|
|
class PartRelatedAdmin(admin.ModelAdmin):
|
|
"""Class to manage PartRelated objects."""
|
|
|
|
autocomplete_fields = ('part_1', 'part_2')
|
|
|
|
|
|
class PartAttachmentAdmin(admin.ModelAdmin):
|
|
"""Admin class for the PartAttachment model."""
|
|
|
|
list_display = ('part', 'attachment', 'comment')
|
|
|
|
autocomplete_fields = ('part',)
|
|
|
|
|
|
class PartTestTemplateAdmin(admin.ModelAdmin):
|
|
"""Admin class for the PartTestTemplate model."""
|
|
|
|
list_display = ('part', 'test_name', 'required')
|
|
|
|
autocomplete_fields = ('part',)
|
|
|
|
|
|
class BomItemResource(InvenTreeResource):
|
|
"""Class for managing BomItem data import/export."""
|
|
|
|
class Meta:
|
|
"""Metaclass options."""
|
|
|
|
model = models.BomItem
|
|
skip_unchanged = True
|
|
report_skipped = False
|
|
clean_model_instances = True
|
|
|
|
exclude = ['checksum', 'id', 'part', 'sub_part', 'validated']
|
|
|
|
level = Field(attribute='level', column_name=_('BOM Level'), readonly=True)
|
|
|
|
bom_id = Field(
|
|
attribute='pk', column_name=_('BOM Item ID'), widget=widgets.IntegerWidget()
|
|
)
|
|
|
|
# ID of the parent part
|
|
parent_part_id = Field(
|
|
attribute='part',
|
|
column_name=_('Parent ID'),
|
|
widget=widgets.ForeignKeyWidget(models.Part),
|
|
)
|
|
parent_part_ipn = Field(
|
|
attribute='part__IPN', column_name=_('Parent IPN'), readonly=True
|
|
)
|
|
parent_part_name = Field(
|
|
attribute='part__name', column_name=_('Parent Name'), readonly=True
|
|
)
|
|
part_id = Field(
|
|
attribute='sub_part',
|
|
column_name=_('Part ID'),
|
|
widget=widgets.ForeignKeyWidget(models.Part),
|
|
)
|
|
part_ipn = Field(
|
|
attribute='sub_part__IPN', column_name=_('Part IPN'), readonly=True
|
|
)
|
|
part_name = Field(
|
|
attribute='sub_part__name', column_name=_('Part Name'), readonly=True
|
|
)
|
|
part_description = Field(
|
|
attribute='sub_part__description', column_name=_('Description'), readonly=True
|
|
)
|
|
quantity = Field(attribute='quantity', column_name=_('Quantity'))
|
|
reference = Field(attribute='reference', column_name=_('Reference'))
|
|
note = Field(attribute='note', column_name=_('Note'))
|
|
min_cost = Field(
|
|
attribute='sub_part__pricing__overall_min',
|
|
column_name=_('Minimum Price'),
|
|
readonly=True,
|
|
)
|
|
max_cost = Field(
|
|
attribute='sub_part__pricing__overall_max',
|
|
column_name=_('Maximum Price'),
|
|
readonly=True,
|
|
)
|
|
|
|
sub_assembly = Field(
|
|
attribute='sub_part__assembly', column_name=_('Assembly'), readonly=True
|
|
)
|
|
|
|
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:
|
|
return float(min_price.amount) * float(item.quantity)
|
|
|
|
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:
|
|
return float(max_price.amount) * float(item.quantity)
|
|
|
|
def dehydrate_quantity(self, item):
|
|
"""Special consideration for the 'quantity' field on data export. We do not want a spreadsheet full of "1.0000" (we'd rather "1").
|
|
|
|
Ref: https://django-import-export.readthedocs.io/en/latest/getting_started.html#advanced-data-manipulation-on-export
|
|
"""
|
|
return float(item.quantity)
|
|
|
|
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)
|
|
|
|
def get_fields(self, **kwargs):
|
|
"""If we are exporting for the purposes of generating a 'bom-import' template, there are some fields which we are not interested in."""
|
|
fields = super().get_fields(**kwargs)
|
|
|
|
is_importing = getattr(self, 'is_importing', False)
|
|
include_pricing = getattr(self, 'include_pricing', False)
|
|
|
|
to_remove = ['metadata']
|
|
|
|
if is_importing or not include_pricing:
|
|
# Remove pricing fields in this instance
|
|
to_remove += [
|
|
'sub_part__pricing__overall_min',
|
|
'sub_part__pricing__overall_max',
|
|
]
|
|
|
|
if is_importing:
|
|
to_remove += [
|
|
'level',
|
|
'pk',
|
|
'part',
|
|
'part__IPN',
|
|
'part__name',
|
|
'sub_part__name',
|
|
'sub_part__description',
|
|
'sub_part__assembly',
|
|
]
|
|
|
|
idx = 0
|
|
|
|
while idx < len(fields):
|
|
if fields[idx].attribute in to_remove:
|
|
del fields[idx]
|
|
else:
|
|
idx += 1
|
|
|
|
return fields
|
|
|
|
|
|
class BomItemAdmin(ImportExportModelAdmin):
|
|
"""Admin class for the BomItem model."""
|
|
|
|
resource_class = BomItemResource
|
|
|
|
list_display = ('part', 'sub_part', 'quantity')
|
|
|
|
search_fields = (
|
|
'part__name',
|
|
'part__description',
|
|
'sub_part__name',
|
|
'sub_part__description',
|
|
)
|
|
|
|
autocomplete_fields = ('part', 'sub_part')
|
|
|
|
|
|
class ParameterTemplateResource(InvenTreeResource):
|
|
"""Class for managing ParameterTemplate import/export."""
|
|
|
|
# The following fields will be converted from None to ''
|
|
CONVERT_NULL_FIELDS = ['choices', 'units']
|
|
|
|
class Meta:
|
|
"""Metaclass options."""
|
|
|
|
model = models.PartParameterTemplate
|
|
skip_unchanged = True
|
|
report_skipped = False
|
|
clean_model_instances = True
|
|
|
|
exclude = ['metadata']
|
|
|
|
|
|
class ParameterTemplateAdmin(ImportExportModelAdmin):
|
|
"""Admin class for the PartParameterTemplate model."""
|
|
|
|
resource_class = ParameterTemplateResource
|
|
|
|
list_display = ('name', 'units')
|
|
|
|
search_fields = ('name', 'units')
|
|
|
|
|
|
class ParameterResource(InvenTreeResource):
|
|
"""Class for managing PartParameter data import/export."""
|
|
|
|
class Meta:
|
|
"""Metaclass options."""
|
|
|
|
model = models.PartParameter
|
|
skip_unchanged = True
|
|
report_skipped = False
|
|
clean_model_instance = True
|
|
|
|
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(models.Part))
|
|
|
|
part_name = Field(attribute='part__name', readonly=True)
|
|
|
|
template = Field(
|
|
attribute='template',
|
|
widget=widgets.ForeignKeyWidget(models.PartParameterTemplate),
|
|
)
|
|
|
|
template_name = Field(attribute='template__name', readonly=True)
|
|
|
|
|
|
class ParameterAdmin(ImportExportModelAdmin):
|
|
"""Admin class for the PartParameter model."""
|
|
|
|
resource_class = ParameterResource
|
|
|
|
list_display = ('part', 'template', 'data')
|
|
|
|
autocomplete_fields = ('part', 'template')
|
|
|
|
|
|
class PartCategoryParameterAdmin(admin.ModelAdmin):
|
|
"""Admin class for the PartCategoryParameterTemplate model."""
|
|
|
|
autocomplete_fields = ('category', 'parameter_template')
|
|
|
|
|
|
class PartSellPriceBreakAdmin(admin.ModelAdmin):
|
|
"""Admin class for the PartSellPriceBreak model."""
|
|
|
|
class Meta:
|
|
"""Metaclass options."""
|
|
|
|
model = models.PartSellPriceBreak
|
|
|
|
list_display = ('part', 'quantity', 'price')
|
|
|
|
|
|
class PartInternalPriceBreakAdmin(admin.ModelAdmin):
|
|
"""Admin class for the PartInternalPriceBreak model."""
|
|
|
|
class Meta:
|
|
"""Metaclass options."""
|
|
|
|
model = models.PartInternalPriceBreak
|
|
|
|
list_display = ('part', 'quantity', 'price')
|
|
|
|
autocomplete_fields = ('part',)
|
|
|
|
|
|
admin.site.register(models.Part, PartAdmin)
|
|
admin.site.register(models.PartCategory, PartCategoryAdmin)
|
|
admin.site.register(models.PartRelated, PartRelatedAdmin)
|
|
admin.site.register(models.PartAttachment, PartAttachmentAdmin)
|
|
admin.site.register(models.BomItem, BomItemAdmin)
|
|
admin.site.register(models.PartParameterTemplate, ParameterTemplateAdmin)
|
|
admin.site.register(models.PartParameter, ParameterAdmin)
|
|
admin.site.register(models.PartCategoryParameterTemplate, PartCategoryParameterAdmin)
|
|
admin.site.register(models.PartTestTemplate, PartTestTemplateAdmin)
|
|
admin.site.register(models.PartSellPriceBreak, PartSellPriceBreakAdmin)
|
|
admin.site.register(models.PartInternalPriceBreak, PartInternalPriceBreakAdmin)
|
|
admin.site.register(models.PartPricing, PartPricingAdmin)
|
|
admin.site.register(models.PartStocktake, PartStocktakeAdmin)
|
|
admin.site.register(models.PartStocktakeReport, PartStocktakeReportAdmin)
|