diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py
index ba203b2f1b..7ec4401101 100644
--- a/InvenTree/InvenTree/forms.py
+++ b/InvenTree/InvenTree/forms.py
@@ -17,3 +17,19 @@ class HelperForm(forms.ModelForm):
self.helper = FormHelper()
self.helper.form_tag = False
+
+
+class DeleteForm(forms.Form):
+ """ Generic deletion form which provides simple user confirmation
+ """
+
+ confirm_delete = forms.BooleanField(
+ required=False,
+ initial=False,
+ help_text='Confirm item deletion'
+ )
+
+ class Meta:
+ fields = [
+ 'confirm_delete'
+ ]
diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index b837b3da59..08ca9e0eb5 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -12,11 +12,14 @@ from django.template.loader import render_to_string
from django.http import JsonResponse
from django.views import View
-from django.views.generic import UpdateView, CreateView, DeleteView
+from django.views.generic import UpdateView, CreateView
from django.views.generic.base import TemplateView
from part.models import Part
+from .forms import DeleteForm
+from .helpers import str2bool
+
from rest_framework import views
@@ -116,7 +119,7 @@ class AjaxMixin(object):
"""
return {}
- def renderJsonResponse(self, request, form=None, data={}, context={}):
+ def renderJsonResponse(self, request, form=None, data={}, context=None):
""" Render a JSON response based on specific class context.
Args:
@@ -129,6 +132,12 @@ class AjaxMixin(object):
JSON response object
"""
+ if context is None:
+ try:
+ context = self.get_context_data()
+ except AttributeError:
+ context = {}
+
if form:
context['form'] = form
@@ -294,13 +303,28 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
return self.renderJsonResponse(request, form, data)
-class AjaxDeleteView(AjaxMixin, DeleteView):
+class AjaxDeleteView(AjaxMixin, UpdateView):
""" An 'AJAXified DeleteView for removing an object from the DB
- Returns a HTML object (not a form!) in JSON format (for delivery to a modal window)
- Handles deletion
"""
+ form_class = DeleteForm
+ ajax_form_title = "Delete Item"
+ ajax_template_name = "modal_delete_form.html"
+ context_object_name = 'item'
+
+ def get_object(self):
+ try:
+ self.object = self.model.objects.get(pk=self.kwargs['pk'])
+ except:
+ return None
+ return self.object
+
+ def get_form(self):
+ return self.form_class(self.get_form_kwargs())
+
def get(self, request, *args, **kwargs):
""" Respond to GET request
@@ -308,19 +332,15 @@ class AjaxDeleteView(AjaxMixin, DeleteView):
- Return rendered form to client
"""
- super(DeleteView, self).get(request, *args, **kwargs)
+ super(UpdateView, self).get(request, *args, **kwargs)
- data = {
- 'id': self.get_object().id,
- 'delete': False,
- 'title': self.ajax_form_title,
- 'html_data': render_to_string(
- self.ajax_template_name,
- self.get_context_data(),
- request=request)
- }
+ form = self.get_form()
- return JsonResponse(data)
+ context = self.get_context_data()
+
+ context[self.context_object_name] = self.get_object()
+
+ return self.renderJsonResponse(request, form, context=context)
def post(self, request, *args, **kwargs):
""" Respond to POST request
@@ -331,14 +351,24 @@ class AjaxDeleteView(AjaxMixin, DeleteView):
obj = self.get_object()
pk = obj.id
- obj.delete()
+
+ form = self.get_form()
+
+ confirmed = str2bool(request.POST.get('confirm_delete', False))
+ context = self.get_context_data()
+
+ if confirmed:
+ obj.delete()
+ else:
+ form.errors['confirm_delete'] = ['Check box to confirm item deletion']
+ context[self.context_object_name] = self.get_object()
data = {
'id': pk,
- 'delete': True
+ 'form_valid': confirmed
}
- return self.renderJsonResponse(request, data=data)
+ return self.renderJsonResponse(request, form, data=data, context=context)
class IndexView(TemplateView):
diff --git a/InvenTree/build/templates/build/delete_build_item.html b/InvenTree/build/templates/build/delete_build_item.html
index 23993feb95..37ed0dfa32 100644
--- a/InvenTree/build/templates/build/delete_build_item.html
+++ b/InvenTree/build/templates/build/delete_build_item.html
@@ -1,3 +1,7 @@
+{% extends "modal_delete_form.html" %}
+
+{% block pre_form_content %}
Are you sure you want to unallocate these parts?
-This will remove {{ item.quantity }} parts from build '{{ item.build.title }}'.
\ No newline at end of file
+This will remove {{ item.quantity }} parts from build '{{ item.build.title }}'.
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/company/templates/company/delete.html b/InvenTree/company/templates/company/delete.html
index 1069d790e3..9a94404739 100644
--- a/InvenTree/company/templates/company/delete.html
+++ b/InvenTree/company/templates/company/delete.html
@@ -1,3 +1,7 @@
+{% extends "modal_delete_form.html" %}
+
+{% block pre_form_content %}
+
Are you sure you want to delete company '{{ company.name }}'?
@@ -11,3 +15,5 @@ If this supplier is deleted, these supplier part entries will also be deleted.
{% endfor %}
{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/company/templates/company/detail.html b/InvenTree/company/templates/company/detail.html
index 0068d150ae..8e26d85a89 100644
--- a/InvenTree/company/templates/company/detail.html
+++ b/InvenTree/company/templates/company/detail.html
@@ -54,7 +54,7 @@
});
$('#delete-company').click(function() {
- launchDeleteForm(
+ launchModalForm(
"{% url 'company-delete' company.id %}",
{
redirect: "{% url 'company-index' %}"
diff --git a/InvenTree/company/templates/company/partdelete.html b/InvenTree/company/templates/company/partdelete.html
index 13ba71e2ca..ba6b78f841 100644
--- a/InvenTree/company/templates/company/partdelete.html
+++ b/InvenTree/company/templates/company/partdelete.html
@@ -1 +1,5 @@
-Are you sure you want to delete this supplier part?
\ No newline at end of file
+{% extends "modal_delete_form.html" %}
+
+{% block pre_form_content %}
+Are you sure you want to delete this supplier part?
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/company/templates/company/partdetail.html b/InvenTree/company/templates/company/partdetail.html
index 2924f05453..b2fdf8aeb9 100644
--- a/InvenTree/company/templates/company/partdetail.html
+++ b/InvenTree/company/templates/company/partdetail.html
@@ -109,7 +109,7 @@ InvenTree | {{ company.name }} - Parts
});
$('#delete-part').click(function() {
- launchDeleteForm(
+ launchModalForm(
"{% url 'supplier-part-delete' part.id %}",
{
redirect: "{% url 'company-index' %}"
diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py
index f61da1fffa..0630556946 100644
--- a/InvenTree/company/views.py
+++ b/InvenTree/company/views.py
@@ -93,10 +93,12 @@ class CompanyCreate(AjaxCreateView):
class CompanyDelete(AjaxDeleteView):
""" View for deleting a Company object """
+
model = Company
success_url = '/company/'
ajax_template_name = 'company/delete.html'
ajax_form_title = 'Delete Company'
+ context_object_name = 'company'
def get_data(self):
return {
diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py
index 2256f28210..580ed737a4 100644
--- a/InvenTree/part/forms.py
+++ b/InvenTree/part/forms.py
@@ -75,11 +75,20 @@ class EditPartAttachmentForm(HelperForm):
class EditPartForm(HelperForm):
""" Form for editing a Part object """
- confirm_creation = forms.BooleanField(required=False, initial=False, help_text='Confirm part creation', widget=forms.HiddenInput())
+ deep_copy = forms.BooleanField(required=False,
+ initial=True,
+ help_text="Perform 'deep copy' which will duplicate all BOM data for this part",
+ widget=forms.HiddenInput())
+
+ confirm_creation = forms.BooleanField(required=False,
+ initial=False,
+ help_text='Confirm part creation',
+ widget=forms.HiddenInput())
class Meta:
model = Part
fields = [
+ 'deep_copy',
'confirm_creation',
'category',
'name',
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index d119c3094e..5a609c0e59 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -16,6 +16,7 @@ from django.core.exceptions import ValidationError
from django.urls import reverse
from django.conf import settings
+from django.core.files.base import ContentFile
from django.db import models, transaction
from django.core.validators import MinValueValidator
@@ -533,6 +534,33 @@ class Part(models.Model):
""" Return the number of supplier parts available for this part """
return self.supplier_parts.count()
+ def deepCopy(self, other, **kwargs):
+ """ Duplicates non-field data from another part.
+ Does not alter the normal fields of this part,
+ but can be used to copy other data linked by ForeignKey refernce.
+
+ Keyword Args:
+ image: If True, copies Part image (default = True)
+ bom: If True, copies BOM data (default = False)
+ """
+
+ # Copy the part image
+ if kwargs.get('image', True):
+ image_file = ContentFile(other.image.read())
+ image_file.name = rename_part_image(self, 'test.png')
+
+ self.image = image_file
+
+ # Copy the BOM data
+ if kwargs.get('bom', False):
+ for item in other.bom_items.all():
+ # Point the item to THIS part
+ item.part = self
+ item.pk = None
+ item.save()
+
+ self.save()
+
def export_bom(self, **kwargs):
data = tablib.Dataset(headers=[
diff --git a/InvenTree/part/templates/part/attachment_delete.html b/InvenTree/part/templates/part/attachment_delete.html
index db98b7f6d6..1adffcc710 100644
--- a/InvenTree/part/templates/part/attachment_delete.html
+++ b/InvenTree/part/templates/part/attachment_delete.html
@@ -1,3 +1,7 @@
+{% extends "modal_delete_form.html" %}
+
+{% block pre_form_content %}
Are you sure you wish to delete this attachment?
-This will remove the file '{{ attachment.basename }}'.
\ No newline at end of file
+This will remove the file '{{ attachment.basename }}'.
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/part/templates/part/attachments.html b/InvenTree/part/templates/part/attachments.html
index e3bed202c4..d0ccaf122d 100644
--- a/InvenTree/part/templates/part/attachments.html
+++ b/InvenTree/part/templates/part/attachments.html
@@ -62,7 +62,7 @@
$("#attachment-table").on('click', '.attachment-delete-button', function() {
var button = $(this);
- launchDeleteForm(button.attr('url'), {
+ launchModalForm(button.attr('url'), {
success: function() {
location.reload();
}
diff --git a/InvenTree/part/templates/part/bom-delete.html b/InvenTree/part/templates/part/bom-delete.html
index 492a6eeeda..ffaf2c57e5 100644
--- a/InvenTree/part/templates/part/bom-delete.html
+++ b/InvenTree/part/templates/part/bom-delete.html
@@ -1,3 +1,7 @@
+{% extends "modal_delete_form.html" %}
+
+{% block pre_form_content %}
+
Are you sure you want to delete this BOM item?
Deleting this entry will remove the BOM row from the following part:
@@ -7,3 +11,5 @@ Deleting this entry will remove the BOM row from the following part:
{{ item.part.full_name }} - {{ item.part.description }}
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html
index 0e83a68a9a..286b5fc418 100644
--- a/InvenTree/part/templates/part/category.html
+++ b/InvenTree/part/templates/part/category.html
@@ -111,7 +111,7 @@
{% endif %}
$('#cat-delete').click(function() {
- launchDeleteForm("{% url 'category-delete' category.id %}",
+ launchModalForm("{% url 'category-delete' category.id %}",
{
redirect: redirect
});
diff --git a/InvenTree/part/templates/part/category_delete.html b/InvenTree/part/templates/part/category_delete.html
index 298499ab1a..98f6ec9590 100644
--- a/InvenTree/part/templates/part/category_delete.html
+++ b/InvenTree/part/templates/part/category_delete.html
@@ -1,3 +1,6 @@
+{% extends "modal_delete_form.html" %}
+
+{% block pre_form_content %}
Are you sure you want to delete category '{{ category.name }}'?
{% if category.children.all|length > 0 %}
@@ -30,4 +33,6 @@ the top level 'Parts' category.
The new part may be a duplicate of these existing parts:
+