diff --git a/InvenTree/part/bom.py b/InvenTree/part/bom.py index 6841de2146..e2d870805a 100644 --- a/InvenTree/part/bom.py +++ b/InvenTree/part/bom.py @@ -59,15 +59,8 @@ class BomUploadManager: 'Notes' ] - def __init__(self, bom_file, starting_row=2): + def __init__(self, bom_file): """ Initialize the BomUpload class with a user-uploaded file object """ - try: - start = int(starting_row) - 1 - if start < 0: - start = 0 - self.starting_row = start - except ValueError: - self.starting_row = 1 self.process(bom_file) @@ -154,6 +147,20 @@ class BomUploadManager: return None + + def get_headers(self): + """ Return a list of headers for the thingy """ + headers = [] + + return headers + + + def col_count(self): + if self.data is None: + return 0 + + return len(self.data.headers) + def row_count(self): """ Return the number of rows in the file. Ignored the top rows as indicated by 'starting row' @@ -162,16 +169,30 @@ class BomUploadManager: if self.data is None: return 0 - # Limit the number of BOM lines to be sensible - count = min(len(self.data) - self.starting_row, 1000) + return len(self.data) - return count + def rows(self): + """ Return a list of all rows """ + rows = [] - def get_row(self, index): + for i in range(self.row_count()): + row = self.get_row_data(i) + + if row: + rows.append(row) + + return rows + + def get_row_data(self, index): + """ Retrieve row data at a particular index """ + if self.data is None or index >= len(self.data): + return None + + return self.data[index] + + def get_row_dict(self, index): """ Retrieve a dict object representing the data row at a particular offset """ - index += self.starting_row - if self.data is None or index >= len(self.data): return None diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index b0dcaabfb0..cb6ae9c70b 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -39,25 +39,29 @@ class BomValidateForm(HelperForm): ] -class BomImportForm(HelperForm): +class BomUploadSelectFile(HelperForm): """ Form for importing a BOM. Provides a file input box for upload """ bom_file = forms.FileField(label='BOM file', required=True, help_text="Select BOM file to upload") - starting_row = forms.IntegerField( - required=True, - initial=2, - help_text='First row containing valid BOM data', - validators=[ - MinValueValidator(1) - ] - ) - class Meta: model = Part fields = [ 'bom_file', + ] + + +class BomUploadSelectFields(HelperForm): + """ Form for selecting BOM fields """ + + starting_row = forms.IntegerField(required=True, initial=2, help_text='Index of starting row', validators=[MinValueValidator(1)]) + row_count = forms.IntegerField(required=True, help_text='Number of rows to process', validators=[MinValueValidator(0)]) + + class Meta: + model = Part + fields = [ 'starting_row', + 'row_count', ] diff --git a/InvenTree/part/templates/part/bom_upload/select_fields.html b/InvenTree/part/templates/part/bom_upload/select_fields.html new file mode 100644 index 0000000000..0656a957a5 --- /dev/null +++ b/InvenTree/part/templates/part/bom_upload/select_fields.html @@ -0,0 +1,38 @@ +{% extends "modal_form.html" %} +{% load inventree_extras %} + +{% block form %} + +

Step 2 of 3 - Select BOM Fields

+ +
+ {% csrf_token %} + {% load crispy_forms_tags %} + + {% crispy form %} + + + + + + + + + + {% for row in bom.rows %} + + {% for item in row %} + + {% endfor %} + + {% endfor %} + +
+ {{ item }} +
+ +
+ +BOM Rows: {{ bom.row_count }} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/bom_upload.html b/InvenTree/part/templates/part/bom_upload/select_file.html similarity index 57% rename from InvenTree/part/templates/part/bom_upload.html rename to InvenTree/part/templates/part/bom_upload/select_file.html index 50ff5cdf87..68380dbfd5 100644 --- a/InvenTree/part/templates/part/bom_upload.html +++ b/InvenTree/part/templates/part/bom_upload/select_file.html @@ -2,9 +2,13 @@ {% block pre_form_content %} +

Step 1 of 3 - Select BOM File

+ {{ block.super }} -

Select a BOM file to upload for {{ part.name }} - {{ part.description }}.

+

Select a BOM file to upload for:
+ {{ part.name }} - {{ part.description }} +

The BOM file must contain the required named columns as provided in the BOM Upload Template

@@ -14,4 +18,8 @@
  • Maximum of 1000 lines per BOM
  • +{% endblock %} + +{% block form_data %} + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index b17c5bf9e8..493a1bb740 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -8,6 +8,11 @@ from InvenTree import version register = template.Library() +@register.simple_tag() +def inrange(n, *args, **kwargs): + """ Return range(n) for iterating through a numeric quantity """ + return range(n) + @register.simple_tag() def multiply(x, y, *args, **kwargs): """ Multiply two numbers together """ diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 11669b464b..f5d689875a 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -650,8 +650,17 @@ class BomUpload(AjaxView, FormMixin): """ ajax_form_title = 'Upload Bill of Materials' - ajax_template_name = 'part/bom_upload.html' - form_class = part_forms.BomImportForm + ajax_template_name = 'part/bom_upload/select_file.html' + + def get_form_class(self): + + form_step = self.request.POST.get('form_step', None) + + if form_step == 'select_fields': + return part_forms.BomUploadSelectFields + else: + # Default form is the starting point + return part_forms.BomUploadSelectFile def get_context_data(self): ctx = { @@ -674,24 +683,54 @@ class BomUpload(AjaxView, FormMixin): return self.renderJsonResponse(request, self.form) - def handleBomFileUpload(self, bom_file): - + def handleBomFileUpload(self): + + bom_file = self.request.FILES.get('bom_file', None) + + manager = None + bom_file_valid = False + + if bom_file is None: + self.form.errors['bom_file'] = [_('No BOM file provided')] + else: + # Create a BomUploadManager object - will perform initial data validation + # (and raise a ValidationError if there is something wrong with the file) + try: + manager = BomUploadManager(bom_file) + bom_file_valid = True + except ValidationError as e: + errors = e.error_dict + + for k,v in errors.items(): + self.form.errors[k] = v + data = { - # TODO - Validate the form if there isn't actually an error! 'form_valid': False } - # Create a BomUploadManager object - will perform initial data validation - # (and raise a ValidationError if there is something wrong with the file) - try: - manager = BomUploadManager(bom_file, self.form['starting_row'].value()) - except ValidationError as e: - errors = e.error_dict + ctx = {} - for k,v in errors.items(): - self.form.errors[k] = v + if bom_file_valid: + # BOM file is valid? Proceed to the next step! + form = part_forms.BomUploadSelectFields + self.ajax_template_name = 'part/bom_upload/select_fields.html' + ctx['bom'] = manager + else: + form = self.form - return self.renderJsonResponse(self.request, self.form, data=data) + return self.renderJsonResponse(self.request, form, data=data, context=ctx) + + def handleFieldSelection(self): + + data = { + 'form_valid': False, + } + + self.ajax_template_name = 'part/bom_upload/select_fields.html' + + ctx = {} + + return self.renderJsonResponse(self.request, form=self.get_form(), data=data, context=ctx) def post(self, request, *args, **kwargs): """ Perform the various 'POST' requests required. @@ -703,10 +742,14 @@ class BomUpload(AjaxView, FormMixin): self.form = self.get_form() # Did the user POST a file named bom_file? - bom_file = request.FILES.get('bom_file', None) + - if bom_file: - return self.handleBomFileUpload(bom_file) + form_step = request.POST.get('form_step', None) + + if form_step == 'select_file': + return self.handleBomFileUpload() + elif form_step == 'select_fields': + return self.handleFieldSelection() data = { 'form_valid': False,