Create new file bom.py for BOM helper functions

- New class for managing BOM upload
This commit is contained in:
Oliver Walters 2019-06-27 22:46:11 +10:00
parent 65c7454124
commit 45d16f2c42
3 changed files with 102 additions and 55 deletions

View File

@ -92,33 +92,6 @@ def MakeBarcode(object_type, object_id, object_url, data={}):
return json.dumps(data, sort_keys=True)
def IsValidSpreadsheetFormat(fmt):
""" Test if a file format specifier is in the valid list of spreadsheet file formats """
return fmt.lower() in ['csv', 'xls', 'xlsx', 'tsv']
def MakeBomTemplate(fmt):
""" Generate a Bill of Materials upload template file (for user download) """
if not IsValidSpreadsheetFormat(fmt):
fmt = 'csv'
fields = [
'Part',
'Quantity',
'Overage',
'Reference',
'Notes'
]
data = tablib.Dataset(headers=fields).export(fmt)
filename = 'InvenTree_BOM_Template.' + fmt
return DownloadFile(data, filename)
def DownloadFile(data, filename, content_type='application/text'):
""" Create a dynamic file for the user to download.

92
InvenTree/part/bom.py Normal file
View File

@ -0,0 +1,92 @@
"""
Functionality for Bill of Material (BOM) management.
Primarily BOM upload tools.
"""
from fuzzywuzzy import fuzz
import tablib
import os
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from .models import Part, BomItem
from InvenTree.helpers import DownloadFile
def IsValidBOMFormat(fmt):
""" Test if a file format specifier is in the valid list of BOM file formats """
return fmt.strip().lower() in ['csv', 'xls', 'xlsx', 'tsv']
def MakeBomTemplate(fmt):
""" Generate a Bill of Materials upload template file (for user download) """
fmt = fmt.strip().lower()
if not IsValidBOMFormat(fmt):
fmt = 'csv'
fields = [
'Part',
'Quantity',
'Overage',
'Reference',
'Notes'
]
data = tablib.Dataset(headers=fields).export(fmt)
filename = 'InvenTree_BOM_Template.' + fmt
return DownloadFile(data, filename)
class BomUploadManager:
""" Class for managing an uploaded BOM file """
# Fields which are absolutely necessary for valid upload
REQUIRED_HEADERS = [
'Part',
'Quantity',
]
# Fields which are not necessary but can be populated
USEFUL_HEADERS = [
'REFERENCE',
'OVERAGE',
'NOTES'
]
def __init__(self, bom_file, starting_row=2):
""" Initialize the BomUpload class with a user-uploaded file object """
self.starting_row = starting_row
print("Starting on row", starting_row)
self.process(bom_file)
def process(self, bom_file):
""" Process a BOM file """
ext = os.path.splitext(bom_file.name)[-1].lower()
if ext in ['.csv', '.tsv', ]:
# These file formats need string decoding
raw_data = bom_file.read().decode('utf-8')
elif ext in ['.xls', '.xlsx']:
raw_data = bom_file.read()
else:
raise ValidationError({'bom_file': _('Unsupported file format: {f}'.format(f=ext))})
try:
bom_data = tablib.Dataset().load(raw_data)
except tablib.UnsupportedFormat:
raise ValidationError({'bom_file': _('Error reading BOM file (invalid data)')})
# Now we have BOM data in memory!
headers = [h.lower() for h in bom_data.headers]
for header in self.REQUIRED_HEADERS:
if not header.lower() in headers:
raise ValidationError({'bom_file': _("Missing required field '{f}'".format(f=header))})

View File

@ -8,6 +8,7 @@ from __future__ import unicode_literals
import tablib
import os
from django.core.exceptions import ValidationError
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.urls import reverse_lazy
@ -23,11 +24,12 @@ from .models import match_part_names
from company.models import SupplierPart
from . import forms as part_forms
from .bom import MakeBomTemplate, BomUploadManager
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.views import QRCodeView
from InvenTree.helpers import DownloadFile, str2bool, MakeBomTemplate
from InvenTree.helpers import DownloadFile, str2bool
from InvenTree.status_codes import OrderStatus
@ -679,35 +681,15 @@ class BomUpload(AjaxView, FormMixin):
'form_valid': False
}
# Extract the file format data
ext = os.path.splitext(bom_file.name)[-1].lower()
if ext in ['.csv', '.tsv', ]:
# These file formats need string decoding
raw_data = bom_file.read().decode('utf-8')
elif ext in ['.xls', '.xlsx']:
raw_data = bom_file.read()
else:
self.form.errors['bom_file'] = ['Unsupported file format: ' + ext]
return self.renderJsonResponse(self.request, self.form, data)
# Now try to read the data
# Create a BomUploadManager object - will perform initial data validation
# (and raise a ValidationError if there is something wrong with the file)
try:
bom_data = tablib.Dataset().load(raw_data)
manager = BomUploadManager(bom_file, self.form['starting_row'].value())
except ValidationError as e:
errors = e.error_dict
headers = [h.lower() for h in bom_data.headers]
# Minimal set of required fields
for header in ['part', 'quantity', 'reference']:
if not header in headers:
self.form.errors['bom_file'] = [_("Missing required field '{f}'".format(f=header))]
break
except tablib.UnsupportedFormat:
valid = False
self.form.errors['bom_file'] = [
"Error reading '{f}' (Unsupported file format)".format(f=str(bom_file)),
]
for k,v in errors.items():
self.form.errors[k] = v
return self.renderJsonResponse(self.request, self.form, data=data)