mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
CreatePart form now uses the API
- Simplify the way category parameter templates are copied
This commit is contained in:
parent
2bf3e3ab02
commit
b04f22fc53
@ -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'),
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 %}
|
||||||
|
|
||||||
|
@ -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 """
|
||||||
|
|
||||||
|
@ -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'),
|
||||||
|
@ -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'
|
||||||
|
Loading…
Reference in New Issue
Block a user