From e31452a6addda700462bfc3d63795ececbc44a30 Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 6 May 2021 16:05:58 -0400 Subject: [PATCH] Match field step is now managed through form --- InvenTree/common/files.py | 2 +- InvenTree/common/forms.py | 78 +++ InvenTree/common/views.py | 281 ++++++++++ InvenTree/order/forms.py | 29 - .../order/order_wizard/match_fields.html | 28 +- .../order/order_wizard/match_parts.html | 4 +- InvenTree/order/views.py | 512 +----------------- 7 files changed, 385 insertions(+), 549 deletions(-) diff --git a/InvenTree/common/files.py b/InvenTree/common/files.py index d1c8a924b1..6de08731a6 100644 --- a/InvenTree/common/files.py +++ b/InvenTree/common/files.py @@ -56,7 +56,7 @@ class FileManager: raw_data = file.read().decode('utf-8') # Reset stream position to beginning of file file.seek(0) - elif ext in ['.xls', '.xlsx']: + elif ext in ['.xls', '.xlsx', '.json', '.yaml', ]: raw_data = file.read() # Reset stream position to beginning of file file.seek(0) diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py index 84e44f3a31..ba349808e1 100644 --- a/InvenTree/common/forms.py +++ b/InvenTree/common/forms.py @@ -5,8 +5,12 @@ Django forms for interacting with common objects # -*- coding: utf-8 -*- from __future__ import unicode_literals +from django import forms +from django.utils.translation import gettext as _ + from InvenTree.forms import HelperForm +from .files import FileManager from .models import InvenTreeSetting @@ -21,3 +25,77 @@ class SettingEditForm(HelperForm): fields = [ 'value' ] + + +class UploadFile(forms.Form): + """ Step 1 of FileManagementFormView """ + + file = forms.FileField( + label=_('File'), + help_text=_('Select file to upload'), + ) + + def __init__(self, *args, **kwargs): + """ Update label and help_text """ + + # Get file name + name = None + if 'name' in kwargs: + name = kwargs.pop('name') + + super().__init__(*args, **kwargs) + + if name: + # Update label and help_text with file name + self.fields['file'].label = _(f'{name.title()} File') + self.fields['file'].help_text = _(f'Select {name} file to upload') + + def clean_file(self): + """ + Run tabular file validation. + If anything is wrong with the file, it will raise ValidationError + """ + + file = self.cleaned_data['file'] + + # Validate file using FileManager class - will perform initial data validation + # (and raise a ValidationError if there is something wrong with the file) + FileManager.validate(file) + + return file + + +class MatchField(forms.Form): + """ Step 2 of FileManagementFormView """ + + def __init__(self, *args, **kwargs): + + # Get FileManager + file_manager = None + if 'file_manager' in kwargs: + file_manager = kwargs.pop('file_manager') + + super().__init__(*args, **kwargs) + + # Setup headers + file_manager.setup() + columns = file_manager.columns() + # Find headers choices + headers_choices = [(header, header) for header in file_manager.HEADERS] + + # Create column fields + for col in columns: + field_name = col['name'] + self.fields[field_name] = forms.ChoiceField( + choices=[('', '-' * 10)] + headers_choices, + required=False, + ) + if col['guess']: + self.fields[field_name].initial = col['guess'] + + +class MatchItem(forms.Form): + """ Step 3 of FileManagementFormView """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index 2abe53d380..f8993584ab 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -19,6 +19,7 @@ from InvenTree.helpers import str2bool from . import models from . import forms +from .files import FileManager class SettingEdit(AjaxUpdateView): @@ -164,3 +165,283 @@ class MultiStepFormView(SessionWizardView): context.update({'description': description}) return context + + +class FileManagementFormView(MultiStepFormView): + """ Setup form wizard to perform the following steps: + 1. Upload tabular data file + 2. Match headers to InvenTree fields + 3. Edit row data and match InvenTree items + """ + + name = None + form_list = [ + ('upload', forms.UploadFile), + ('fields', forms.MatchField), + ('items', forms.MatchItem), + ] + form_steps_description = [ + _("Upload File"), + _("Match Fields"), + _("Match Items"), + ] + media_folder = 'file_upload/' + extra_context_data = {} + + def get_context_data(self, form, **kwargs): + context = super().get_context_data(form=form, **kwargs) + + if self.steps.current == 'fields': + # Get columns and row data + columns = self.file_manager.columns() + rows = self.file_manager.rows() + # Optimize for template + for row in rows: + row_data = row['data'] + + data = [] + + for idx, item in enumerate(row_data): + data.append({ + 'cell': item, + 'idx': idx, + 'column': columns[idx] + }) + + row['data'] = data + + context.update({'rows': rows}) + + # Load extra context data + print(f'{self.extra_context_data=}') + for key, items in self.extra_context_data.items(): + context.update({key: items}) + + return context + + def getFileManager(self, step=None, form=None): + """ Get FileManager instance from uploaded file """ + + if self.file_manager: + return + + if step is not None: + # Retrieve stored files from upload step + upload_files = self.storage.get_step_files('upload') + if upload_files: + # Get file + file = upload_files.get('upload-file', None) + if file: + self.file_manager = FileManager(file=file, name=self.name) + + def get_form_kwargs(self, step=None): + """ Update kwargs to dynamically build forms """ + + print(f'[STEP] {step}') + + # Always retrieve FileManager instance from uploaded file + self.getFileManager(step) + + if step == 'upload': + if self.name: + # Dynamically build upload form + kwargs = { + 'name': self.name + } + return kwargs + elif step == 'fields': + if self.file_manager: + # Dynamically build match field form + kwargs = { + 'file_manager': self.file_manager + } + return kwargs + + return super().get_form_kwargs() + + def getFormTableData(self, form_data): + """ Extract table cell data from form data. + These data are used to maintain state between sessions. + + Table data keys are as follows: + + col_name_ - Column name at idx as provided in the uploaded file + col_guess_ - Column guess at idx as selected + row__col - Cell data as provided in the uploaded file + + """ + + # Store extra context data + self.extra_context_data = {} + + # Map the columns + self.column_names = {} + self.column_selections = {} + + self.row_data = {} + + for item in form_data: + # print(f'{item} | {form_data[item]}') + value = form_data[item] + + # Column names as passed as col_name_ where idx is an integer + + # Extract the column names + if item.startswith('col_name_'): + try: + col_id = int(item.replace('col_name_', '')) + except ValueError: + continue + + self.column_names[value] = col_id + + # Extract the column selections (in the 'select fields' view) + if item.startswith('fields-'): + + try: + col_name = item.replace('fields-', '') + except ValueError: + continue + + self.column_selections[col_name] = value + + # Extract the row data + if item.startswith('row_'): + # Item should be of the format row__col_ + s = item.split('_') + + if len(s) < 4: + continue + + # Ignore row/col IDs which are not correct numeric values + try: + row_id = int(s[1]) + col_id = int(s[3]) + except ValueError: + continue + + if row_id not in self.row_data: + self.row_data[row_id] = {} + + self.row_data[row_id][col_id] = value + + # self.col_ids = sorted(self.column_names.keys()) + + # Re-construct the data table + self.rows = [] + + for row_idx in sorted(self.row_data.keys()): + row = self.row_data[row_idx] + items = [] + + for col_idx in sorted(row.keys()): + + value = row[col_idx] + items.append(value) + + self.rows.append({ + 'index': row_idx, + 'data': items, + 'errors': {}, + }) + + # Construct the column data + self.columns = [] + + # Track any duplicate column selections + duplicates = [] + + for col in self.column_names: + + if col in self.column_selections: + guess = self.column_selections[col] + else: + guess = None + + header = ({ + 'name': self.column_names[col], + 'guess': guess + }) + + if guess: + n = list(self.column_selections.values()).count(self.column_selections[col]) + if n > 1: + header['duplicate'] = True + duplicates.append(col) + + self.columns.append(header) + + # Are there any missing columns? + missing_columns = [] + + # Check that all required fields are present + for col in self.file_manager.REQUIRED_HEADERS: + if col not in self.column_selections.values(): + missing_columns.append(col) + + # Check that at least one of the part match field is present + part_match_found = False + for col in self.file_manager.PART_MATCH_HEADERS: + if col in self.column_selections.values(): + part_match_found = True + break + + # If not, notify user + if not part_match_found: + for col in self.file_manager.PART_MATCH_HEADERS: + missing_columns.append(col) + + # Store extra context data + self.extra_context_data['missing_columns'] = missing_columns + self.extra_context_data['duplicates'] = duplicates + + def checkFieldSelection(self, form): + """ Check field matching """ + + # Extract form data + self.getFormTableData(form.data) + + valid = len(self.extra_context_data.get('missing_columns', [])) == 0 and not self.extra_context_data.get('duplicates', []) + + return valid + + def validate(self, step, form): + """ Validate forms """ + + valid = False + + # Process steps + if step == 'upload': + # Validation is done during POST + valid = True + elif step == 'fields': + # Validate user form data + valid = self.checkFieldSelection(form) + + if not valid: + form.add_error(None, 'Fields matching failed') + + elif step == 'items': + # valid = self.checkPartSelection(form) + + # if not valid: + # form.add_error(None, 'Items matching failed') + pass + + return valid + + def post(self, request, *args, **kwargs): + """ Perform validations before posting data """ + + wizard_goto_step = self.request.POST.get('wizard_goto_step', None) + + form = self.get_form(data=self.request.POST, files=self.request.FILES) + + form_valid = self.validate(self.steps.current, form) + + if not form_valid and not wizard_goto_step: + # Re-render same step + return self.render(form) + + print('\nPosting... ') + return super().post(*args, **kwargs) diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index b99c09caba..4c9caf3b53 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -14,8 +14,6 @@ from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField from InvenTree.fields import DatePickerFormField -from common.files import FileManager - import part.models from stock.models import StockLocation @@ -286,30 +284,3 @@ class EditSalesOrderAllocationForm(HelperForm): 'line', 'item', 'quantity'] - - -class UploadFile(forms.Form): - """ Step 1 """ - file = forms.FileField( - label=_('Order File'), - help_text=_('Select order file to upload'), - ) - - def clean_file(self): - file = self.cleaned_data['file'] - - # Validate file using FileManager class - will perform initial data validation - # (and raise a ValidationError if there is something wrong with the file) - FileManager.validate(file) - - return file - - -class MatchField(forms.Form): - """ Step 2 """ - pass - - -class MatchPart(forms.Form): - """ Step 3 """ - pass diff --git a/InvenTree/order/templates/order/order_wizard/match_fields.html b/InvenTree/order/templates/order/order_wizard/match_fields.html index 08fce9802d..2d5dd3292b 100644 --- a/InvenTree/order/templates/order/order_wizard/match_fields.html +++ b/InvenTree/order/templates/order/order_wizard/match_fields.html @@ -15,12 +15,17 @@ {% endif %} +{% if duplicates and duplicates|length > 0 %} + +{% endif %} {% endblock form_alert %} {% block form_buttons_top %} - {% comment %} {% if wizard.steps.prev %} + {% if wizard.steps.prev %} - {% endif %} {% endcomment %} + {% endif %} {% endblock form_buttons_top %} @@ -29,7 +34,7 @@ {% trans "File Fields" %} - {% for col in columns %} + {% for col in form %}
@@ -46,17 +51,22 @@ {% trans "Match Fields" %} - {% for col in columns %} + {% for col in form %} - {% for req in headers %} {% endfor %} - - {% if col.duplicate %} -

{% trans "Duplicate column selection" %}

- {% endif %} + {% endcomment %} + {% for duplicate in duplicates %} + {% if duplicate == col.name %} + + {% endif %} + {% endfor %} {% endfor %} diff --git a/InvenTree/order/templates/order/order_wizard/match_parts.html b/InvenTree/order/templates/order/order_wizard/match_parts.html index 6714c0c11a..63cac689b4 100644 --- a/InvenTree/order/templates/order/order_wizard/match_parts.html +++ b/InvenTree/order/templates/order/order_wizard/match_parts.html @@ -12,9 +12,9 @@ {% endblock form_alert %} {% block form_buttons_top %} - {% comment %} {% if wizard.steps.prev %} + {% if wizard.steps.prev %} - {% endif %} {% endcomment %} + {% endif %} {% endblock form_buttons_top %} diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 6a6a8ce6e2..b3f6c1326a 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -28,8 +28,7 @@ from stock.models import StockItem, StockLocation from part.models import Part from common.models import InvenTreeSetting -from common.views import MultiStepFormView -from common.files import FileManager +from common.views import FileManagementFormView from . import forms as order_forms @@ -567,14 +566,10 @@ class SalesOrderShip(AjaxUpdateView): return self.renderJsonResponse(request, form, data, context) -class PurchaseOrderUpload(MultiStepFormView): +class PurchaseOrderUpload(FileManagementFormView): ''' PurchaseOrder: Upload file, match to fields and parts (using multi-Step form) ''' - form_list = [ - ('upload', order_forms.UploadFile), - ('fields', order_forms.MatchField), - ('parts', order_forms.MatchPart), - ] + name = 'order' form_steps_template = [ 'order/order_wizard/po_upload.html', 'order/order_wizard/match_fields.html', @@ -583,16 +578,8 @@ class PurchaseOrderUpload(MultiStepFormView): form_steps_description = [ _("Upload File"), _("Match Fields"), - _("Match Parts"), + _("Match Supplier Parts"), ] - media_folder = 'order_uploads/' - - # Used for data table - headers = None - rows = None - columns = None - missing_columns = None - allowed_parts = None def get_context_data(self, form, **kwargs): context = super().get_context_data(form=form, **kwargs) @@ -601,499 +588,8 @@ class PurchaseOrderUpload(MultiStepFormView): context.update({'order': order}) - if self.headers: - context.update({'headers': self.headers}) - # print(f'{self.headers}') - if self.columns: - context.update({'columns': self.columns}) - # print(f'{self.columns}') - if self.rows: - for row in self.rows: - row_data = row['data'] - - data = [] - - for idx, item in enumerate(row_data): - data.append({ - 'cell': item, - 'idx': idx, - 'column': self.columns[idx] - }) - - row['data'] = data - - context.update({'rows': self.rows}) - # print(f'{self.rows}') - if self.missing_columns: - context.update({'missing_columns': self.missing_columns}) - return context - def getTableDataFromForm(self, form_data): - """ Extract table cell data from form data. - These data are used to maintain state between sessions. - - Table data keys are as follows: - - col_name_ - Column name at idx as provided in the uploaded file - col_guess_ - Column guess at idx as selected - row__col - Cell data as provided in the uploaded file - - """ - - # Map the columns - self.column_names = {} - self.column_selections = {} - - self.row_data = {} - - for item in form_data: - value = form_data[item] - - # Column names as passed as col_name_ where idx is an integer - - # Extract the column names - if item.startswith('col_name_'): - try: - col_id = int(item.replace('col_name_', '')) - except ValueError: - continue - col_name = value - - self.column_names[col_id] = col_name - - # Extract the column selections (in the 'select fields' view) - if item.startswith('col_guess_'): - - try: - col_id = int(item.replace('col_guess_', '')) - except ValueError: - continue - - col_name = value - - self.column_selections[col_id] = value - - # Extract the row data - if item.startswith('row_'): - # Item should be of the format row__col_ - s = item.split('_') - - if len(s) < 4: - continue - - # Ignore row/col IDs which are not correct numeric values - try: - row_id = int(s[1]) - col_id = int(s[3]) - except ValueError: - continue - - if row_id not in self.row_data: - self.row_data[row_id] = {} - - self.row_data[row_id][col_id] = value - - self.col_ids = sorted(self.column_names.keys()) - - # Re-construct the data table - self.rows = [] - - for row_idx in sorted(self.row_data.keys()): - row = self.row_data[row_idx] - items = [] - - for col_idx in sorted(row.keys()): - - value = row[col_idx] - items.append(value) - - self.rows.append({ - 'index': row_idx, - 'data': items, - 'errors': {}, - }) - - # Construct the column data - self.columns = [] - - # Track any duplicate column selections - self.duplicates = False - - for col in self.col_ids: - - if col in self.column_selections: - guess = self.column_selections[col] - else: - guess = None - - header = ({ - 'name': self.column_names[col], - 'guess': guess - }) - - if guess: - n = list(self.column_selections.values()).count(self.column_selections[col]) - if n > 1: - header['duplicate'] = True - self.duplicates = True - - self.columns.append(header) - - # Are there any missing columns? - self.missing_columns = [] - - # Check that all required fields are present - for col in self.file_manager.REQUIRED_HEADERS: - if col not in self.column_selections.values(): - self.missing_columns.append(col) - - # Check that at least one of the part match field is present - part_match_found = False - for col in self.file_manager.PART_MATCH_HEADERS: - if col in self.column_selections.values(): - part_match_found = True - break - - # If not, notify user - if not part_match_found: - for col in self.file_manager.PART_MATCH_HEADERS: - self.missing_columns.append(col) - - def getColumnIndex(self, name): - """ Return the index of the column with the given name. - It named column is not found, return -1 - """ - - try: - idx = list(self.column_selections.values()).index(name) - except ValueError: - idx = -1 - - return idx - - def preFillSelections(self): - """ Once data columns have been selected, attempt to pre-select the proper data from the database. - This function is called once the field selection has been validated. - The pre-fill data are then passed through to the part selection form. - """ - - # Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database - q_idx = self.getColumnIndex('Quantity') - s_idx = self.getColumnIndex('Supplier_SKU') - # m_idx = self.getColumnIndex('Manufacturer_MPN') - # p_idx = self.getColumnIndex('Unit_Price') - # e_idx = self.getColumnIndex('Extended_Price') - - for row in self.rows: - - # Initially use a quantity of zero - quantity = Decimal(0) - - # Initially we do not have a part to reference - exact_match_part = None - - # A list of potential Part matches - part_options = self.allowed_parts - - # Check if there is a column corresponding to "quantity" - if q_idx >= 0: - q_val = row['data'][q_idx] - - if q_val: - # Delete commas - q_val = q_val.replace(',','') - - try: - # Attempt to extract a valid quantity from the field - quantity = Decimal(q_val) - except (ValueError, InvalidOperation): - pass - - # Store the 'quantity' value - row['quantity'] = quantity - - # Check if there is a column corresponding to "Supplier SKU" - if s_idx >= 0: - sku = row['data'][s_idx] - - try: - # Attempt SupplierPart lookup based on SKU value - exact_match_part = SupplierPart.objects.get(SKU__contains=sku) - except (ValueError, SupplierPart.DoesNotExist, SupplierPart.MultipleObjectsReturned): - exact_match_part = None - - # Check if there is a column corresponding to "Manufacturer MPN" - # if m_idx >= 0: - # row['part_mpn'] = row['data'][m_idx] - - # try: - # # Attempt ManufacturerPart lookup based on MPN value - # exact_match_part = ManufacturerPart.objects.get(MPN=row['part_mpn']) - # except (ValueError, ManufacturerPart.DoesNotExist): - # exact_match_part = None - - # Supply list of part options for each row, sorted by how closely they match the part name - row['part_options'] = part_options - - # Unless found, the 'part_match' is blank - row['part_match'] = None - - if exact_match_part: - # If there is an exact match based on SKU or MPN, use that - row['part_match'] = exact_match_part - - def updatePartSelectionColumns(self, form): - # for idx, row in enumerate(self.rows): - # print(f'{idx} | {row}\n\n') - pass - - def getFileManager(self, form=None): - """ Create FileManager instance from upload file """ - - if self.file_manager: - return - - if self.steps.current == 'upload': - # Get file from form data - order_file = form.cleaned_data['file'] - self.file_manager = FileManager(file=order_file, name='order') - else: - # Retrieve stored files from upload step - upload_files = self.storage.get_step_files('upload') - # Get file - order_file = upload_files.get('upload-file', None) - if order_file: - self.file_manager = FileManager(file=order_file, name='order') - - def setupFieldSelection(self, form): - """ Setup fields form """ - - # Get FileManager - self.getFileManager(form) - # Setup headers - self.file_manager.setup() - # Set headers - self.headers = self.file_manager.HEADERS - # Set columns and rows - self.columns = self.file_manager.columns() - self.rows = self.file_manager.rows() - - def handleFieldSelection(self, form): - """ Process field matching """ - - # Retrieve FileManager instance from uploaded file - self.getFileManager(form) - - # Update headers - if self.file_manager: - self.file_manager.setup() - else: - return False - - # Extract form data - self.getTableDataFromForm(form.data) - - valid = len(self.missing_columns) == 0 and not self.duplicates - - return valid - - def getRowByIndex(self, idx): - - for row in self.rows: - if row['index'] == idx: - return row - - return None - - def handlePartSelection(self, form): - - # Retrieve FileManager instance from uploaded file - self.getFileManager(form) - - # Extract form data - self.getTableDataFromForm(form.data) - - # Keep track of the parts that have been selected - parts = {} - - # Extract other data (part selections, etc) - for key, value in form.data.items(): - - # Extract quantity from each row - if key.startswith('quantity_'): - try: - row_id = int(key.replace('quantity_', '')) - - row = self.getRowByIndex(row_id) - - if row is None: - continue - - q = Decimal(1) - - try: - q = Decimal(value) - if q < 0: - row['errors']['quantity'] = _('Quantity must be greater than zero') - - if 'part' in row.keys(): - if row['part'].trackable: - # Trackable parts must use integer quantities - if not q == int(q): - row['errors']['quantity'] = _('Quantity must be integer value for trackable parts') - - except (ValueError, InvalidOperation): - row['errors']['quantity'] = _('Enter a valid quantity') - - row['quantity'] = q - - except ValueError: - continue - - # Extract part from each row - if key.startswith('part_'): - - try: - row_id = int(key.replace('part_', '')) - - row = self.getRowByIndex(row_id) - - if row is None: - continue - except ValueError: - # Row ID non integer value - continue - - try: - part_id = int(value) - part = Part.objects.get(id=part_id) - except ValueError: - row['errors']['part'] = _('Select valid part') - continue - except Part.DoesNotExist: - row['errors']['part'] = _('Select valid part') - continue - - # Keep track of how many of each part we have seen - if part_id in parts: - parts[part_id]['quantity'] += 1 - row['errors']['part'] = _('Duplicate part selected') - else: - parts[part_id] = { - 'part': part, - 'quantity': 1, - } - - row['part'] = part - - if part.trackable: - # For trackable parts, ensure the quantity is an integer value! - if 'quantity' in row.keys(): - q = row['quantity'] - - if not q == int(q): - row['errors']['quantity'] = _('Quantity must be integer value for trackable parts') - - # Extract other fields which do not require further validation - for field in ['reference', 'notes']: - if key.startswith(field + '_'): - try: - row_id = int(key.replace(field + '_', '')) - - row = self.getRowByIndex(row_id) - - if row: - row[field] = value - except: - continue - - # Are there any errors after form handling? - valid = True - - for row in self.rows: - # Has a part been selected for the given row? - part = row.get('part', None) - - if part is None: - row['errors']['part'] = _('Select a part') - - # Has a quantity been specified? - if row.get('quantity', None) is None: - row['errors']['quantity'] = _('Specify quantity') - - errors = row.get('errors', []) - - if len(errors) > 0: - valid = False - - return valid - - def get_form_step_data(self, form): - """ Process form data after it has been posted """ - - # print(f'{self.steps.current=}\n{form.data=}') - - # Retrieve FileManager instance from uploaded file - self.getFileManager(form) - # print(f'{self.file_manager=}') - - # Process steps - if self.steps.current == 'upload': - self.setupFieldSelection(form) - elif self.steps.current == 'fields': - self.allowed_parts = SupplierPart.objects.all() - self.rows = self.file_manager.rows() - self.preFillSelections() - # self.updatePartSelectionColumns(form) - # elif self.steps.current == 'parts': - # self.handlePartSelection(form) - - return form.data - - def validate(self, step, form): - """ Validate forms """ - - valid = False - - # Process steps - if step == 'upload': - # Validation is done during POST - valid = True - elif step == 'fields': - # Validate user form data - valid = self.handleFieldSelection(form) - - if not valid: - form.add_error(None, 'Fields matching failed') - # Reload headers - self.headers = self.file_manager.HEADERS - - elif step == 'parts': - valid = self.handlePartSelection(form) - - # if not valid: - # pass - - return valid - - def post(self, request, *args, **kwargs): - """ Perform validations before posting data """ - - wizard_goto_step = self.request.POST.get('wizard_goto_step', None) - - form = self.get_form(data=self.request.POST, files=self.request.FILES) - - print(f'\nCurrent step = {self.steps.current}') - form_valid = self.validate(self.steps.current, form) - - if not form_valid and not wizard_goto_step: - # Re-render same step - return self.render(form) - - print('\nPosting... ') - return super().post(*args, **kwargs) - def done(self, form_list, **kwargs): return HttpResponseRedirect(reverse('po-detail', kwargs={'pk': self.kwargs['pk']}))