Merge pull request #181 from SchrodingersGat/supplier-parts

Supplier parts
This commit is contained in:
Oliver 2019-04-28 11:28:32 +10:00 committed by GitHub
commit e5e6480099
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 219 additions and 127 deletions

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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'

View File

@ -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

View File

@ -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