diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 5d75a4dd74..839780d5b4 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -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'), diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 3b91d27c81..88866ad58c 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -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): diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 2dd5d3ad7f..b75edde9cc 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -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,39 +437,29 @@ 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: + parent_categories = category.get_ancestors(include_self=True) + + for category in parent_categories: for template in category.get_parameter_templates(): - parameter = PartParameter.create(part=self, - template=template.parameter_template, - data=template.default_value, - save=True) - if parameter: + # Check that template wasn't already added + if template.parameter_template not in template_list: + 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 template in category.get_parameter_templates(): - # Check that template wasn't already added - if template.parameter_template not in template_list: - try: - PartParameter.create(part=self, - template=template.parameter_template, - data=template.default_value, - save=True) - except IntegrityError: - # PartParameter already exists - pass + try: + PartParameter.create( + part=self, + template=template.parameter_template, + data=template.default_value, + save=True + ) + except IntegrityError: + # PartParameter already exists + pass def __str__(self): return f"{self.full_name} - {self.description}" diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 1c41092574..b149fd28ed 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -264,25 +264,25 @@ {% if roles.part.add %} $("#part-create").click(function() { - launchModalForm( - "{% url 'part-create' %}", - { - follow: true, - data: { - {% if category %} - category: {{ category.id }} - {% endif %} - }, - secondary: [ - { - field: 'default_location', - label: '{% trans "New Location" %}', - title: '{% trans "Create new Stock Location" %}', - url: "{% url 'stock-location-create' %}", - } - ] - } - ); + + var fields = partFields({ + create: true, + }); + + {% if category %} + 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}/`; + }, + }); + }); {% endif %} diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index 206d4dd56a..c555687183 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -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 """ diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 52e9b929c1..0802a94f1a 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -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'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index b35e752351..3e4b6c59d7 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -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'