Getting there...

This commit is contained in:
eeintech 2021-05-07 16:46:10 -04:00
parent 6e269ae41a
commit fbf24621f3
4 changed files with 188 additions and 207 deletions

View File

@ -107,6 +107,8 @@ class MatchItem(forms.Form):
if 'row_data' in kwargs: if 'row_data' in kwargs:
row_data = kwargs.pop('row_data') row_data = kwargs.pop('row_data')
else:
row_data = None
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -115,44 +117,46 @@ class MatchItem(forms.Form):
# Get columns # Get columns
columns = file_manager.columns() columns = file_manager.columns()
# Create fields if row_data:
# Item selection # Create fields
for row in row_data: for row in row_data:
for col in row['data']: for col in row['data']:
if col['column']['guess'] in file_manager.REQUIRED_HEADERS: # print(f"{col=}")
field_name = col['column']['guess'].lower() + '-' + str(row['index'] - 1) if col['column']['guess'] in file_manager.REQUIRED_HEADERS:
if 'quantity' in col['column']['guess'].lower(): field_name = col['column']['guess'].lower() + '-' + str(row['index'])
self.fields[field_name] = forms.CharField( if 'quantity' in col['column']['guess'].lower():
required=True, self.fields[field_name] = forms.CharField(
widget=forms.NumberInput(attrs={ required=True,
'name': 'quantity' + str(row['index']), widget=forms.NumberInput(attrs={
'class': 'numberinput', 'name': 'quantity' + str(row['index']),
'type': 'number', 'class': 'numberinput',
'min': '1', 'type': 'number',
'step': 'any', 'min': '0',
'value': row['quantity'], 'step': 'any',
}) 'value': row['quantity'],
) })
else: )
self.fields[field_name] = forms.Input( else:
self.fields[field_name] = forms.Input(
required=True,
widget=forms.Select(attrs={
})
)
elif col['column']['guess'] in file_manager.ITEM_MATCH_HEADERS:
# print(f'{row["index"]=} | {col["column"]["guess"]=} | {row.get("item_match", "No Match")}')
# Get item options
item_options = [(option.id, option) for option in row['item_options']]
# Get item match
item_match = row['item_match']
field_name = col['column']['guess'].lower() + '-' + str(row['index'])
self.fields[field_name] = forms.ChoiceField(
choices=[('', '-' * 10)] + item_options,
required=True, required=True,
widget=forms.Select(attrs={ widget=forms.Select(attrs={
'class': 'select bomselect',
}) })
) )
elif col['column']['guess'] in file_manager.ITEM_MATCH_HEADERS: if item_match:
print(f'{row["index"]=} | {col["column"]["guess"]=} | {row.get("item_match", "No Match")}') self.fields[field_name].initial = item_match.id
# Get item options
item_options = [(option.id, option) for option in row['item_options']]
# Get item match
item_match = row['item_match']
field_name = col['column']['guess'].lower() + '-' + str(row['index'] - 1)
self.fields[field_name] = forms.ChoiceField(
choices=[('', '-' * 10)] + item_options,
required=True,
widget=forms.Select(attrs={'class': 'bomselect'})
)
if item_match:
print(f'{item_match=}')
self.fields[field_name].initial = item_match.id

View File

@ -197,66 +197,24 @@ class FileManagementFormView(MultiStepFormView):
if self.steps.current == 'fields' or self.steps.current == 'items': if self.steps.current == 'fields' or self.steps.current == 'items':
# Get columns and row data # Get columns and row data
columns = self.file_manager.columns() self.columns = self.file_manager.columns()
rows = self.file_manager.rows() self.rows = self.file_manager.rows()
# Set form table data
self.set_form_table_data(form=form)
key_item_select = '' # if self.steps.current == 'items':
key_quantity_select = '' # for row in self.rows:
if self.steps.current == 'items': # print(f'{row=}')
# Get file manager context.update({'rows': self.rows})
self.getFileManager() context.update({'columns': self.columns})
# Find column key for item selection
for item in self.file_manager.ITEM_MATCH_HEADERS:
item = item.lower()
for key in form.fields.keys():
print(f'{item=} is in {key=} ?')
if item in key:
key_item_select = item
break
break
# Find column key for quantity selection
key_quantity_select = 'quantity'
# Optimize for template
for row in rows:
# Add item select field
if key_item_select:
row['item_select'] = key_item_select + '-' + str(row['index'])
print(f'{row["item_select"]}')
# Add quantity select field
if key_quantity_select:
row['quantity_select'] = key_quantity_select + '-' + str(row['index'])
row_data = row['data']
data = []
for idx, item in enumerate(row_data):
data.append({
'cell': item,
'idx': idx,
'column': columns[idx],
})
row['data'] = data
print(f'\n{row=}')
context.update({'rows': rows})
if self.steps.current == 'items':
context.update({'columns': columns})
# Load extra context data # Load extra context data
print(f'{self.extra_context_data=}')
for key, items in self.extra_context_data.items(): for key, items in self.extra_context_data.items():
context.update({key: items}) context.update({key: items})
return context return context
def getFileManager(self, step=None, form=None): def get_file_manager(self, step=None, form=None):
""" Get FileManager instance from uploaded file """ """ Get FileManager instance from uploaded file """
if self.file_manager: if self.file_manager:
@ -274,10 +232,8 @@ class FileManagementFormView(MultiStepFormView):
def get_form_kwargs(self, step=None): def get_form_kwargs(self, step=None):
""" Update kwargs to dynamically build forms """ """ Update kwargs to dynamically build forms """
print(f'[STEP] {step}')
# Always retrieve FileManager instance from uploaded file # Always retrieve FileManager instance from uploaded file
self.getFileManager(step) self.get_file_manager(step)
if step == 'upload': if step == 'upload':
# Dynamically build upload form # Dynamically build upload form
@ -296,14 +252,25 @@ class FileManagementFormView(MultiStepFormView):
# Dynamically build match item form # Dynamically build match item form
kwargs = {} kwargs = {}
kwargs['file_manager'] = self.file_manager kwargs['file_manager'] = self.file_manager
self.getFieldSelections()
# Get data from fields step
data = self.storage.get_step_data('fields')
# Process to update columns and rows
self.rows = self.file_manager.rows()
self.columns = self.file_manager.columns()
self.get_form_table_data(data)
self.set_form_table_data()
self.get_field_selection()
kwargs['row_data'] = self.rows kwargs['row_data'] = self.rows
return kwargs return kwargs
return super().get_form_kwargs() return super().get_form_kwargs()
def getFormTableData(self, form_data): def get_form_table_data(self, form_data):
""" Extract table cell data from form data. """ Extract table cell data from form data and fields.
These data are used to maintain state between sessions. These data are used to maintain state between sessions.
Table data keys are as follows: Table data keys are as follows:
@ -314,18 +281,15 @@ class FileManagementFormView(MultiStepFormView):
""" """
# Store extra context data
self.extra_context_data = {}
# Map the columns # Map the columns
self.column_names = {} self.column_names = {}
self.column_selections = {} self.column_selections = {}
self.row_data = {} self.row_data = {}
for item in form_data: for item, value in form_data.items():
# print(f'{item} | {form_data[item]} | {type(form_data[item])}') # print(f'{item} | {form_data[item]} | {type(form_data[item])}')
value = form_data[item] # value = form.data[item]
# Column names as passed as col_name_<idx> where idx is an integer # Column names as passed as col_name_<idx> where idx is an integer
@ -368,79 +332,85 @@ class FileManagementFormView(MultiStepFormView):
# TODO: this is a hack # TODO: this is a hack
value = value.replace("'", '"') value = value.replace("'", '"')
self.row_data[row_id][col_id] = ast.literal_eval(value) # print(f'{type(value)=} | {value=}')
try:
self.row_data[row_id][col_id] = ast.literal_eval(value)
except (ValueError, SyntaxError):
pass
# self.col_ids = sorted(self.column_names.keys()) def set_form_table_data(self, form=None):
if self.row_data:
# Re-construct the row data
self.rows = []
# Re-construct the data table for row_idx in sorted(self.row_data.keys()):
self.rows = [] row = self.row_data[row_idx]
items = []
for row_idx in sorted(self.row_data.keys()): for col_idx in sorted(row.keys()):
row = self.row_data[row_idx]
items = []
for col_idx in sorted(row.keys()): value = row[col_idx]
items.append(value)
value = row[col_idx] self.rows.append({
items.append(value) 'index': row_idx,
'data': items,
'errors': {},
})
else:
# Update the row data
for row in self.rows:
row_data = row['data']
self.rows.append({ data = []
'index': row_idx,
'data': items,
'errors': {},
})
# Construct the column data for idx, item in enumerate(row_data):
self.columns = [] data.append({
'cell': item,
'idx': idx,
'column': self.columns[idx],
})
row['data'] = data
# Track any duplicate column selections # In the item selection step: update row data to contain fields
duplicates = [] if form and self.steps.current == 'items':
key_item_select = ''
key_quantity_select = ''
for col in self.column_names: # Find column key for item selection
for item in self.file_manager.ITEM_MATCH_HEADERS:
item = item.lower()
for key in form.fields.keys():
if item in key:
key_item_select = item
break
break
if col in self.column_selections: # Find column key for quantity selection
guess = self.column_selections[col] key_quantity_select = 'quantity'
else:
guess = None
header = ({ # Update row data
'name': self.column_names[col], for row in self.rows:
'guess': guess # Add item select field
}) if key_item_select:
row['item_select'] = key_item_select + '-' + str(row['index'])
# Add quantity select field
if key_quantity_select:
row['quantity_select'] = key_quantity_select + '-' + str(row['index'])
if guess: if self.column_names:
n = list(self.column_selections.values()).count(self.column_selections[col]) # Re-construct the column data
if n > 1: self.columns = []
header['duplicate'] = True
duplicates.append(col)
self.columns.append(header) for key in self.column_names:
header = ({
'name': key,
'guess': self.column_selections[key],
})
self.columns.append(header)
# Are there any missing columns? def get_column_index(self, name):
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.ITEM_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.ITEM_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 getColumnIndex(self, name):
""" Return the index of the column with the given name. """ Return the index of the column with the given name.
It named column is not found, return -1 It named column is not found, return -1
""" """
@ -452,7 +422,7 @@ class FileManagementFormView(MultiStepFormView):
return idx return idx
def getFieldSelections(self): def get_field_selection(self):
""" Once data columns have been selected, attempt to pre-select the proper data from the database. """ 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. This function is called once the field selection has been validated.
The pre-fill data are then passed through to the part selection form. The pre-fill data are then passed through to the part selection form.
@ -460,13 +430,14 @@ class FileManagementFormView(MultiStepFormView):
match_supplier = False match_supplier = False
match_manufacturer = False match_manufacturer = False
self.allowed_items = None
# Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database # Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database
q_idx = self.getColumnIndex('Quantity') q_idx = self.get_column_index('Quantity')
s_idx = self.getColumnIndex('Supplier_SKU') s_idx = self.get_column_index('Supplier_SKU')
m_idx = self.getColumnIndex('Manufacturer_MPN') m_idx = self.get_column_index('Manufacturer_MPN')
# p_idx = self.getColumnIndex('Unit_Price') # p_idx = self.get_column_index('Unit_Price')
# e_idx = self.getColumnIndex('Extended_Price') # e_idx = self.get_column_index('Extended_Price')
for row in self.rows: for row in self.rows:
@ -495,6 +466,7 @@ class FileManagementFormView(MultiStepFormView):
# Check if there is a column corresponding to "Supplier SKU" # Check if there is a column corresponding to "Supplier SKU"
if s_idx >= 0: if s_idx >= 0:
print(f'{row["data"][s_idx]=}')
sku = row['data'][s_idx]['cell'] sku = row['data'][s_idx]['cell']
# Match for supplier # Match for supplier
@ -536,22 +508,51 @@ class FileManagementFormView(MultiStepFormView):
# If there is an exact match based on SKU or MPN, use that # If there is an exact match based on SKU or MPN, use that
row['item_match'] = exact_match_part row['item_match'] = exact_match_part
def checkFieldSelection(self, form): def check_field_selection(self, form):
""" Check field matching """ """ Check field matching """
# Extract form data # Are there any missing columns?
self.getFormTableData(form.data) missing_columns = []
valid = len(self.extra_context_data.get('missing_columns', [])) == 0 and not self.extra_context_data.get('duplicates', []) # 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)
return valid # Check that at least one of the part match field is present
part_match_found = False
for col in self.file_manager.ITEM_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.ITEM_MATCH_HEADERS:
missing_columns.append(col)
def checkPartSelection(self, form): # Track any duplicate column selections
""" Check part matching """ duplicates = []
# Extract form data for col in self.column_names:
self.getFormTableData(form.data)
if col in self.column_selections:
guess = self.column_selections[col]
else:
guess = None
if guess:
n = list(self.column_selections.values()).count(self.column_selections[col])
if n > 1:
duplicates.append(col)
# Store extra context data
self.extra_context_data = {
'missing_columns': missing_columns,
'duplicates': duplicates,
}
# Data validation
valid = len(self.extra_context_data.get('missing_columns', [])) == 0 and not self.extra_context_data.get('duplicates', []) valid = len(self.extra_context_data.get('missing_columns', [])) == 0 and not self.extra_context_data.get('duplicates', [])
return valid return valid
@ -559,24 +560,20 @@ class FileManagementFormView(MultiStepFormView):
def validate(self, step, form): def validate(self, step, form):
""" Validate forms """ """ Validate forms """
valid = False valid = True
# Process steps # Get form table data
if step == 'upload': self.get_form_table_data(form.data)
# Validation is done during POST
valid = True if step == 'fields':
elif step == 'fields':
# Validate user form data # Validate user form data
valid = self.checkFieldSelection(form) valid = self.check_field_selection(form)
if not valid: if not valid:
form.add_error(None, 'Fields matching failed') form.add_error(None, 'Fields matching failed')
elif step == 'items': elif step == 'items':
valid = self.checkPartSelection(form) pass
if not valid:
form.add_error(None, 'Items matching failed')
return valid return valid
@ -593,5 +590,4 @@ class FileManagementFormView(MultiStepFormView):
# Re-render same step # Re-render same step
return self.render(form) return self.render(form)
print('\nPosting... ')
return super().post(*args, **kwargs) return super().post(*args, **kwargs)

View File

@ -54,12 +54,6 @@
{% for col in form %} {% for col in form %}
<td> <td>
{{ col }} {{ col }}
{% comment %} <select class='select' id='id_col_{{ forloop.counter0 }}' name='col_guess_{{ forloop.counter0 }}'>
<option value=''>---------</option>
{% for req in headers %}
<option value='{{ req }}'{% if req == col.guess %}selected='selected'{% endif %}>{{ req }}</option>
{% endfor %}
</select> {% endcomment %}
{% for duplicate in duplicates %} {% for duplicate in duplicates %}
{% if duplicate == col.name %} {% if duplicate == col.name %}
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'> <div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>

View File

@ -40,7 +40,7 @@
</thead> </thead>
<tbody> <tbody>
{% for row in rows %} {% for row in rows %}
<tr {% if row.errors %} style='background: #ffeaea;'{% endif %} part-name='{{ row.part_name }}' part-description='{{ row.description }}' part-select='#select_part_{{ row.index }}'> <tr {% if row.errors %} style='background: #ffeaea;'{% endif %} part-select='#select_part_{{ row.index }}'>
<td> <td>
<button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='{% trans "Remove row" %}'> <button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='{% trans "Remove row" %}'>
<span row_id='{{ forloop.counter }}' class='fas fa-trash-alt icon-red'></span> <span row_id='{{ forloop.counter }}' class='fas fa-trash-alt icon-red'></span>
@ -51,26 +51,14 @@
{% add row.index 1 %} {% add row.index 1 %}
</td> </td>
<td> <td>
<button class='btn btn-default btn-create' onClick='newPartFromBomWizard()' id='new_part_row_{{ row.index }}' title='{% trans "Create new part" %}' type='button'>
<span row_id='{{ row.index }}' class='fas fa-plus icon-green'/>
</button>
{% for field in form.visible_fields %} {% for field in form.visible_fields %}
{% if field.name == row.item_select %} {% if field.name == row.item_select %}
{{ field }} {{ field }}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% comment %}
<select class='select bomselect' id='select_part_{{ row.index }}' name='part_{{ row.index }}'>
<option value=''>--- {% trans "Select Part" %} ---</option>
{% for part in row.part_options %}
<option value='{{ part.id }}' {% if part.id == row.part.id %} selected='selected' {% elif part.id == row.part_match.id %} selected='selected' {% endif %}>
{{ part }}
</option>
{% endfor %}
</select>
{% if row.errors.part %} {% if row.errors.part %}
<p class='help-inline'>{{ row.errors.part }}</p> <p class='help-inline'>{{ row.errors.part }}</p>
{% endif %} {% endcomment %} {% endif %}
</td> </td>
{% for item in row.data %} {% for item in row.data %}
<td> <td>
@ -80,7 +68,6 @@
{{ field }} {{ field }}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% comment %} <input name='quantity_{{ row.index }}' class='numberinput' type='number' min='1' step='any' value='{% decimal row.quantity %}'/> {% endcomment %}
{% if row.errors.quantity %} {% if row.errors.quantity %}
<p class='help-inline'>{{ row.errors.quantity }}</p> <p class='help-inline'>{{ row.errors.quantity }}</p>
{% endif %} {% endif %}