diff --git a/InvenTree/part/templates/part/variant_part.html b/InvenTree/part/templates/part/variant_part.html
new file mode 100644
index 0000000000..eac8762947
--- /dev/null
+++ b/InvenTree/part/templates/part/variant_part.html
@@ -0,0 +1,12 @@
+{% extends "modal_form.html" %}
+
+{% block pre_form_content %}
+
+{{ block.super }}
+
+
+ Create new part variant
+ Create a new variant of template '{{ part.full_name }}'.
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/part/templates/part/variants.html b/InvenTree/part/templates/part/variants.html
index 0cf9735364..98ac08a8d3 100644
--- a/InvenTree/part/templates/part/variants.html
+++ b/InvenTree/part/templates/part/variants.html
@@ -60,4 +60,13 @@
sortable: true,
});
+ $('#new-variant').click(function() {
+ launchModalForm(
+ "{% url 'make-part-variant' part.id %}",
+ {
+ follow: true,
+ }
+ );
+ });
+
{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py
index c8582160bb..0147cd4d07 100644
--- a/InvenTree/part/urls.py
+++ b/InvenTree/part/urls.py
@@ -24,6 +24,7 @@ part_detail_urls = [
url(r'^bom-export/?', views.BomDownload.as_view(), name='bom-export'),
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'),
url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
url(r'^variants/?', views.PartDetail.as_view(template_name='part/variants.html'), name='part-variants'),
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 02b5b19909..1d33cec347 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -124,6 +124,77 @@ class PartAttachmentDelete(AjaxDeleteView):
}
+class MakePartVariant(AjaxCreateView):
+ """ View for creating a new variant based on an existing template Part
+
+ - Part is provided in the URL '/part//make_variant/'
+ - Automatically copy relevent data (BOM, etc, etc)
+
+ """
+
+ model = Part
+ form_class = part_forms.EditPartForm
+
+ ajax_form_title = 'Create Variant'
+ ajax_template_name = 'part/variant_part.html'
+
+ def get_part_template(self):
+ return get_object_or_404(Part, id=self.kwargs['pk'])
+
+ def get_context_data(self):
+ return {
+ 'part': self.get_part_template(),
+ }
+
+ def get_form(self):
+ form = super(AjaxCreateView, self).get_form()
+
+ # Hide some variant-related fields
+ form.fields['is_template'].widget = HiddenInput()
+ form.fields['variant_of'].widget = HiddenInput()
+
+ return form
+
+ def post(self, request, *args, **kwargs):
+
+ form = self.get_form()
+ context = self.get_context_data()
+ part_template = self.get_part_template()
+
+ valid = form.is_valid()
+
+ data = {
+ 'form_valid': valid,
+ }
+
+ if valid:
+ # Create the new part variant
+ part = form.save(commit=False)
+ part.variant_of = part_template
+ part.is_template = False
+
+ part.save()
+
+ data['pk'] = part.pk
+ data['text'] = str(part)
+ data['url'] = part.get_absolute_url()
+
+ # Copy relevent information from the template part
+ part.deepCopy(part_template, bom=True)
+
+ return self.renderJsonResponse(request, form, data, context=context)
+
+ def get_initial(self):
+
+ part_template = self.get_part_template()
+
+ initials = model_to_dict(part_template)
+ initials['is_template'] = False
+ initials['variant_of'] = part_template
+
+ return initials
+
+
class PartDuplicate(AjaxCreateView):
""" View for duplicating an existing Part object.
@@ -209,7 +280,7 @@ class PartDuplicate(AjaxCreateView):
original = self.get_part_to_copy()
if original:
- part.deepCopy(original, bom=deep_copy)
+ part.deepCopy(original, bom=deep_copy)
try:
data['url'] = part.get_absolute_url()