2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-01 04:56:45 +00:00
Matthias Mair d36cf358f8
Bump to Dj 4.x (#6173)
* bump to dj >4.2

* switch to experimental git release

* bump django-import_export

* bump mptt

* replace is_ajax, which was removed
https://docs.djangoproject.com/en/3.1/releases/3.1/#id2

* Save before accessing values in m2m/fk fields

* move plugin init

* use dev version of django for fix

* update deps

* fix deps

* use django smaller 4.2

* fix reqs

* fix merge

* remove moved code

* another merge fix

* fix ajax call

* fix refs

* change python min v

* fix deps

* bump deps

* fix deps

* pin pillow

* dj 4.1 upgrades

* make diff smaller

* bump all deps

* drop down to py3.9

* bump versions

* merge fix

* fix diff

* more bumping

* diff cleanup

* bump deps

* fix reqs

* use accurate state for model migrations
using apps the historically correct state is used

* try import

* added more logs

* add try here too

* clean up rebuilds

* Dj 4.2 (#161)

* autochanges

* bump

* fix diff

* fix diff

* bump deps

* fix req

* remove select_related to test error influence

* switch to mptt fork

* fix reqs for upstream

* move tracking ensureance into save

* optimize check frequency

* use psycopg instead of psycopg2

* fix header

* just use the values

* switch to dj < 4.2

* fix req

* another req fix

* switch to 4.2 again

* fix merge error

* Check for null pk in calculate_total_price

Cannot access self.lines if pk is Null

* use patched mptt

* try psycopg2 again

* Remove tree rebuild from migrations

* Prevent notify_users if importing or migrating

* Add order_by() to subquery annotations

- Ref: https://stackoverflow.com/a/629691

* Update stock filters

- Append order_by()

* fix error if running without timezones in testing

* add logging to figure this out

* remove tz from self.creation if TZ is off

* add tz?

* move around?

* only run the test i am trying to figure out
not reproducible on my machine

* only run the test i am trying to figure out
not reproducible on my machine

* run all tests again

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2024-02-06 13:30:50 +11:00

323 lines
9.2 KiB
Python

"""Admin for stock app."""
from django.contrib import admin
from django.db.models import Count
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 build.models import Build
from company.models import Company, SupplierPart
from InvenTree.admin import InvenTreeResource
from order.models import PurchaseOrder, SalesOrder
from part.models import Part
from .models import (
StockItem,
StockItemAttachment,
StockItemTestResult,
StockItemTracking,
StockLocation,
StockLocationType,
)
class LocationResource(InvenTreeResource):
"""Class for managing StockLocation data import/export."""
class Meta:
"""Metaclass options."""
model = StockLocation
skip_unchanged = True
report_skipped = False
clean_model_instances = True
exclude = [
# Exclude MPTT internal model fields
'lft',
'rght',
'tree_id',
'level',
'metadata',
'barcode_data',
'barcode_hash',
'owner',
'icon',
]
id = Field(
attribute='id', column_name=_('Location ID'), widget=widgets.IntegerWidget()
)
name = Field(attribute='name', column_name=_('Location Name'))
description = Field(attribute='description', column_name=_('Description'))
parent = Field(
attribute='parent',
column_name=_('Parent ID'),
widget=widgets.ForeignKeyWidget(StockLocation),
)
parent_name = Field(
attribute='parent__name', column_name=_('Parent Name'), readonly=True
)
pathstring = Field(attribute='pathstring', column_name=_('Location Path'))
# Calculated fields
items = Field(
attribute='item_count',
column_name=_('Stock Items'),
widget=widgets.IntegerWidget(),
readonly=True,
)
def after_import(self, dataset, result, using_transactions, dry_run, **kwargs):
"""Rebuild after import to keep tree intact."""
super().after_import(dataset, result, using_transactions, dry_run, **kwargs)
# Rebuild the StockLocation tree(s)
StockLocation.objects.rebuild()
class LocationInline(admin.TabularInline):
"""Inline for sub-locations."""
model = StockLocation
@admin.register(StockLocation)
class LocationAdmin(ImportExportModelAdmin):
"""Admin class for Location."""
resource_class = LocationResource
list_display = ('name', 'pathstring', 'description')
search_fields = ('name', 'description')
inlines = [LocationInline]
autocomplete_fields = ['parent']
@admin.register(StockLocationType)
class LocationTypeAdmin(admin.ModelAdmin):
"""Admin class for StockLocationType."""
list_display = ('name', 'description', 'icon', 'location_count')
readonly_fields = ('location_count',)
def get_queryset(self, request):
"""Annotate queryset to fetch location count."""
return (
super()
.get_queryset(request)
.annotate(location_count=Count('stock_locations'))
)
def location_count(self, obj):
"""Returns the number of locations this location type is assigned to."""
return obj.location_count
class StockItemResource(InvenTreeResource):
"""Class for managing StockItem data import/export."""
class Meta:
"""Metaclass options."""
model = StockItem
skip_unchanged = True
report_skipped = False
clean_model_instance = True
exclude = [
# Exclude MPTT internal model fields
'lft',
'rght',
'tree_id',
'level',
# Exclude internal fields
'serial_int',
'metadata',
'barcode_hash',
'barcode_data',
'owner',
]
id = Field(
attribute='pk', column_name=_('Stock Item ID'), widget=widgets.IntegerWidget()
)
part = Field(
attribute='part',
column_name=_('Part ID'),
widget=widgets.ForeignKeyWidget(Part),
)
part_name = Field(
attribute='part__full_name', column_name=_('Part Name'), readonly=True
)
quantity = Field(
attribute='quantity', column_name=_('Quantity'), widget=widgets.DecimalWidget()
)
serial = Field(attribute='serial', column_name=_('Serial'))
batch = Field(attribute='batch', column_name=_('Batch'))
status_label = Field(
attribute='status_label', column_name=_('Status'), readonly=True
)
status = Field(
attribute='status', column_name=_('Status Code'), widget=widgets.IntegerWidget()
)
location = Field(
attribute='location',
column_name=_('Location ID'),
widget=widgets.ForeignKeyWidget(StockLocation),
)
location_name = Field(
attribute='location__name', column_name=_('Location Name'), readonly=True
)
supplier_part = Field(
attribute='supplier_part',
column_name=_('Supplier Part ID'),
widget=widgets.ForeignKeyWidget(SupplierPart),
)
supplier = Field(
attribute='supplier_part__supplier__id',
column_name=_('Supplier ID'),
readonly=True,
widget=widgets.IntegerWidget(),
)
supplier_name = Field(
attribute='supplier_part__supplier__name',
column_name=_('Supplier Name'),
readonly=True,
)
customer = Field(
attribute='customer',
column_name=_('Customer ID'),
widget=widgets.ForeignKeyWidget(Company),
)
belongs_to = Field(
attribute='belongs_to',
column_name=_('Installed In'),
widget=widgets.ForeignKeyWidget(StockItem),
)
build = Field(
attribute='build',
column_name=_('Build ID'),
widget=widgets.ForeignKeyWidget(Build),
)
parent = Field(
attribute='parent',
column_name=_('Parent ID'),
widget=widgets.ForeignKeyWidget(StockItem),
)
sales_order = Field(
attribute='sales_order',
column_name=_('Sales Order ID'),
widget=widgets.ForeignKeyWidget(SalesOrder),
)
purchase_order = Field(
attribute='purchase_order',
column_name=_('Purchase Order ID'),
widget=widgets.ForeignKeyWidget(PurchaseOrder),
)
packaging = Field(attribute='packaging', column_name=_('Packaging'))
link = Field(attribute='link', column_name=_('Link'))
notes = Field(attribute='notes', column_name=_('Notes'))
# Status fields (note that IntegerWidget exports better to excel than BooleanWidget)
is_building = Field(
attribute='is_building',
column_name=_('Building'),
widget=widgets.IntegerWidget(),
)
review_needed = Field(
attribute='review_needed',
column_name=_('Review Needed'),
widget=widgets.IntegerWidget(),
)
delete_on_deplete = Field(
attribute='delete_on_deplete',
column_name=_('Delete on Deplete'),
widget=widgets.IntegerWidget(),
)
# Date management
updated = Field(
attribute='updated', column_name=_('Last Updated'), widget=widgets.DateWidget()
)
stocktake_date = Field(
attribute='stocktake_date',
column_name=_('Stocktake'),
widget=widgets.DateWidget(),
)
expiry_date = Field(
attribute='expiry_date',
column_name=_('Expiry Date'),
widget=widgets.DateWidget(),
)
def dehydrate_purchase_price(self, item):
"""Render purchase pric as float."""
if item.purchase_price is not None:
return float(item.purchase_price.amount)
def after_import(self, dataset, result, using_transactions, dry_run, **kwargs):
"""Rebuild after import to keep tree intact."""
super().after_import(dataset, result, using_transactions, dry_run, **kwargs)
# Rebuild the StockItem tree(s)
StockItem.objects.rebuild()
@admin.register(StockItem)
class StockItemAdmin(ImportExportModelAdmin):
"""Admin class for StockItem."""
resource_class = StockItemResource
list_display = ('part', 'quantity', 'location', 'status', 'updated')
# A list of search fields which can be used for lookup on matching 'autocomplete' fields
search_fields = ['part__name', 'part__description', 'serial', 'batch']
autocomplete_fields = [
'belongs_to',
'build',
'customer',
'location',
'parent',
'part',
'purchase_order',
'sales_order',
'stocktake_user',
'supplier_part',
]
@admin.register(StockItemAttachment)
class StockAttachmentAdmin(admin.ModelAdmin):
"""Admin class for StockAttachment."""
list_display = ('stock_item', 'attachment', 'comment')
autocomplete_fields = ['stock_item']
@admin.register(StockItemTracking)
class StockTrackingAdmin(ImportExportModelAdmin):
"""Admin class for StockTracking."""
list_display = ('item', 'date', 'label')
autocomplete_fields = ['item']
@admin.register(StockItemTestResult)
class StockItemTestResultAdmin(admin.ModelAdmin):
"""Admin class for StockItemTestResult."""
list_display = ('stock_item', 'test', 'result', 'value')
autocomplete_fields = ['stock_item']