diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 46734f9e15..19a0383b36 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -116,14 +116,14 @@ def rename_part_image(instance, filename): return os.path.join(base, fn) -def match_part_names(match, include_description=False, threshold=65, reverse=True): +def match_part_names(match, threshold=80, reverse=True, compare_length=False): """ Return a list of parts whose name matches the search term using fuzzy search. Args: match: Term to match against - include_description: Also search the part description (default = False) threshold: Match percentage that must be exceeded (default = 65) reverse: Ordering for search results (default = True - highest match is first) + compare_length: Include string length checks Returns: A sorted dict where each element contains the following key:value pairs: @@ -131,16 +131,29 @@ def match_part_names(match, include_description=False, threshold=65, reverse=Tru - 'ratio' : The matched ratio """ + match = str(match).strip().lower() + + if len(match) == 0: + return [] + parts = Part.objects.all() matches = [] for part in parts: - compare = part.name - if include_description: - compare += part.description + compare = str(part.name).strip().lower() - ratio = fuzz.partial_ratio(match, compare) + if len(compare) == 0: + continue + + ratio = fuzz.partial_ratio(compare, match) + + if compare_length: + # Also employ primitive length comparison + l_min = min(len(match), len(compare)) + l_max = max(len(match), len(compare)) + + ratio *= (l_min / l_max) if ratio >= threshold: matches.append({ diff --git a/InvenTree/part/templates/part/create_part.html b/InvenTree/part/templates/part/create_part.html new file mode 100644 index 0000000000..25e0d66597 --- /dev/null +++ b/InvenTree/part/templates/part/create_part.html @@ -0,0 +1,18 @@ +{% extends "modal_form.html" %} + +{% block pre_form_content %} + +{{ block.super }} + +{% if matches %} +Matching Parts + +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index c221cf3a1b..73555bfc0a 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -16,6 +16,7 @@ from company.models import Company from .models import PartCategory, Part, PartAttachment from .models import BomItem from .models import SupplierPart +from .models import match_part_names from . import forms as part_forms @@ -135,7 +136,7 @@ class PartCreate(AjaxCreateView): form_class = part_forms.EditPartForm ajax_form_title = 'Create new part' - ajax_template_name = 'modal_form.html' + ajax_template_name = 'part/create_part.html' def get_data(self): return { @@ -174,6 +175,28 @@ class PartCreate(AjaxCreateView): return form + def post(self, request, *args, **kwargs): + + form = self.get_form() + + name = request.POST.get('name', None) + + context = {} + + if name: + matches = match_part_names(name) + + if len(matches) > 0: + context['matches'] = matches + + form.non_field_errors = 'Check matches' + + data = { + 'form_valid': False + } + + return self.renderJsonResponse(request, form, data, context=context) + def get_initial(self): """ Get initial data for the new Part object: diff --git a/InvenTree/static/css/inventree.css b/InvenTree/static/css/inventree.css index 0961ed2bf1..9ce6f52400 100644 --- a/InvenTree/static/css/inventree.css +++ b/InvenTree/static/css/inventree.css @@ -27,6 +27,10 @@ padding: 6px 12px; } +.list-group-item-condensed { + padding: 5px 10px; +} + /* Force select2 elements in modal forms to be full width */ .select-full-width { width: 100%;