CreatePart form now uses the API

- Simplify the way category parameter templates are copied
This commit is contained in:
Oliver Walters 2021-08-04 23:27:16 +10:00
parent 2bf3e3ab02
commit b04f22fc53
7 changed files with 74 additions and 227 deletions

View File

@ -637,7 +637,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'PART_PURCHASEABLE': { 'PART_PURCHASEABLE': {
'name': _('Purchaseable'), 'name': _('Purchaseable'),
'description': _('Parts are purchaseable by default'), 'description': _('Parts are purchaseable by default'),
'default': False, 'default': True,
'validator': bool, 'validator': bool,
}, },
@ -662,6 +662,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'validator': bool, 'validator': bool,
}, },
# TODO: Remove this setting in future, new API forms make this not useful
'PART_SHOW_QUANTITY_IN_FORMS': { 'PART_SHOW_QUANTITY_IN_FORMS': {
'name': _('Show Quantity in Forms'), 'name': _('Show Quantity in Forms'),
'description': _('Display available part quantity in some forms'), 'description': _('Display available part quantity in some forms'),

View File

@ -630,16 +630,47 @@ class PartList(generics.ListCreateAPIView):
else: else:
return Response(data) return Response(data)
def perform_create(self, serializer): def create(self, request, *args, **kwargs):
""" """
We wish to save the user who created this part! We wish to save the user who created this part!
Note: Implementation copied from DRF class CreateModelMixin Note: Implementation copied from DRF class CreateModelMixin
""" """
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
part = serializer.save() part = serializer.save()
part.creation_user = self.request.user part.creation_user = self.request.user
part.save()
# Optionally copy templates from category or parent category
copy_templates = {
'main': str2bool(request.data.get('copy_category_templates', False)),
'parent': str2bool(request.data.get('copy_parent_templates', False))
}
part.save(**{'add_category_templates': copy_templates})
# Optionally create initial stock item
try:
initial_stock = Decimal(request.data.get('initial_stock', 0))
if initial_stock > 0 and part.default_location is not None:
stock_item = StockItem(
part=part,
quantity=initial_stock,
location=part.default_location,
)
stock_item.save(user=request.user)
except:
pass
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):

View File

@ -409,7 +409,7 @@ class Part(MPTTModel):
""" """
# Get category templates settings # Get category templates settings
add_category_templates = kwargs.pop('add_category_templates', None) add_category_templates = kwargs.pop('add_category_templates', False)
if self.pk: if self.pk:
previous = Part.objects.get(pk=self.pk) previous = Part.objects.get(pk=self.pk)
@ -437,36 +437,26 @@ class Part(MPTTModel):
# Get part category # Get part category
category = self.category category = self.category
if category and add_category_templates: if category is not None:
# Store templates added to part
template_list = [] template_list = []
# Create part parameters for selected category parent_categories = category.get_ancestors(include_self=True)
category_templates = add_category_templates['main']
if category_templates:
for template in category.get_parameter_templates():
parameter = PartParameter.create(part=self,
template=template.parameter_template,
data=template.default_value,
save=True)
if parameter:
template_list.append(template.parameter_template)
# Create part parameters for parent category
category_templates = add_category_templates['parent']
if category_templates:
# Get parent categories
parent_categories = category.get_ancestors()
for category in parent_categories: for category in parent_categories:
for template in category.get_parameter_templates(): for template in category.get_parameter_templates():
# Check that template wasn't already added # Check that template wasn't already added
if template.parameter_template not in template_list: if template.parameter_template not in template_list:
template_list.append(template.parameter_template)
try: try:
PartParameter.create(part=self, PartParameter.create(
part=self,
template=template.parameter_template, template=template.parameter_template,
data=template.default_value, data=template.default_value,
save=True) save=True
)
except IntegrityError: except IntegrityError:
# PartParameter already exists # PartParameter already exists
pass pass

View File

@ -264,25 +264,25 @@
{% if roles.part.add %} {% if roles.part.add %}
$("#part-create").click(function() { $("#part-create").click(function() {
launchModalForm(
"{% url 'part-create' %}", var fields = partFields({
{ create: true,
follow: true, });
data: {
{% if category %} {% if category %}
category: {{ category.id }} fields.category.value = {{ category.pk }};
{% endif %} {% endif %}
constructForm('{% url "api-part-list" %}', {
method: 'POST',
fields: fields,
title: '{% trans "Create Part" %}',
onSuccess: function(data) {
// Follow the new part
location.href = `/part/${data.pk}/`;
}, },
secondary: [ });
{
field: 'default_location',
label: '{% trans "New Location" %}',
title: '{% trans "Create new Stock Location" %}',
url: "{% url 'stock-location-create' %}",
}
]
}
);
}); });
{% endif %} {% endif %}

View File

@ -158,19 +158,6 @@ class PartDetailTest(PartViewTestCase):
class PartTests(PartViewTestCase): class PartTests(PartViewTestCase):
""" Tests for Part forms """ """ Tests for Part forms """
def test_part_create(self):
""" Launch form to create a new part """
response = self.client.get(reverse('part-create'), {'category': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# And again, with an invalid category
response = self.client.get(reverse('part-create'), {'category': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# And again, with no category
response = self.client.get(reverse('part-create'), {'name': 'Test part'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
def test_part_duplicate(self): def test_part_duplicate(self):
""" Launch form to duplicate part """ """ Launch form to duplicate part """

View File

@ -81,9 +81,6 @@ category_urls = [
# URL list for part web interface # URL list for part web interface
part_urls = [ part_urls = [
# Create a new part
url(r'^new/?', views.PartCreate.as_view(), name='part-create'),
# Upload a part # Upload a part
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'),

View File

@ -44,7 +44,7 @@ from common.files import FileManager
from common.views import FileManagementFormView, FileManagementAjaxView from common.views import FileManagementFormView, FileManagementAjaxView
from common.forms import UploadFileForm, MatchFieldForm from common.forms import UploadFileForm, MatchFieldForm
from stock.models import StockItem, StockLocation from stock.models import StockLocation
import common.settings as inventree_settings import common.settings as inventree_settings
@ -438,165 +438,6 @@ class PartDuplicate(AjaxCreateView):
return initials return initials
class PartCreate(AjaxCreateView):
""" View for creating a new Part object.
Options for providing initial conditions:
- Provide a category object as initial data
"""
model = Part
form_class = part_forms.EditPartForm
ajax_form_title = _('Create New Part')
ajax_template_name = 'part/create_part.html'
def get_data(self):
return {
'success': _("Created new part"),
}
def get_category_id(self):
return self.request.GET.get('category', None)
def get_context_data(self, **kwargs):
""" Provide extra context information for the form to display:
- Add category information (if provided)
"""
context = super(PartCreate, self).get_context_data(**kwargs)
# Add category information to the page
cat_id = self.get_category_id()
if cat_id:
try:
context['category'] = PartCategory.objects.get(pk=cat_id)
except (PartCategory.DoesNotExist, ValueError):
pass
return context
def get_form(self):
""" Create Form for making new Part object.
Remove the 'default_supplier' field as there are not yet any matching SupplierPart objects
"""
form = super(AjaxCreateView, self).get_form()
# Hide the "default expiry" field if the feature is not enabled
if not inventree_settings.stock_expiry_enabled():
form.fields['default_expiry'].widget = HiddenInput()
# Hide the "initial stock amount" field if the feature is not enabled
if not InvenTreeSetting.get_setting('PART_CREATE_INITIAL'):
form.fields['initial_stock'].widget = HiddenInput()
# Hide the default_supplier field (there are no matching supplier parts yet!)
form.fields['default_supplier'].widget = HiddenInput()
# Display category templates widgets
form.fields['selected_category_templates'].widget = CheckboxInput()
form.fields['parent_category_templates'].widget = CheckboxInput()
return form
def post(self, request, *args, **kwargs):
form = self.get_form()
context = {}
valid = form.is_valid()
name = request.POST.get('name', None)
if name:
matches = match_part_names(name)
if len(matches) > 0:
# Limit to the top 5 matches (to prevent clutter)
context['matches'] = matches[:5]
# 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:
msg = _('Possible matches exist - confirm creation of new part')
form.add_error('confirm_creation', msg)
form.pre_form_warning = msg
valid = False
data = {
'form_valid': valid
}
if valid:
# Create the new Part
part = form.save(commit=False)
# Record the user who created this part
part.creation_user = request.user
# Store category templates settings
add_category_templates = {
'main': form.cleaned_data['selected_category_templates'],
'parent': form.cleaned_data['parent_category_templates'],
}
# Save part and pass category template settings
part.save(**{'add_category_templates': add_category_templates})
# Add stock if set
init_stock = int(request.POST.get('initial_stock', 0))
if init_stock:
stock = StockItem(part=part,
quantity=init_stock,
location=part.default_location)
stock.save()
data['pk'] = part.pk
data['text'] = str(part)
try:
data['url'] = part.get_absolute_url()
except AttributeError:
pass
return self.renderJsonResponse(request, form, data, context=context)
def get_initial(self):
""" Get initial data for the new Part object:
- If a category is provided, pre-fill the Category field
"""
initials = super(PartCreate, self).get_initial()
if self.get_category_id():
try:
category = PartCategory.objects.get(pk=self.get_category_id())
initials['category'] = category
initials['keywords'] = category.default_keywords
except (PartCategory.DoesNotExist, ValueError):
pass
# Allow initial data to be passed through as arguments
for label in ['name', 'IPN', 'description', 'revision', 'keywords']:
if label in self.request.GET:
initials[label] = self.request.GET.get(label)
# Automatically create part parameters from category templates
initials['selected_category_templates'] = str2bool(InvenTreeSetting.get_setting('PART_CATEGORY_PARAMETERS', False))
initials['parent_category_templates'] = initials['selected_category_templates']
return initials
class PartImport(FileManagementFormView): class PartImport(FileManagementFormView):
''' Part: Upload file, match to fields and import parts(using multi-Step form) ''' ''' Part: Upload file, match to fields and import parts(using multi-Step form) '''
permission_required = 'part.add' permission_required = 'part.add'