Merge pull request #1902 from SchrodingersGat/bom-item-form

Use API forms for creating and editing BomItem objects
This commit is contained in:
Oliver 2021-08-04 18:06:42 +10:00 committed by GitHub
commit 5aa111b0aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 43 additions and 207 deletions

View File

@ -18,7 +18,6 @@ import common.models
from common.forms import MatchItemForm
from .models import Part, PartCategory, PartRelated
from .models import BomItem
from .models import PartParameterTemplate, PartParameter
from .models import PartCategoryParameterTemplate
from .models import PartSellPriceBreak, PartInternalPriceBreak
@ -317,33 +316,6 @@ class EditCategoryParameterTemplateForm(HelperForm):
]
class EditBomItemForm(HelperForm):
""" Form for editing a BomItem object """
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity'))
sub_part = PartModelChoiceField(queryset=Part.objects.all(), label=_('Sub part'))
class Meta:
model = BomItem
fields = [
'part',
'sub_part',
'quantity',
'reference',
'overage',
'note',
'allow_variants',
'inherited',
'optional',
]
# Prevent editing of the part associated with this BomItem
widgets = {
'part': forms.HiddenInput()
}
class PartPriceForm(forms.Form):
""" Simple form for viewing part pricing information """

View File

@ -440,22 +440,22 @@
});
$("#bom-item-new").click(function () {
launchModalForm(
"{% url 'bom-item-create' %}?parent={{ part.id }}",
{
success: function() {
$("#bom-table").bootstrapTable('refresh');
},
secondary: [
{
field: 'sub_part',
label: '{% trans "New Part" %}',
title: '{% trans "Create New Part" %}',
url: "{% url 'part-create' %}",
},
]
var fields = bomItemFields();
fields.part.value = {{ part.pk }};
fields.sub_part.filters = {
active: true,
};
constructForm('{% url "api-bom-list" %}', {
fields: fields,
method: 'POST',
title: '{% trans "Create BOM Item" %}',
onSuccess: function() {
$('#bom-table').bootstrapTable('refresh');
}
);
});
});
{% else %}

View File

@ -259,22 +259,3 @@ class CategoryTest(PartViewTestCase):
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
class BomItemTests(PartViewTestCase):
""" Tests for BomItem related views """
def test_create_valid_parent(self):
""" Create a BomItem for a valid part """
response = self.client.get(reverse('bom-item-create'), {'parent': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
def test_create_no_parent(self):
""" Create a BomItem without a parent """
response = self.client.get(reverse('bom-item-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
def test_create_invalid_parent(self):
""" Create a BomItem with an invalid parent """
response = self.client.get(reverse('bom-item-create'), {'parent': 99999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)

View File

@ -78,10 +78,6 @@ category_urls = [
]))
]
part_bom_urls = [
url(r'^edit/?', views.BomItemEdit.as_view(), name='bom-item-edit'),
]
# URL list for part web interface
part_urls = [
@ -92,9 +88,6 @@ part_urls = [
url(r'^import/', views.PartImport.as_view(), name='part-import'),
url(r'^import-api/', views.PartImportAjax.as_view(), name='api-part-import'),
# Create a new BOM item
url(r'^bom/new/?', views.BomItemCreate.as_view(), name='bom-item-create'),
# Download a BOM upload template
url(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='bom-upload-template'),
@ -122,9 +115,6 @@ part_urls = [
# Change category for multiple parts
url(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'),
# Bom Items
url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
# Individual part using IPN as slug
url(r'^(?P<slug>[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'),

View File

@ -2078,134 +2078,6 @@ class CategoryParameterTemplateDelete(AjaxDeleteView):
return self.object
class BomItemCreate(AjaxCreateView):
"""
Create view for making a new BomItem object
"""
model = BomItem
form_class = part_forms.EditBomItemForm
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Create BOM Item')
def get_form(self):
""" Override get_form() method to reduce Part selection options.
- Do not allow part to be added to its own BOM
- Remove any Part items that are already in the BOM
"""
form = super(AjaxCreateView, self).get_form()
part_id = form['part'].value()
# Construct a queryset for the part field
part_query = Part.objects.filter(active=True)
# Construct a queryset for the sub_part field
sub_part_query = Part.objects.filter(
component=True,
active=True
)
try:
part = Part.objects.get(id=part_id)
# Hide the 'part' field
form.fields['part'].widget = HiddenInput()
# Exclude the part from its own BOM
sub_part_query = sub_part_query.exclude(id=part.id)
# Eliminate any options that are already in the BOM!
sub_part_query = sub_part_query.exclude(id__in=[item.id for item in part.getRequiredParts()])
except (ValueError, Part.DoesNotExist):
pass
# Set the querysets for the fields
form.fields['part'].queryset = part_query
form.fields['sub_part'].queryset = sub_part_query
return form
def get_initial(self):
""" Provide initial data for the BomItem:
- If 'parent' provided, set the parent part field
"""
# Look for initial values
initials = super(BomItemCreate, self).get_initial().copy()
# Parent part for this item?
parent_id = self.request.GET.get('parent', None)
if parent_id:
try:
initials['part'] = Part.objects.get(pk=parent_id)
except Part.DoesNotExist:
pass
return initials
class BomItemEdit(AjaxUpdateView):
""" Update view for editing BomItem """
model = BomItem
form_class = part_forms.EditBomItemForm
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit BOM item')
def get_form(self):
""" Override get_form() method to filter part selection options
- Do not allow part to be added to its own BOM
- Remove any part items that are already in the BOM
"""
item = self.get_object()
form = super().get_form()
part_id = form['part'].value()
try:
part = Part.objects.get(pk=part_id)
# Construct a queryset
query = Part.objects.filter(component=True)
# Limit to "active" items, *unless* the currently selected item is not active
if item.sub_part.active:
query = query.filter(active=True)
# Prevent the parent part from being selected
query = query.exclude(pk=part_id)
# Eliminate any options that are already in the BOM,
# *except* for the item which is already selected
try:
sub_part_id = int(form['sub_part'].value())
except ValueError:
sub_part_id = -1
existing = [item.pk for item in part.getRequiredParts()]
if sub_part_id in existing:
existing.remove(sub_part_id)
query = query.exclude(id__in=existing)
form.fields['sub_part'].queryset = query
except (ValueError, Part.DoesNotExist):
pass
return form
class PartSalePriceBreakCreate(AjaxCreateView):
"""
View for creating a sale price break for a part

View File

@ -8,6 +8,26 @@
*/
function bomItemFields() {
return {
part: {
hidden: true,
},
sub_part: {
},
quantity: {},
reference: {},
overage: {},
note: {},
allow_variants: {},
inherited: {},
optional: {},
};
}
function reloadBomTable(table, options) {
table.bootstrapTable('refresh');
@ -528,14 +548,15 @@ function loadBomTable(table, options) {
var pk = $(this).attr('pk');
var url = `/part/bom/${pk}/edit/`;
launchModalForm(
url,
{
success: function() {
reloadBomTable(table);
}
var fields = bomItemFields();
constructForm(`/api/bom/${pk}/`, {
fields: fields,
title: '{% trans "Edit BOM Item" %}',
onSuccess: function() {
reloadBomTable(table);
}
);
});
});
table.on('click', '.bom-validate-button', function() {