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. {% 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.
  • {{ part.full_name }} - {{ part.description }}
  • {% endfor %} -{% endif %} \ No newline at end of file +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/copy_part.html b/InvenTree/part/templates/part/copy_part.html new file mode 100644 index 0000000000..3abf0c5d54 --- /dev/null +++ b/InvenTree/part/templates/part/copy_part.html @@ -0,0 +1,24 @@ +{% extends "modal_form.html" %} + +{% block pre_form_content %} + +{{ block.super }} + +
    + Duplicate Part
    + Make a copy of part '{{ part.full_name }}'. +
    + +{% if matches %} +Possible Matching Parts +

    The new part may be a duplicate of these existing parts:

    + +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index ddb0523a83..c0f823ce13 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -14,8 +14,8 @@ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/modal_delete_form.html b/InvenTree/templates/modal_delete_form.html new file mode 100644 index 0000000000..df8553f26e --- /dev/null +++ b/InvenTree/templates/modal_delete_form.html @@ -0,0 +1 @@ +{% extends "modal_form.html" %} diff --git a/InvenTree/templates/modal_form.html b/InvenTree/templates/modal_form.html index e25d2587da..1a99fc09ff 100644 --- a/InvenTree/templates/modal_form.html +++ b/InvenTree/templates/modal_form.html @@ -1,6 +1,6 @@
    {% if form.pre_form_info %} -