diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 6c89ff7265..3725d79c7b 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -52,6 +52,10 @@ font-size: 12px; } +.glyphicon-right { + float: right; +} + .starred-part { color: #ffbb00; } diff --git a/InvenTree/InvenTree/static/script/inventree/bom.js b/InvenTree/InvenTree/static/script/inventree/bom.js index 8d0accb83b..1b74088abf 100644 --- a/InvenTree/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/InvenTree/static/script/inventree/bom.js @@ -133,7 +133,14 @@ function loadBomTable(table, options) { title: 'Part', sortable: true, formatter: function(value, row, index, field) { - return imageHoverIcon(row.sub_part_detail.image_url) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url); + var html = imageHoverIcon(row.sub_part_detail.image_url) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url); + + // Display an extra icon if this part is an assembly + if (row.sub_part_detail.assembly) { + html += ""; + } + + return html; } } ); diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 7a4eecd583..5adf1e08dd 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -6,6 +6,7 @@ Django Forms for interacting with Part objects from __future__ import unicode_literals from InvenTree.forms import HelperForm +from InvenTree.helpers import GetExportFormats from mptt.fields import TreeNodeChoiceField from django import forms @@ -28,6 +29,26 @@ class PartImageForm(HelperForm): ] +class BomExportForm(forms.Form): + """ Simple form to let user set BOM export options, + before exporting a BOM (bill of materials) file. + """ + + file_format = forms.ChoiceField(label=_("File Format"), help_text=_("Select output file format")) + + cascading = forms.BooleanField(label=_("Cascading"), required=False, initial=False, help_text=_("Download cascading / multi-level BOM")) + + def get_choices(self): + """ BOM export format choices """ + + return [(x, x.upper()) for x in GetExportFormats()] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields['file_format'].choices = self.get_choices() + + class BomValidateForm(HelperForm): """ Simple confirmation form for BOM validation. User is presented with a single checkbox input, diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 3e1ed6949a..c23298a441 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -65,6 +65,8 @@ class PartBriefSerializer(InvenTreeModelSerializer): 'available_stock', 'image_url', 'active', + 'assembly', + 'virtual', ] diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index 3d61e24e2a..374acc94fd 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -44,16 +44,7 @@ {% if part.is_bom_valid == False %} {% endif %} -
- -
+ {% endif %} @@ -119,8 +110,14 @@ location.href = "{% url 'part-bom' part.id %}?edit=1"; }); - $(".download-bom").click(function () { - location.href = "{% url 'bom-export' part.id %}?format=" + $(this).attr('format'); + $("#download-bom").click(function () { + launchModalForm("{% url 'bom-export' part.id %}", + { + success: function(response) { + location.href = response.url; + }, + } + ); }); {% endif %} diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 48e647302a..36be170bcb 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -33,7 +33,8 @@ part_parameter_urls = [ part_detail_urls = [ url(r'^edit/?', views.PartEdit.as_view(), name='part-edit'), url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'), - url(r'^bom-export/?', views.BomDownload.as_view(), name='bom-export'), + url(r'^bom-export/?', views.BomExport.as_view(), name='bom-export'), + url(r'^bom-download/?', views.BomDownload.as_view(), name='bom-download'), url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'), url(r'^duplicate/', views.PartDuplicate.as_view(), name='part-duplicate'), url(r'^make-variant/', views.MakePartVariant.as_view(), name='make-part-variant'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 24717d55bb..c894c6ba99 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1332,7 +1332,7 @@ class BomDownload(AjaxView): part = get_object_or_404(Part, pk=self.kwargs['pk']) - export_format = request.GET.get('format', 'csv') + export_format = request.GET.get('file_format', 'csv') if not IsValidBOMFormat(export_format): export_format = 'csv' @@ -1345,6 +1345,47 @@ class BomDownload(AjaxView): } +class BomExport(AjaxView): + """ Provide a simple form to allow the user to select BOM download options. + """ + + model = Part + form_class = part_forms.BomExportForm + ajax_form_title = _("Export Bill of Materials") + + def get(self, request, *args, **kwargs): + return self.renderJsonResponse(request, self.form_class()) + + def post(self, request, *args, **kwargs): + + # Extract POSTed form data + fmt = request.POST.get('file_format', 'csv').lower() + cascade = str2bool(request.POST.get('cascading', False)) + + try: + part = Part.objects.get(pk=self.kwargs['pk']) + except: + part = None + + # Format a URL to redirect to + if part: + url = reverse('bom-download', kwargs={'pk': part.pk}) + else: + url = '' + + url += '?file_format=' + fmt + url += '&cascade=' + str(cascade) + + print("URL:", url) + + data = { + 'form_valid': part is not None, + 'url': url, + } + + return self.renderJsonResponse(request, self.form_class(), data=data) + + class PartDelete(AjaxDeleteView): """ View to delete a Part object """