diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 551f624cb9..26303a6549 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -69,6 +69,22 @@ class AjaxMixin(object): ajax_form_action = '' ajax_form_title = '' + def get_param(self, name, method='GET'): + """ Get a request query parameter value from URL e.g. ?part=3 + + Args: + name: Variable name e.g. 'part' + method: Request type ('GET' or 'POST') + + Returns: + Value of the supplier parameter or None if parameter is not available + """ + + if method == 'POST': + return self.request.POST.get(name, None) + else: + return self.request.GET.get(name, None) + def get_data(self): """ Get extra context data (default implementation is empty dict) @@ -134,79 +150,82 @@ class AjaxCreateView(AjaxMixin, CreateView): """ def get(self, request, *args, **kwargs): + """ Creates form with initial data, and renders JSON response """ - response = super(CreateView, self).get(request, *args, **kwargs) + super(CreateView, self).get(request, *args, **kwargs) - if request.is_ajax(): - # Initialize a a new form - form = self.form_class(initial=self.get_initial()) - - return self.renderJsonResponse(request, form) - - else: - return response + form = self.get_form() + return self.renderJsonResponse(request, form) def post(self, request, *args, **kwargs): - form = self.form_class(data=request.POST, files=request.FILES) + """ Responds to form POST. Validates POST data and returns status info. - if request.is_ajax(): + - Validate POST form data + - If valid, save form + - Return status info (success / failure) + """ + form = self.get_form() - data = { - 'form_valid': form.is_valid(), - } + # Extra JSON data sent alongside form + data = { + 'form_valid': form.is_valid(), + } - if form.is_valid(): - obj = form.save() + if form.is_valid(): + obj = form.save() - # Return the PK of the newly-created object - data['pk'] = obj.pk + # Return the PK of the newly-created object + data['pk'] = obj.pk - data['url'] = obj.get_absolute_url() + data['url'] = obj.get_absolute_url() - return self.renderJsonResponse(request, form, data) - - else: - return super(CreateView, self).post(request, *args, **kwargs) + return self.renderJsonResponse(request, form, data) class AjaxUpdateView(AjaxMixin, UpdateView): - """ An 'AJAXified' UpdateView for updating an object in the db - Returns form in JSON format (for delivery to a modal window) - Handles repeated form validation (via AJAX) until the form is valid """ def get(self, request, *args, **kwargs): + """ Respond to GET request. - html_response = super(UpdateView, self).get(request, *args, **kwargs) + - Populates form with object data + - Renders form to JSON and returns to client + """ - if request.is_ajax(): - form = self.form_class(instance=self.get_object()) + super(UpdateView, self).get(request, *args, **kwargs) - return self.renderJsonResponse(request, form) - - else: - return html_response + form = self.get_form() + + return self.renderJsonResponse(request, form) def post(self, request, *args, **kwargs): + """ Respond to POST request. - form = self.form_class(instance=self.get_object(), data=request.POST, files=request.FILES) + - Updates model with POST field data + - Performs form and object validation + - If errors exist, re-render the form + - Otherwise, return sucess status + """ - if request.is_ajax(): + super(UpdateView, self).post(request, *args, **kwargs) - data = {'form_valid': form.is_valid()} + form = self.get_form() - if form.is_valid(): - obj = form.save() + data = { + 'form_valid': form.is_valid() + } - data['pk'] = obj.id - data['url'] = obj.get_absolute_url() + if form.is_valid(): + obj = form.save() + + # Include context data about the updated object + data['pk'] = obj.id + data['url'] = obj.get_absolute_url() - response = self.renderJsonResponse(request, form, data) - return response - - else: - return super(UpdateView, self).post(request, *args, **kwargs) + return self.renderJsonResponse(request, form, data) class AjaxDeleteView(AjaxMixin, DeleteView): @@ -217,39 +236,43 @@ class AjaxDeleteView(AjaxMixin, DeleteView): """ def get(self, request, *args, **kwargs): + """ Respond to GET request - html_response = super(DeleteView, self).get(request, *args, **kwargs) + - Render a DELETE confirmation form to JSON + - Return rendered form to client + """ - if request.is_ajax(): + super(DeleteView, self).get(request, *args, **kwargs) - data = {'id': self.get_object().id, - 'delete': False, - 'title': self.ajax_form_title, - 'html_data': render_to_string(self.ajax_template_name, - self.get_context_data(), - request=request) - } + data = { + 'id': self.get_object().id, + 'delete': False, + 'title': self.ajax_form_title, + 'html_data': render_to_string( + self.ajax_template_name, + self.get_context_data(), + request=request) + } - return JsonResponse(data) - - else: - return html_response + return JsonResponse(data) def post(self, request, *args, **kwargs): + """ Respond to POST request - if request.is_ajax(): + - DELETE the object + - Render success message to JSON and return to client + """ - obj = self.get_object() - pk = obj.id - obj.delete() + obj = self.get_object() + pk = obj.id + obj.delete() - data = {'id': pk, - 'delete': True} + data = { + 'id': pk, + 'delete': True + } - return self.renderJsonResponse(request, data=data) - - else: - return super(DeleteView, self).post(request, *args, **kwargs) + return self.renderJsonResponse(request, data=data) class IndexView(TemplateView): diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index c8d40e80e6..dc7b3141c3 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -101,7 +101,10 @@ class BuildCreate(AjaxCreateView): part_id = self.request.GET.get('part', None) if part_id: - initials['part'] = get_object_or_404(Part, pk=part_id) + try: + initials['part'] = Part.objects.get(pk=part_id) + except Part.DoesNotExist: + pass return initials diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 8f93cede7c..46a15cfc62 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -11,7 +11,6 @@ from rest_framework import generics, permissions from django.db.models import Q from django.conf.urls import url, include -from django.shortcuts import get_object_or_404 from .models import Part, PartCategory, BomItem from .models import SupplierPart, SupplierPriceBreak @@ -99,20 +98,24 @@ class PartList(generics.ListCreateAPIView): parts_list = Part.objects.all() if cat_id: - category = get_object_or_404(PartCategory, pk=cat_id) + try: + category = PartCategory.objects.get(pk=cat_id) + + # Filter by the supplied category + flt = Q(category=cat_id) - # Filter by the supplied category - flt = Q(category=cat_id) + if self.request.query_params.get('include_child_categories', None): + childs = category.getUniqueChildren() + for child in childs: + # Ignore the top-level category (already filtered) + if str(child) == str(cat_id): + continue + flt |= Q(category=child) - if self.request.query_params.get('include_child_categories', None): - childs = category.getUniqueChildren() - for child in childs: - # Ignore the top-level category (already filtered) - if str(child) == str(cat_id): - continue - flt |= Q(category=child) + parts_list = parts_list.filter(flt) - parts_list = parts_list.filter(flt) + except PartCategory.DoesNotExist: + pass return parts_list diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 040444093d..bba8c3e973 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -92,6 +92,8 @@ class EditBomItemForm(HelperForm): 'quantity', 'note' ] + + # Prevent editing of the part associated with this BomItem widgets = {'part': forms.HiddenInput()} @@ -101,13 +103,13 @@ class EditSupplierPartForm(HelperForm): class Meta: model = SupplierPart fields = [ + 'part', 'supplier', 'SKU', - 'part', 'description', - 'URL', 'manufacturer', 'MPN', + 'URL', 'note', 'single_price', 'base_cost', diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index db9f66994c..d0f3a1ac01 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -84,7 +84,10 @@ class PartCreate(AjaxCreateView): cat_id = self.get_category_id() if cat_id: - context['category'] = get_object_or_404(PartCategory, pk=cat_id) + try: + context['category'] = PartCategory.objects.get(pk=cat_id) + except PartCategory.DoesNotExist: + pass return context @@ -111,7 +114,10 @@ class PartCreate(AjaxCreateView): initials = super(PartCreate, self).get_initial() if self.get_category_id(): - initials['category'] = get_object_or_404(PartCategory, pk=self.get_category_id()) + try: + initials['category'] = PartCategory.objects.get(pk=self.get_category_id()) + except PartCategory.DoesNotExist: + pass return initials @@ -158,7 +164,6 @@ class PartEdit(AjaxUpdateView): """ View for editing Part object """ model = Part - template_name = 'part/edit.html' form_class = EditPartForm ajax_template_name = 'modal_form.html' ajax_form_title = 'Edit Part Properties' @@ -246,7 +251,6 @@ class PartDelete(AjaxDeleteView): """ View to delete a Part object """ model = Part - template_name = 'part/delete.html' ajax_template_name = 'part/partial_delete.html' ajax_form_title = 'Confirm Part Deletion' context_object_name = 'part' @@ -270,7 +274,6 @@ class CategoryDetail(DetailView): class CategoryEdit(AjaxUpdateView): """ Update view to edit a PartCategory """ model = PartCategory - template_name = 'part/category_edit.html' form_class = EditCategoryForm ajax_template_name = 'modal_form.html' ajax_form_title = 'Edit Part Category' @@ -278,7 +281,10 @@ class CategoryEdit(AjaxUpdateView): def get_context_data(self, **kwargs): context = super(CategoryEdit, self).get_context_data(**kwargs).copy() - context['category'] = get_object_or_404(PartCategory, pk=self.kwargs['pk']) + try: + context['category'] = PartCategory.objects.get(pk=self.kwargs['pk']) + except: + pass return context @@ -286,7 +292,7 @@ class CategoryEdit(AjaxUpdateView): class CategoryDelete(AjaxDeleteView): """ Delete view to delete a PartCategory """ model = PartCategory - template_name = 'part/category_delete.html' + ajax_template_name = 'part/category_delete.html' context_object_name = 'category' success_url = '/part/' @@ -302,7 +308,6 @@ class CategoryCreate(AjaxCreateView): ajax_form_action = reverse_lazy('category-create') ajax_form_title = 'Create new part category' ajax_template_name = 'modal_form.html' - template_name = 'part/category_new.html' form_class = EditCategoryForm def get_context_data(self, **kwargs): @@ -315,7 +320,10 @@ class CategoryCreate(AjaxCreateView): parent_id = self.request.GET.get('category', None) if parent_id: - context['category'] = get_object_or_404(PartCategory, pk=parent_id) + try: + context['category'] = PartCategory.objects.get(pk=parent_id) + except PartCategory.DoesNotExist: + pass return context @@ -329,7 +337,10 @@ class CategoryCreate(AjaxCreateView): parent_id = self.request.GET.get('category', None) if parent_id: - initials['parent'] = get_object_or_404(PartCategory, pk=parent_id) + try: + initials['parent'] = PartCategory.objects.get(pk=parent_id) + except PartCategory.DoesNotExist: + pass return initials @@ -345,7 +356,6 @@ class BomItemCreate(AjaxCreateView): """ Create view for making a new BomItem object """ model = BomItem form_class = EditBomItemForm - template_name = 'part/bom-create.html' ajax_template_name = 'modal_form.html' ajax_form_title = 'Create BOM item' @@ -362,7 +372,10 @@ class BomItemCreate(AjaxCreateView): parent_id = self.request.GET.get('parent', None) if parent_id: - initials['part'] = get_object_or_404(Part, pk=parent_id) + try: + initials['part'] = Part.objects.get(pk=parent_id) + except Part.DoesNotExist: + pass return initials @@ -372,7 +385,6 @@ class BomItemEdit(AjaxUpdateView): model = BomItem form_class = EditBomItemForm - template_name = 'part/bom-edit.html' ajax_template_name = 'modal_form.html' ajax_form_title = 'Edit BOM item' @@ -380,7 +392,7 @@ class BomItemEdit(AjaxUpdateView): class BomItemDelete(AjaxDeleteView): """ Delete view for removing BomItem """ model = BomItem - template_name = 'part/bom-delete.html' + ajax_template_name = 'part/bom-delete.html' context_object_name = 'item' ajax_form_title = 'Confim BOM item deletion' @@ -397,7 +409,6 @@ class SupplierPartEdit(AjaxUpdateView): """ Update view for editing SupplierPart """ model = SupplierPart - template_name = 'company/partedit.html' context_object_name = 'part' form_class = EditSupplierPartForm ajax_template_name = 'modal_form.html' @@ -413,6 +424,19 @@ class SupplierPartCreate(AjaxCreateView): ajax_form_title = 'Create new Supplier Part' context_object_name = 'part' + def get_form(self): + form = super(AjaxCreateView, self).get_form() + + if form.initial.get('supplier', None): + # Hide the supplier field + form.fields['supplier'].widget.attrs['disabled'] = True + + if form.initial.get('part', None): + # Hide the part field + form.fields['part'].widget.attrs['disabled'] = True + + return form + def get_initial(self): """ Provide initial data for new SupplierPart: @@ -421,18 +445,21 @@ class SupplierPartCreate(AjaxCreateView): """ initials = super(SupplierPartCreate, self).get_initial().copy() - supplier_id = self.request.GET.get('supplier', None) - part_id = self.request.GET.get('part', None) + supplier_id = self.get_param('supplier') + part_id = self.get_param('part') if supplier_id: - initials['supplier'] = get_object_or_404(Company, pk=supplier_id) - # TODO - # self.fields['supplier'].disabled = True + try: + initials['supplier'] = Company.objects.get(pk=supplier_id) + except Company.DoesNotExist: + initials['supplier'] = None + if part_id: - initials['part'] = get_object_or_404(Part, pk=part_id) - # TODO - # self.fields['part'].disabled = True - + try: + initials['part'] = Part.objects.get(pk=part_id) + except Part.DoesNotExist: + initials['part'] = None + return initials @@ -440,4 +467,4 @@ class SupplierPartDelete(AjaxDeleteView): """ Delete view for removing a SupplierPart """ model = SupplierPart success_url = '/supplier/' - template_name = 'company/partdelete.html' + ajax_template_name = 'company/partdelete.html' diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index c352605ad2..8604f54c8f 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -7,7 +7,6 @@ from django_filters import NumberFilter from django.conf.urls import url, include from django.db.models import Q -from django.shortcuts import get_object_or_404 from .models import StockLocation, StockItem from .models import StockItemTracking @@ -238,20 +237,24 @@ class StockList(generics.ListCreateAPIView): stock_list = StockItem.objects.all() if loc_id: - location = get_object_or_404(StockLocation, pk=loc_id) + try: + location = StockLocation.objects.get(pk=loc_id) - # Filter by the supplied category - flt = Q(location=loc_id) + # Filter by the supplied category + flt = Q(location=loc_id) - if self.request.query_params.get('include_child_locations', None): - childs = location.getUniqueChildren() - for child in childs: - # Ignore the top-level category (already filtered!) - if str(child) == str(loc_id): - continue - flt |= Q(location=child) + if self.request.query_params.get('include_child_locations', None): + childs = location.getUniqueChildren() + for child in childs: + # Ignore the top-level category (already filtered!) + if str(child) == str(loc_id): + continue + flt |= Q(location=child) - stock_list = stock_list.filter(flt) + stock_list = stock_list.filter(flt) + + except StockLocation.DoesNotExist: + pass return stock_list diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 70d3f7ded3..a1f0d2335c 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -5,8 +5,6 @@ Django views for interacting with Stock app # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.shortcuts import get_object_or_404 - from django.views.generic import DetailView, ListView from django.forms.models import model_to_dict @@ -106,7 +104,10 @@ class StockLocationCreate(AjaxCreateView): loc_id = self.request.GET.get('location', None) if loc_id: - initials['parent'] = get_object_or_404(StockLocation, pk=loc_id) + try: + initials['parent'] = StockLocation.objects.get(pk=loc_id) + except StockLocation.DoesNotExist: + pass return initials @@ -125,7 +126,30 @@ class StockItemCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' ajax_form_title = 'Create new Stock Item' + def get_form(self): + """ Get form for StockItem creation. + Overrides the default get_form() method to intelligently limit + ForeignKey choices based on other selections + """ + + form = super(AjaxCreateView, self).get_form() + + # If the user has selected a Part, limit choices for SupplierPart + if form['part'].value() is not None: + part = form['part'].value() + parts = form.fields['supplier_part'].queryset + parts = parts.filter(part=part) + form.fields['supplier_part'].queryset = parts + + # Otherwise if the user has selected a SupplierPart, we know what Part they meant! + elif form['supplier_part'].value() is not None: + pass + + return form + def get_initial(self): + """ Provide initial data to create a new StockItem object + """ # Is the client attempting to copy an existing stock item? item_to_copy = self.request.GET.get('copy', None) @@ -144,15 +168,22 @@ class StockItemCreate(AjaxCreateView): part_id = self.request.GET.get('part', None) loc_id = self.request.GET.get('location', None) + # Part field has been specified if part_id: - part = get_object_or_404(Part, pk=part_id) - if part: - initials['part'] = get_object_or_404(Part, pk=part_id) + try: + part = Part.objects.get(pk=part_id) + initials['part'] = part initials['location'] = part.default_location initials['supplier_part'] = part.default_supplier + except Part.DoesNotExist: + pass + # Location has been specified if loc_id: - initials['location'] = get_object_or_404(StockLocation, pk=loc_id) + try: + initials['location'] = StockLocation.objects.get(pk=loc_id) + except StockLocation.DoesNotExist: + pass return initials @@ -178,7 +209,7 @@ class StockItemDelete(AjaxDeleteView): model = StockItem success_url = '/stock/' - template_name = 'stock/item_delete.html' + ajax_template_name = 'stock/item_delete.html' context_object_name = 'item' ajax_form_title = 'Delete Stock Item' @@ -190,7 +221,7 @@ class StockItemMove(AjaxUpdateView): """ model = StockItem - template_name = 'modal_form.html' + ajax_template_name = 'modal_form.html' context_object_name = 'item' ajax_form_title = 'Move Stock Item' form_class = MoveStockItemForm