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 common.forms import MatchItemForm
from .models import Part, PartCategory, PartRelated from .models import Part, PartCategory, PartRelated
from .models import BomItem
from .models import PartParameterTemplate, PartParameter from .models import PartParameterTemplate, PartParameter
from .models import PartCategoryParameterTemplate from .models import PartCategoryParameterTemplate
from .models import PartSellPriceBreak, PartInternalPriceBreak 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): class PartPriceForm(forms.Form):
""" Simple form for viewing part pricing information """ """ Simple form for viewing part pricing information """

View File

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

View File

@ -259,22 +259,3 @@ class CategoryTest(PartViewTestCase):
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200) 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 # URL list for part web interface
part_urls = [ part_urls = [
@ -92,9 +88,6 @@ part_urls = [
url(r'^import/', views.PartImport.as_view(), name='part-import'), url(r'^import/', views.PartImport.as_view(), name='part-import'),
url(r'^import-api/', views.PartImportAjax.as_view(), name='api-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 # Download a BOM upload template
url(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='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 # Change category for multiple parts
url(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'), 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 # Individual part using IPN as slug
url(r'^(?P<slug>[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'), 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 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): class PartSalePriceBreakCreate(AjaxCreateView):
""" """
View for creating a sale price break for a part 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) { function reloadBomTable(table, options) {
table.bootstrapTable('refresh'); table.bootstrapTable('refresh');
@ -528,14 +548,15 @@ function loadBomTable(table, options) {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
var url = `/part/bom/${pk}/edit/`; var url = `/part/bom/${pk}/edit/`;
launchModalForm( var fields = bomItemFields();
url,
{ constructForm(`/api/bom/${pk}/`, {
success: function() { fields: fields,
reloadBomTable(table); title: '{% trans "Edit BOM Item" %}',
} onSuccess: function() {
reloadBomTable(table);
} }
); });
}); });
table.on('click', '.bom-validate-button', function() { table.on('click', '.bom-validate-button', function() {