mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Added ManufacturerPart model, form and views
This commit is contained in:
parent
c9464fd393
commit
e897864396
@ -17,6 +17,7 @@ from djmoney.forms.fields import MoneyField
|
|||||||
import common.settings
|
import common.settings
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
|
from .models import ManufacturerPart
|
||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
from .models import SupplierPriceBreak
|
from .models import SupplierPriceBreak
|
||||||
|
|
||||||
@ -84,12 +85,30 @@ class CompanyImageDownloadForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EditManufacturerPartForm(HelperForm):
|
||||||
|
""" Form for editing a ManufacturerPart object """
|
||||||
|
|
||||||
|
field_prefix = {
|
||||||
|
'link': 'fa-link',
|
||||||
|
'MPN': 'fa-hashtag',
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ManufacturerPart
|
||||||
|
fields = [
|
||||||
|
'part',
|
||||||
|
'description',
|
||||||
|
'manufacturer',
|
||||||
|
'MPN',
|
||||||
|
'link',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditSupplierPartForm(HelperForm):
|
class EditSupplierPartForm(HelperForm):
|
||||||
""" Form for editing a SupplierPart object """
|
""" Form for editing a SupplierPart object """
|
||||||
|
|
||||||
field_prefix = {
|
field_prefix = {
|
||||||
'link': 'fa-link',
|
'link': 'fa-link',
|
||||||
'MPN': 'fa-hashtag',
|
|
||||||
'SKU': 'fa-hashtag',
|
'SKU': 'fa-hashtag',
|
||||||
'note': 'fa-pencil-alt',
|
'note': 'fa-pencil-alt',
|
||||||
}
|
}
|
||||||
@ -110,8 +129,6 @@ class EditSupplierPartForm(HelperForm):
|
|||||||
'supplier',
|
'supplier',
|
||||||
'SKU',
|
'SKU',
|
||||||
'description',
|
'description',
|
||||||
'manufacturer',
|
|
||||||
'MPN',
|
|
||||||
'link',
|
'link',
|
||||||
'note',
|
'note',
|
||||||
'single_pricing',
|
'single_pricing',
|
||||||
|
@ -278,19 +278,75 @@ class Contact(models.Model):
|
|||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
class SupplierPart(models.Model):
|
class MaufacturerPart(models.Model):
|
||||||
""" Represents a unique part as provided by a Supplier
|
""" Represents a unique part as provided by a Manufacturer
|
||||||
Each SupplierPart is identified by a MPN (Manufacturer Part Number)
|
Each MaufacturerPart is identified by a MPN (Manufacturer Part Number)
|
||||||
Each SupplierPart is also linked to a Part object.
|
Each MaufacturerPart is also linked to a Part object.
|
||||||
A Part may be available from multiple suppliers
|
A Part may be available from multiple manufacturers
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
part: Link to the master Part
|
part: Link to the master Part
|
||||||
|
manufacturer: Company that manufactures the MaufacturerPart
|
||||||
|
MPN: Manufacture part number
|
||||||
|
link: Link to external website for this manufacturer part
|
||||||
|
description: Descriptive notes field
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('part', 'manufacturer', 'MPN')
|
||||||
|
|
||||||
|
part = models.ForeignKey('part.Part', on_delete=models.CASCADE,
|
||||||
|
related_name='manufacturer_parts',
|
||||||
|
verbose_name=_('Base Part'),
|
||||||
|
limit_choices_to={
|
||||||
|
'purchaseable': True,
|
||||||
|
},
|
||||||
|
help_text=_('Select part'),
|
||||||
|
)
|
||||||
|
|
||||||
|
manufacturer = models.ForeignKey(
|
||||||
|
Company,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='manufactured_parts',
|
||||||
|
limit_choices_to={
|
||||||
|
'is_manufacturer': True
|
||||||
|
},
|
||||||
|
verbose_name=_('Manufacturer'),
|
||||||
|
help_text=_('Select manufacturer'),
|
||||||
|
null=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
MPN = models.CharField(
|
||||||
|
max_length=100, blank=True, null=True,
|
||||||
|
verbose_name=_('MPN'),
|
||||||
|
help_text=_('Manufacturer Part Number')
|
||||||
|
)
|
||||||
|
|
||||||
|
link = InvenTreeURLField(
|
||||||
|
blank=True, null=True,
|
||||||
|
verbose_name=_('Link'),
|
||||||
|
help_text=_('URL for external manufacturer part link')
|
||||||
|
)
|
||||||
|
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=250, blank=True, null=True,
|
||||||
|
verbose_name=_('Description'),
|
||||||
|
help_text=_('Manufacturer part description')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SupplierPart(models.Model):
|
||||||
|
""" Represents a unique part as provided by a Supplier
|
||||||
|
Each SupplierPart is identified by a SKU (Supplier Part Number)
|
||||||
|
Each SupplierPart is also linked to a Part or ManufacturerPart object.
|
||||||
|
A Part may be available from multiple suppliers
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
part_type: Part or ManufacturerPart
|
||||||
|
part_id: Part or ManufacturerPart ID
|
||||||
supplier: Company that supplies this SupplierPart object
|
supplier: Company that supplies this SupplierPart object
|
||||||
SKU: Stock keeping unit (supplier part number)
|
SKU: Stock keeping unit (supplier part number)
|
||||||
manufacturer: Company that manufactures the SupplierPart (leave blank if it is the sample as the Supplier!)
|
link: Link to external website for this supplier part
|
||||||
MPN: Manufacture part number
|
|
||||||
link: Link to external website for this part
|
|
||||||
description: Descriptive notes field
|
description: Descriptive notes field
|
||||||
note: Longer form note field
|
note: Longer form note field
|
||||||
base_cost: Base charge added to order independent of quantity e.g. "Reeling Fee"
|
base_cost: Base charge added to order independent of quantity e.g. "Reeling Fee"
|
||||||
@ -330,24 +386,6 @@ class SupplierPart(models.Model):
|
|||||||
help_text=_('Supplier stock keeping unit')
|
help_text=_('Supplier stock keeping unit')
|
||||||
)
|
)
|
||||||
|
|
||||||
manufacturer = models.ForeignKey(
|
|
||||||
Company,
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name='manufactured_parts',
|
|
||||||
limit_choices_to={
|
|
||||||
'is_manufacturer': True
|
|
||||||
},
|
|
||||||
verbose_name=_('Manufacturer'),
|
|
||||||
help_text=_('Select manufacturer'),
|
|
||||||
null=True, blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
MPN = models.CharField(
|
|
||||||
max_length=100, blank=True, null=True,
|
|
||||||
verbose_name=_('MPN'),
|
|
||||||
help_text=_('Manufacturer part number')
|
|
||||||
)
|
|
||||||
|
|
||||||
link = InvenTreeURLField(
|
link = InvenTreeURLField(
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
verbose_name=_('Link'),
|
verbose_name=_('Link'),
|
||||||
|
@ -24,6 +24,7 @@ from InvenTree.helpers import str2bool
|
|||||||
from InvenTree.views import InvenTreeRoleMixin
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
|
from .models import ManufacturerPart
|
||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
from .models import SupplierPriceBreak
|
from .models import SupplierPriceBreak
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ from part.models import Part
|
|||||||
|
|
||||||
from .forms import EditCompanyForm
|
from .forms import EditCompanyForm
|
||||||
from .forms import CompanyImageForm
|
from .forms import CompanyImageForm
|
||||||
|
from .forms import EditManufacturerPartForm
|
||||||
from .forms import EditSupplierPartForm
|
from .forms import EditSupplierPartForm
|
||||||
from .forms import EditPriceBreakForm
|
from .forms import EditPriceBreakForm
|
||||||
from .forms import CompanyImageDownloadForm
|
from .forms import CompanyImageDownloadForm
|
||||||
@ -331,6 +333,175 @@ class CompanyDelete(AjaxDeleteView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartDetail(DetailView):
|
||||||
|
""" Detail view for ManufacturerPart """
|
||||||
|
model = ManufacturerPart
|
||||||
|
template_name = 'company/manufacturer_part_detail.html'
|
||||||
|
context_object_name = 'part'
|
||||||
|
queryset = ManufacturerPart.objects.all()
|
||||||
|
permission_required = 'purchase_order.view'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartEdit(AjaxUpdateView):
|
||||||
|
""" Update view for editing ManufacturerPart """
|
||||||
|
|
||||||
|
model = ManufacturerPart
|
||||||
|
context_object_name = 'part'
|
||||||
|
form_class = EditSupplierPartForm
|
||||||
|
ajax_template_name = 'modal_form.html'
|
||||||
|
ajax_form_title = _('Edit Manufacturer Part')
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartCreate(AjaxCreateView):
|
||||||
|
""" Create view for making new ManufacturerPart """
|
||||||
|
|
||||||
|
model = ManufacturerPart
|
||||||
|
form_class = EditManufacturerPartForm
|
||||||
|
ajax_template_name = 'company/manufacturer_part_create.html'
|
||||||
|
ajax_form_title = _('Create New Manufacturer Part')
|
||||||
|
context_object_name = 'part'
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
"""
|
||||||
|
Supply context data to the form
|
||||||
|
"""
|
||||||
|
|
||||||
|
ctx = super().get_context_data()
|
||||||
|
|
||||||
|
# Add 'part' object
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
part = form['part'].value()
|
||||||
|
|
||||||
|
try:
|
||||||
|
part = Part.objects.get(pk=part)
|
||||||
|
except (ValueError, Part.DoesNotExist):
|
||||||
|
part = None
|
||||||
|
|
||||||
|
ctx['part'] = part
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
""" Create Form instance to create a new ManufacturerPart object.
|
||||||
|
Hide some fields if they are not appropriate in context
|
||||||
|
"""
|
||||||
|
form = super(AjaxCreateView, self).get_form()
|
||||||
|
|
||||||
|
if form.initial.get('part', None):
|
||||||
|
# Hide the part field
|
||||||
|
form.fields['part'].widget = HiddenInput()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
""" Provide initial data for new ManufacturerPart:
|
||||||
|
|
||||||
|
- If 'manufacturer_id' provided, pre-fill manufacturer field
|
||||||
|
- If 'part_id' provided, pre-fill part field
|
||||||
|
"""
|
||||||
|
initials = super(SupplierPartCreate, self).get_initial().copy()
|
||||||
|
|
||||||
|
manufacturer_id = self.get_param('manufacturer')
|
||||||
|
part_id = self.get_param('part')
|
||||||
|
|
||||||
|
if manufacturer_id:
|
||||||
|
try:
|
||||||
|
initials['manufacturer'] = Company.objects.get(pk=manufacturer_id)
|
||||||
|
except (ValueError, Company.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if part_id:
|
||||||
|
try:
|
||||||
|
initials['part'] = Part.objects.get(pk=part_id)
|
||||||
|
except (ValueError, Part.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerPartDelete(AjaxDeleteView):
|
||||||
|
""" Delete view for removing a ManufacturerPart.
|
||||||
|
|
||||||
|
ManufacturerParts can be deleted using a variety of 'selectors'.
|
||||||
|
|
||||||
|
- ?part=<pk> -> Delete a single ManufacturerPart object
|
||||||
|
- ?parts=[] -> Delete a list of ManufacturerPart objects
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
success_url = '/manufacturer/'
|
||||||
|
ajax_template_name = 'company/partdelete.html'
|
||||||
|
ajax_form_title = _('Delete Manufacturer Part')
|
||||||
|
|
||||||
|
role_required = 'purchase_order.delete'
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
ctx = {}
|
||||||
|
|
||||||
|
ctx['parts'] = self.parts
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
def get_parts(self):
|
||||||
|
""" Determine which ManufacturerPart object(s) the user wishes to delete.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.parts = []
|
||||||
|
|
||||||
|
# User passes a single ManufacturerPart ID
|
||||||
|
if 'part' in self.request.GET:
|
||||||
|
try:
|
||||||
|
self.parts.append(ManufacturerPart.objects.get(pk=self.request.GET.get('part')))
|
||||||
|
except (ValueError, SupplierPart.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif 'parts[]' in self.request.GET:
|
||||||
|
|
||||||
|
part_id_list = self.request.GET.getlist('parts[]')
|
||||||
|
|
||||||
|
self.parts = ManufacturerPart.objects.filter(id__in=part_id_list)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.request = request
|
||||||
|
self.get_parts()
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, form=self.get_form())
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
""" Handle the POST action for deleting ManufacturerPart object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
self.parts = []
|
||||||
|
|
||||||
|
for item in self.request.POST:
|
||||||
|
if item.startswith('manufacturer-part-'):
|
||||||
|
pk = item.replace('manufacturer-part-', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.parts.append(ManufacturerPart.objects.get(pk=pk))
|
||||||
|
except (ValueError, ManufacturerPart.DoesNotExist):
|
||||||
|
pass
|
||||||
|
|
||||||
|
confirm = str2bool(self.request.POST.get('confirm_delete', False))
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'form_valid': confirm,
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirm:
|
||||||
|
for part in self.parts:
|
||||||
|
part.delete()
|
||||||
|
|
||||||
|
return self.renderJsonResponse(self.request, data=data, form=self.get_form())
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartDetail(DetailView):
|
class SupplierPartDetail(DetailView):
|
||||||
""" Detail view for SupplierPart """
|
""" Detail view for SupplierPart """
|
||||||
model = SupplierPart
|
model = SupplierPart
|
||||||
|
Loading…
Reference in New Issue
Block a user