mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Render column selection options
- Guess which header is which
This commit is contained in:
parent
60050e9f32
commit
fb96651c15
@ -46,13 +46,9 @@ class BomUploadManager:
|
|||||||
""" Class for managing an uploaded BOM file """
|
""" Class for managing an uploaded BOM file """
|
||||||
|
|
||||||
# Fields which are absolutely necessary for valid upload
|
# Fields which are absolutely necessary for valid upload
|
||||||
REQUIRED_HEADERS = [
|
HEADERS = [
|
||||||
'Part',
|
'Part',
|
||||||
'Quantity',
|
'Quantity',
|
||||||
]
|
|
||||||
|
|
||||||
# Fields which are not necessary but can be populated
|
|
||||||
USEFUL_HEADERS = [
|
|
||||||
'Reference',
|
'Reference',
|
||||||
'Overage',
|
'Overage',
|
||||||
'Notes'
|
'Notes'
|
||||||
@ -83,69 +79,49 @@ class BomUploadManager:
|
|||||||
except tablib.UnsupportedFormat:
|
except tablib.UnsupportedFormat:
|
||||||
raise ValidationError({'bom_file': _('Error reading BOM file (invalid data)')})
|
raise ValidationError({'bom_file': _('Error reading BOM file (invalid data)')})
|
||||||
|
|
||||||
# Now we have BOM data in memory!
|
def guess_headers(self, header, threshold=80):
|
||||||
|
""" Try to match a header (from the file) to a list of known headers
|
||||||
self.header_map = {}
|
|
||||||
|
Args:
|
||||||
for header in self.REQUIRED_HEADERS:
|
header - Header name to look for
|
||||||
match = self.extract_header(header)
|
threshold - Match threshold for fuzzy search
|
||||||
if match is None:
|
|
||||||
raise ValidationError({'bom_file': _("Missing required field '{f}'".format(f=header))})
|
|
||||||
else:
|
|
||||||
self.header_map[header] = match
|
|
||||||
|
|
||||||
for header in self.USEFUL_HEADERS:
|
|
||||||
match = self.extract_header(header)
|
|
||||||
|
|
||||||
self.header_map[header] = match
|
|
||||||
|
|
||||||
def get_header(self, header_name):
|
|
||||||
""" Returns the matching header name for the internal name """
|
|
||||||
|
|
||||||
if header_name in self.header_map.keys():
|
|
||||||
return self.header_map[header_name]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def extract_header(self, header_name, threshold=80):
|
|
||||||
""" Retrieve a matching column header from the uploaded file.
|
|
||||||
If there is not an exact match, try to match one that is close.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
headers = self.data.headers
|
# Try for an exact match
|
||||||
|
for h in self.HEADERS:
|
||||||
|
if h == header:
|
||||||
|
return h
|
||||||
|
|
||||||
# First, try for an exact match
|
# Try for a case-insensitive match
|
||||||
for header in headers:
|
for h in self.HEADERS:
|
||||||
if header == header_name:
|
if h.lower() == header.lower():
|
||||||
return header
|
return h
|
||||||
|
|
||||||
# Next, try for a case-insensitive match
|
|
||||||
for header in headers:
|
|
||||||
if header.lower() == header_name.lower():
|
|
||||||
return header
|
|
||||||
|
|
||||||
# Finally, look for a close match using fuzzy matching
|
# Finally, look for a close match using fuzzy matching
|
||||||
|
|
||||||
matches = []
|
matches = []
|
||||||
|
|
||||||
for header in headers:
|
for h in self.HEADERS:
|
||||||
|
ratio = fuzz.partial_ratio(header, h)
|
||||||
ratio = fuzz.partial_ratio(header, header_name)
|
|
||||||
if ratio > threshold:
|
if ratio > threshold:
|
||||||
matches.append({'header': header, 'match': ratio})
|
matches.append({'header': h, 'match': ratio})
|
||||||
|
|
||||||
if len(matches) > 0:
|
if len(matches) > 0:
|
||||||
matches = sorted(matches, key=lambda item: item['match'], reverse=True)
|
matches = sorted(matches, key=lambda item: item['match'], reverse=True)
|
||||||
|
|
||||||
# Return the field with the best match
|
|
||||||
return matches[0]['header']
|
return matches[0]['header']
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_headers(self):
|
def get_headers(self):
|
||||||
""" Return a list of headers for the thingy """
|
""" Return a list of headers for the thingy """
|
||||||
headers = []
|
headers = []
|
||||||
|
|
||||||
|
for header in self.data.headers:
|
||||||
|
headers.append({
|
||||||
|
'name': header,
|
||||||
|
'guess': self.guess_header(header)
|
||||||
|
})
|
||||||
|
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def col_count(self):
|
def col_count(self):
|
||||||
|
@ -16,11 +16,24 @@
|
|||||||
<table class='table table-striped'>
|
<table class='table table-striped'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Row</th>
|
||||||
|
{% for col in bom_cols %}
|
||||||
|
<th>
|
||||||
|
<select class='select' id='id_col_{{ forloop.counter0 }}' name='col_{{ forloop.counter0 }}'>
|
||||||
|
<option value=''>---------</option>
|
||||||
|
{% for req in req_cols %}
|
||||||
|
<option value='{{ req }}'{% if req == col.guess %}selected='selected'{% endif %}>{{ req }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{{ col.name }}
|
||||||
|
</th>
|
||||||
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in bom.rows %}
|
{% for row in bom_rows %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{% add forloop.counter 1 %}</td>
|
||||||
{% for item in row %}
|
{% for item in row %}
|
||||||
<td>
|
<td>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
|
@ -20,6 +20,12 @@ def multiply(x, y, *args, **kwargs):
|
|||||||
return x * y
|
return x * y
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag()
|
||||||
|
def add(x, y, *args, **kwargs):
|
||||||
|
""" Add two numbers together """
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def part_allocation_count(build, part, *args, **kwargs):
|
def part_allocation_count(build, part, *args, **kwargs):
|
||||||
""" Return the total number of <part> allocated to <build> """
|
""" Return the total number of <part> allocated to <build> """
|
||||||
|
@ -683,6 +683,13 @@ class BomUpload(AjaxView, FormMixin):
|
|||||||
return self.renderJsonResponse(request, self.form)
|
return self.renderJsonResponse(request, self.form)
|
||||||
|
|
||||||
def handleBomFileUpload(self):
|
def handleBomFileUpload(self):
|
||||||
|
""" Process a BOM file upload form.
|
||||||
|
|
||||||
|
This function validates that the uploaded file was valid,
|
||||||
|
and contains tabulated data that can be extracted.
|
||||||
|
If the file does not satisfy these requirements,
|
||||||
|
the "upload file" form is again shown to the user.
|
||||||
|
"""
|
||||||
|
|
||||||
bom_file = self.request.FILES.get('bom_file', None)
|
bom_file = self.request.FILES.get('bom_file', None)
|
||||||
|
|
||||||
@ -713,13 +720,25 @@ class BomUpload(AjaxView, FormMixin):
|
|||||||
# BOM file is valid? Proceed to the next step!
|
# BOM file is valid? Proceed to the next step!
|
||||||
form = part_forms.BomUploadSelectFields
|
form = part_forms.BomUploadSelectFields
|
||||||
self.ajax_template_name = 'part/bom_upload/select_fields.html'
|
self.ajax_template_name = 'part/bom_upload/select_fields.html'
|
||||||
ctx['bom'] = manager
|
|
||||||
|
# Try to guess at the
|
||||||
|
|
||||||
|
# Provide context to the next form
|
||||||
|
ctx = {
|
||||||
|
'req_cols': BomUploadManager.HEADERS,
|
||||||
|
'bom_cols': manager.get_headers(),
|
||||||
|
'bom_rows': manager.rows(),
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
form = self.form
|
form = self.form
|
||||||
|
|
||||||
return self.renderJsonResponse(self.request, form, data=data, context=ctx)
|
return self.renderJsonResponse(self.request, form, data=data, context=ctx)
|
||||||
|
|
||||||
def handleFieldSelection(self):
|
def handleFieldSelection(self):
|
||||||
|
""" Handle the output of the field selection form.
|
||||||
|
Here the user is presented with the raw data and must select the
|
||||||
|
column names and which rows to process.
|
||||||
|
"""
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'form_valid': False,
|
'form_valid': False,
|
||||||
@ -727,7 +746,10 @@ class BomUpload(AjaxView, FormMixin):
|
|||||||
|
|
||||||
self.ajax_template_name = 'part/bom_upload/select_fields.html'
|
self.ajax_template_name = 'part/bom_upload/select_fields.html'
|
||||||
|
|
||||||
ctx = {}
|
ctx = {
|
||||||
|
# The headers that we know about
|
||||||
|
'known_headers': BomUploadManager.HEADERS,
|
||||||
|
}
|
||||||
|
|
||||||
return self.renderJsonResponse(self.request, form=self.get_form(), data=data, context=ctx)
|
return self.renderJsonResponse(self.request, form=self.get_form(), data=data, context=ctx)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user