Add new view to duplicate a part

- Allows 'deep_copy' (copies all BOM items for the duplicated part)
This commit is contained in:
Oliver Walters 2019-05-13 21:41:32 +10:00
parent 6ae185ec0e
commit 2408318eae
9 changed files with 174 additions and 22 deletions

View File

@ -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

View File

@ -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',

View File

@ -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=[

View 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 %}

View File

@ -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 }},
},
} }
); );
}); });

View File

@ -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'),

View File

@ -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:

View File

@ -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%;
} }

View File

@ -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 %}