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

View File

@ -630,16 +630,47 @@ class PartList(generics.ListCreateAPIView):
else:
return Response(data)
def perform_create(self, serializer):
def create(self, request, *args, **kwargs):
"""
We wish to save the user who created this part!
Note: Implementation copied from DRF class CreateModelMixin
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
part = serializer.save()
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):

View File

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

View File

@ -264,25 +264,25 @@
{% if roles.part.add %}
$("#part-create").click(function() {
launchModalForm(
"{% url 'part-create' %}",
{
follow: true,
data: {
var fields = partFields({
create: true,
});
{% if category %}
category: {{ category.id }}
fields.category.value = {{ category.pk }};
{% 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 %}

View File

@ -158,19 +158,6 @@ class PartDetailTest(PartViewTestCase):
class PartTests(PartViewTestCase):
""" 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):
""" Launch form to duplicate part """

View File

@ -81,9 +81,6 @@ category_urls = [
# URL list for part web interface
part_urls = [
# Create a new part
url(r'^new/?', views.PartCreate.as_view(), name='part-create'),
# Upload a part
url(r'^import/', views.PartImport.as_view(), name='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.forms import UploadFileForm, MatchFieldForm
from stock.models import StockItem, StockLocation
from stock.models import StockLocation
import common.settings as inventree_settings
@ -438,165 +438,6 @@ class PartDuplicate(AjaxCreateView):
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):
''' Part: Upload file, match to fields and import parts(using multi-Step form) '''
permission_required = 'part.add'