mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add new view to duplicate a part
- Allows 'deep_copy' (copies all BOM items for the duplicated part)
This commit is contained in:
parent
6ae185ec0e
commit
2408318eae
@ -116,7 +116,7 @@ class AjaxMixin(object):
|
|||||||
"""
|
"""
|
||||||
return {}
|
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.
|
""" Render a JSON response based on specific class context.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -129,6 +129,12 @@ class AjaxMixin(object):
|
|||||||
JSON response object
|
JSON response object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if context is None:
|
||||||
|
try:
|
||||||
|
context = self.get_context_data()
|
||||||
|
except AttributeError:
|
||||||
|
context = {}
|
||||||
|
|
||||||
if form:
|
if form:
|
||||||
context['form'] = form
|
context['form'] = form
|
||||||
|
|
||||||
|
@ -75,11 +75,20 @@ class EditPartAttachmentForm(HelperForm):
|
|||||||
class EditPartForm(HelperForm):
|
class EditPartForm(HelperForm):
|
||||||
""" Form for editing a Part object """
|
""" 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:
|
class Meta:
|
||||||
model = Part
|
model = Part
|
||||||
fields = [
|
fields = [
|
||||||
|
'deep_copy',
|
||||||
'confirm_creation',
|
'confirm_creation',
|
||||||
'category',
|
'category',
|
||||||
'name',
|
'name',
|
||||||
|
@ -533,6 +533,19 @@ class Part(models.Model):
|
|||||||
""" Return the number of supplier parts available for this part """
|
""" Return the number of supplier parts available for this part """
|
||||||
return self.supplier_parts.count()
|
return self.supplier_parts.count()
|
||||||
|
|
||||||
|
def copyBomFrom(self, other):
|
||||||
|
""" Duplicates the BOM from another part.
|
||||||
|
|
||||||
|
This should only be called during part creation,
|
||||||
|
and it does not delete any existing BOM items for *this* part.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for item in other.bom_items.all():
|
||||||
|
# Point the item to THIS part
|
||||||
|
item.part = self
|
||||||
|
item.pk = None
|
||||||
|
item.save()
|
||||||
|
|
||||||
def export_bom(self, **kwargs):
|
def export_bom(self, **kwargs):
|
||||||
|
|
||||||
data = tablib.Dataset(headers=[
|
data = tablib.Dataset(headers=[
|
||||||
|
24
InvenTree/part/templates/part/copy_part.html
Normal file
24
InvenTree/part/templates/part/copy_part.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% extends "modal_form.html" %}
|
||||||
|
|
||||||
|
{% block pre_form_content %}
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
<div class='alert alert-info alert-block'>
|
||||||
|
<b>Duplicate Part</b><br>
|
||||||
|
Make a copy of part '{{ part.full_name }}'.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if matches %}
|
||||||
|
<b>Possible Matching Parts</b>
|
||||||
|
<p>The new part may be a duplicate of these existing parts:</p>
|
||||||
|
<ul class='list-group'>
|
||||||
|
{% for match in matches %}
|
||||||
|
<li class='list-group-item list-group-item-condensed'>
|
||||||
|
{{ match.part.full_name }} - <i>{{ match.part.description }}</i> ({{ match.ratio }}%)
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -131,12 +131,9 @@
|
|||||||
|
|
||||||
$("#duplicate-part").click(function() {
|
$("#duplicate-part").click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'part-create' %}",
|
"{% url 'part-duplicate' part.id %}",
|
||||||
{
|
{
|
||||||
follow: true,
|
follow: true,
|
||||||
data: {
|
|
||||||
copy: {{ part.id }},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -36,6 +36,7 @@ part_detail_urls = [
|
|||||||
url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
|
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.BomDownload.as_view(), name='bom-export'),
|
||||||
url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'),
|
url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'),
|
||||||
|
url(r'^duplicate/', views.PartDuplicate.as_view(), name='part-duplicate'),
|
||||||
|
|
||||||
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
||||||
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
||||||
|
@ -124,13 +124,124 @@ class PartAttachmentDelete(AjaxDeleteView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PartDuplicate(AjaxCreateView):
|
||||||
|
""" View for duplicating an existing Part object.
|
||||||
|
|
||||||
|
- Part <pk> is provided in the URL '/part/<pk>/copy/'
|
||||||
|
- Option for 'deep-copy' which will duplicate all BOM items (default = True)
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = Part
|
||||||
|
form_class = part_forms.EditPartForm
|
||||||
|
|
||||||
|
ajax_form_title = "Duplicate Part"
|
||||||
|
ajax_template_name = "part/copy_part.html"
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return {
|
||||||
|
'success': 'Copied part'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_part_to_copy(self):
|
||||||
|
try:
|
||||||
|
return Part.objects.get(id=self.kwargs['pk'])
|
||||||
|
except Part.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
return {
|
||||||
|
'part': self.get_part_to_copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
form = super(AjaxCreateView, self).get_form()
|
||||||
|
|
||||||
|
# Force display of the 'deep_copy' widget
|
||||||
|
form.fields['deep_copy'].widget = CheckboxInput()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
""" Capture the POST request for part duplication
|
||||||
|
|
||||||
|
- If the deep_copy object is set, copy all the BOM items too!
|
||||||
|
"""
|
||||||
|
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
|
||||||
|
valid = form.is_valid()
|
||||||
|
|
||||||
|
name = request.POST.get('name', None)
|
||||||
|
|
||||||
|
if name:
|
||||||
|
matches = match_part_names(name)
|
||||||
|
|
||||||
|
if len(matches) > 0:
|
||||||
|
context['matches'] = matches
|
||||||
|
|
||||||
|
# Enforce display of the checkbox
|
||||||
|
form.fields['confirm_creation'].widget = CheckboxInput()
|
||||||
|
|
||||||
|
# Check if the user has checked the 'confirm_creation' input
|
||||||
|
confirmed = str2bool(request.POST.get('confirm_creation', False))
|
||||||
|
|
||||||
|
if not confirmed:
|
||||||
|
form.errors['confirm_creation'] = ['Possible matches exist - confirm creation of new part']
|
||||||
|
|
||||||
|
form.pre_form_warning = 'Possible matches exist - confirm creation of new part'
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'form_valid': valid
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
# Create the new Part
|
||||||
|
part = form.save()
|
||||||
|
|
||||||
|
data['pk'] = part.pk
|
||||||
|
|
||||||
|
deep_copy = str2bool(request.POST.get('deep_copy', False))
|
||||||
|
|
||||||
|
original = self.get_part_to_copy()
|
||||||
|
|
||||||
|
if original:
|
||||||
|
if deep_copy:
|
||||||
|
part.copyBomFrom(original)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data['url'] = part.get_absolute_url()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, form, data, context=context)
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
""" Get initial data based on the Part to be copied from.
|
||||||
|
"""
|
||||||
|
|
||||||
|
part = self.get_part_to_copy()
|
||||||
|
|
||||||
|
if part:
|
||||||
|
initials = model_to_dict(part)
|
||||||
|
else:
|
||||||
|
initials = super(AjaxCreateView, self).get_initials()
|
||||||
|
|
||||||
|
return initials
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PartCreate(AjaxCreateView):
|
class PartCreate(AjaxCreateView):
|
||||||
""" View for creating a new Part object.
|
""" View for creating a new Part object.
|
||||||
|
|
||||||
Options for providing initial conditions:
|
Options for providing initial conditions:
|
||||||
|
|
||||||
- Provide a category object as initial data
|
- Provide a category object as initial data
|
||||||
- Copy an existing Part
|
|
||||||
"""
|
"""
|
||||||
model = Part
|
model = Part
|
||||||
form_class = part_forms.EditPartForm
|
form_class = part_forms.EditPartForm
|
||||||
@ -224,22 +335,9 @@ class PartCreate(AjaxCreateView):
|
|||||||
""" Get initial data for the new Part object:
|
""" Get initial data for the new Part object:
|
||||||
|
|
||||||
- If a category is provided, pre-fill the Category field
|
- If a category is provided, pre-fill the Category field
|
||||||
- If 'copy' parameter is provided, copy from referenced Part
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Is the client attempting to copy an existing part?
|
initials = super(PartCreate, self).get_initial()
|
||||||
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():
|
if self.get_category_id():
|
||||||
try:
|
try:
|
||||||
|
@ -162,6 +162,10 @@
|
|||||||
z-index: 99999999;
|
z-index: 99999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.js-modal-form .checkbox {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
width: 45%;
|
width: 45%;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
{% if form.pre_form_info %}
|
{% if form.pre_form_info %}
|
||||||
<div class='alert alert-info' role='alert' style='display: block;'>
|
<div class='alert alert-info alert-block' role='alert'>
|
||||||
{{ form.pre_form_info }}
|
{{ form.pre_form_info }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
Loading…
Reference in New Issue
Block a user