diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index ba3f3525d5..a492ca353d 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -4,6 +4,15 @@ from wsgiref.util import FileWrapper from django.http import StreamingHttpResponse +def str2bool(text, test=True): + """ Test if a string 'looks' like a boolean value + """ + if test: + return str(text).lower() in ['1', 'y', 'yes', 't', 'true', 'ok', ] + else: + return str(text).lower() in ['0', 'n', 'no', 'none', 'f', 'false', ] + + def WrapWithQuotes(text): # TODO - Make this better if not text.startswith('"'): diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 4c25be2fe6..94dee8e95c 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django.db import models from django.contrib.contenttypes.models import ContentType from rest_framework.exceptions import ValidationError +from .helpers import str2bool from django.db.models.signals import pre_delete from django.dispatch import receiver @@ -183,7 +184,7 @@ def FilterChildren(queryset, parent): if not parent: return queryset - elif str(parent).lower() in ['none', 'false', 'null', 'top', '0']: + elif str2bool(parent, False): return queryset.filter(parent=None) else: try: diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index c97423ed86..df19003b21 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -32,7 +32,7 @@ {{ part.URL }} {% endif %} - + @@ -95,4 +95,17 @@ } ); }); + + $("#duplicate-part").click(function() { + launchModalForm( + "{% url 'part-create' %}", + { + follow: true, + data: { + copy: {{ part.id }}, + }, + } + ); + }); + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 5c5ad4f0a2..4f7175301a 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.views.generic import DetailView, ListView +from django.forms.models import model_to_dict from company.models import Company from .models import PartCategory, Part, BomItem @@ -49,6 +50,7 @@ class PartCreate(AjaxCreateView): """ model = Part form_class = EditPartForm + ajax_form_title = 'Create new part' ajax_template_name = 'modal_form.html' @@ -75,7 +77,19 @@ class PartCreate(AjaxCreateView): # Pre-fill the category field if a valid category is provided def get_initial(self): - initials = super(PartCreate, self).get_initial().copy() + # Is the client attempting to copy an existing part? + part_to_copy = self.request.GET.get('copy', None) + + if part_to_copy: + try: + original = Part.objects.get(pk=part_to_copy) + initials = model_to_dict(original) + self.ajax_form_title = "Copy Part '{p}'".format(p=original.name) + except Part.DoesNotExist: + initials = super(PartCreate, self).get_initial() + + else: + initials = super(PartCreate, self).get_initial() if self.get_category_id(): initials['category'] = get_object_or_404(PartCategory, pk=self.get_category_id()) @@ -115,6 +129,7 @@ class PartImage(AjaxUpdateView): class PartEdit(AjaxUpdateView): model = Part + template_name = 'part/edit.html' form_class = EditPartForm ajax_template_name = 'modal_form.html' ajax_form_title = 'Edit Part Properties' @@ -200,6 +215,7 @@ class BomDownload(AjaxView): class PartDelete(AjaxDeleteView): model = Part + template_name = 'part/delete.html' ajax_template_name = 'part/partial_delete.html' ajax_form_title = 'Confirm Part Deletion' context_object_name = 'part' @@ -221,6 +237,7 @@ class CategoryDetail(DetailView): class CategoryEdit(AjaxUpdateView): model = PartCategory + template_name = 'part/category_edit.html' form_class = EditCategoryForm ajax_template_name = 'modal_form.html' ajax_form_title = 'Edit Part Category' @@ -235,7 +252,7 @@ class CategoryEdit(AjaxUpdateView): class CategoryDelete(AjaxDeleteView): model = PartCategory - ajax_template_name = 'part/category_delete.html' + template_name = 'part/category_delete.html' context_object_name = 'category' success_url = '/part/' @@ -250,6 +267,7 @@ class CategoryCreate(AjaxCreateView): ajax_form_action = reverse_lazy('category-create') ajax_form_title = 'Create new part category' ajax_template_name = 'modal_form.html' + template_name = 'part/category_new.html' form_class = EditCategoryForm def get_context_data(self, **kwargs): @@ -282,6 +300,7 @@ class BomItemDetail(DetailView): class BomItemCreate(AjaxCreateView): model = BomItem form_class = EditBomItemForm + template_name = 'part/bom-create.html' ajax_template_name = 'modal_form.html' ajax_form_title = 'Create BOM item' @@ -301,6 +320,7 @@ class BomItemCreate(AjaxCreateView): class BomItemEdit(AjaxUpdateView): model = BomItem form_class = EditBomItemForm + template_name = 'part/bom-edit.html' ajax_template_name = 'modal_form.html' ajax_form_title = 'Edit BOM item' @@ -321,6 +341,7 @@ class SupplierPartDetail(DetailView): class SupplierPartEdit(AjaxUpdateView): model = SupplierPart + template_name = 'company/partedit.html' context_object_name = 'part' form_class = EditSupplierPartForm ajax_template_name = 'modal_form.html' diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index f1f0a5db1d..aee40d513d 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -13,6 +13,7 @@