Can now create, view list of parts and view detail page

This commit is contained in:
eeintech 2021-03-24 11:44:51 -04:00
parent e28dde7f7b
commit afd2dacfc7
8 changed files with 296 additions and 12 deletions

View File

@ -15,9 +15,11 @@ from django.db.models import Q
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from .models import Company from .models import Company
from .models import ManufacturerPart
from .models import SupplierPart, SupplierPriceBreak from .models import SupplierPart, SupplierPriceBreak
from .serializers import CompanySerializer from .serializers import CompanySerializer
from .serializers import ManufacturerPartSerializer
from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer
@ -82,6 +84,105 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
return queryset return queryset
class ManufacturerPartList(generics.ListCreateAPIView):
""" API endpoint for list view of ManufacturerPart object
- GET: Return list of ManufacturerPart objects
- POST: Create a new ManufacturerPart object
"""
queryset = ManufacturerPart.objects.all().prefetch_related(
'part',
'manufacturer',
)
serializer_class = ManufacturerPartSerializer
def get_serializer(self, *args, **kwargs):
# Do we wish to include extra detail?
try:
kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', None))
except AttributeError:
pass
try:
kwargs['manufacturer_detail'] = str2bool(self.request.query_params.get('manufacturer_detail', None))
except AttributeError:
pass
try:
kwargs['pretty'] = str2bool(self.request.query_params.get('pretty', None))
except AttributeError:
pass
kwargs['context'] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
def filter_queryset(self, queryset):
"""
Custom filtering for the queryset.
"""
queryset = super().filter_queryset(queryset)
params = self.request.query_params
# Filter by manufacturer
manufacturer = params.get('manufacturer', None)
if manufacturer is not None:
queryset = queryset.filter(manufacturer=manufacturer)
# Filter by parent part?
part = params.get('part', None)
if part is not None:
queryset = queryset.filter(part=part)
# Filter by 'active' status of the part?
active = params.get('active', None)
if active is not None:
active = str2bool(active)
queryset = queryset.filter(part__active=active)
return queryset
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter,
]
filter_fields = [
]
search_fields = [
'manufacturer__name',
'description',
'MPN',
'part__name',
'part__description',
]
class ManufacturerPartDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail view of ManufacturerPart object
- GET: Retrieve detail view
- PATCH: Update object
- DELETE: Delete object
"""
queryset = ManufacturerPart.objects.all()
serializer_class = ManufacturerPartSerializer
read_only_fields = [
]
class SupplierPartList(generics.ListCreateAPIView): class SupplierPartList(generics.ListCreateAPIView):
""" API endpoint for list view of SupplierPart object """ API endpoint for list view of SupplierPart object
@ -226,6 +327,15 @@ class SupplierPriceBreakList(generics.ListCreateAPIView):
] ]
manufacturer_part_api_urls = [
url(r'^(?P<pk>\d+)/?', ManufacturerPartDetail.as_view(), name='api-manufacturer-part-detail'),
# Catch anything else
url(r'^.*$', ManufacturerPartList.as_view(), name='api-manufacturer-part-list'),
]
supplier_part_api_urls = [ supplier_part_api_urls = [
url(r'^(?P<pk>\d+)/?', SupplierPartDetail.as_view(), name='api-supplier-part-detail'), url(r'^(?P<pk>\d+)/?', SupplierPartDetail.as_view(), name='api-supplier-part-detail'),
@ -236,6 +346,7 @@ supplier_part_api_urls = [
company_api_urls = [ company_api_urls = [
url(r'^part/manufacturer/', include(manufacturer_part_api_urls)),
url(r'^part/', include(supplier_part_api_urls)), url(r'^part/', include(supplier_part_api_urls)),

View File

@ -0,0 +1,30 @@
# Generated by Django 3.0.7 on 2021-03-24 14:18
import InvenTree.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('part', '0063_bomitem_inherited'),
('company', '0031_auto_20210103_2215'),
]
operations = [
migrations.CreateModel(
name='ManufacturerPart',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('MPN', models.CharField(help_text='Manufacturer Part Number', max_length=100, verbose_name='MPN')),
('link', InvenTree.fields.InvenTreeURLField(blank=True, help_text='URL for external manufacturer part link', null=True, verbose_name='Link')),
('description', models.CharField(blank=True, help_text='Manufacturer part description', max_length=250, null=True, verbose_name='Description')),
('manufacturer', models.ForeignKey(help_text='Select manufacturer', limit_choices_to={'is_manufacturer': True}, on_delete=django.db.models.deletion.CASCADE, related_name='manufacturer_parts', to='company.Company', verbose_name='Manufacturer')),
('part', models.ForeignKey(help_text='Select part', limit_choices_to={'purchaseable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='manufacturer_parts', to='part.Part', verbose_name='Base Part')),
],
options={
'unique_together': {('part', 'manufacturer', 'MPN')},
},
),
]

View File

@ -306,18 +306,17 @@ class ManufacturerPart(models.Model):
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
Company, Company,
on_delete=models.SET_NULL, on_delete=models.CASCADE,
related_name='manufacturer_parts', related_name='manufacturer_parts',
limit_choices_to={ limit_choices_to={
'is_manufacturer': True 'is_manufacturer': True
}, },
verbose_name=_('Manufacturer'), verbose_name=_('Manufacturer'),
help_text=_('Select manufacturer'), help_text=_('Select manufacturer'),
null=True, blank=True
) )
MPN = models.CharField( MPN = models.CharField(
max_length=100, blank=True, null=True, max_length=100,
verbose_name=_('MPN'), verbose_name=_('MPN'),
help_text=_('Manufacturer Part Number') help_text=_('Manufacturer Part Number')
) )

View File

@ -7,6 +7,7 @@ from rest_framework import serializers
from sql_util.utils import SubqueryCount from sql_util.utils import SubqueryCount
from .models import Company from .models import Company
from .models import ManufacturerPart
from .models import SupplierPart, SupplierPriceBreak from .models import SupplierPart, SupplierPriceBreak
from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeModelSerializer
@ -80,6 +81,49 @@ class CompanySerializer(InvenTreeModelSerializer):
] ]
class ManufacturerPartSerializer(InvenTreeModelSerializer):
""" Serializer for SupplierPart object """
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True)
pretty_name = serializers.CharField(read_only=True)
def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False)
manufacturer_detail = kwargs.pop('manufacturer_detail', False)
prettify = kwargs.pop('pretty', False)
super(ManufacturerPartSerializer, self).__init__(*args, **kwargs)
if part_detail is not True:
self.fields.pop('part_detail')
if manufacturer_detail is not True:
self.fields.pop('manufacturer_detail')
if prettify is not True:
self.fields.pop('pretty_name')
manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True))
class Meta:
model = SupplierPart
fields = [
'pk',
'part',
'part_detail',
'pretty_name',
'manufacturer',
'manufacturer_detail',
'description',
'MPN',
'link',
]
class SupplierPartSerializer(InvenTreeModelSerializer): class SupplierPartSerializer(InvenTreeModelSerializer):
""" Serializer for SupplierPart object """ """ Serializer for SupplierPart object """

View File

@ -53,7 +53,9 @@ price_break_urls = [
] ]
manufacturer_part_detail_urls = [ manufacturer_part_detail_urls = [
url(r'^edit/?', views.ManufacturerPartEdit.as_view(), name='supplier-part-edit'), url(r'^edit/?', views.ManufacturerPartEdit.as_view(), name='manufacturer-part-edit'),
url('^.*$', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_detail.html'), name='manufacturer-part-detail'),
] ]
manufacturer_part_urls = [ manufacturer_part_urls = [

View File

@ -405,7 +405,7 @@ class ManufacturerPartCreate(AjaxCreateView):
- If 'manufacturer_id' provided, pre-fill manufacturer field - If 'manufacturer_id' provided, pre-fill manufacturer field
- If 'part_id' provided, pre-fill part field - If 'part_id' provided, pre-fill part field
""" """
initials = super(SupplierPartCreate, self).get_initial().copy() initials = super(ManufacturerPartCreate, self).get_initial().copy()
manufacturer_id = self.get_param('manufacturer') manufacturer_id = self.get_param('manufacturer')
part_id = self.get_param('part') part_id = self.get_param('part')
@ -422,6 +422,8 @@ class ManufacturerPartCreate(AjaxCreateView):
except (ValueError, Part.DoesNotExist): except (ValueError, Part.DoesNotExist):
pass pass
return initials
class ManufacturerPartDelete(AjaxDeleteView): class ManufacturerPartDelete(AjaxDeleteView):
""" Delete view for removing a ManufacturerPart. """ Delete view for removing a ManufacturerPart.

View File

@ -14,7 +14,7 @@
<div id='button-toolbar'> <div id='button-toolbar'>
<div class='btn-group'> <div class='btn-group'>
<button class="btn btn-success" id='manufacturer-create'> <button class="btn btn-success" id='manufacturer-create'>
<span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %} <span class='fas fa-plus-circle'></span> {% trans "New Manufacturer Part" %}
</button> </button>
<div id='opt-dropdown' class="btn-group"> <div id='opt-dropdown' class="btn-group">
<button id='manufacturer-part-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button> <button id='manufacturer-part-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
@ -73,19 +73,18 @@
}); });
}); });
loadSupplierPartTable( loadManufacturerPartTable(
"#supplier-table", "#manufacturer-table",
"{% url 'api-supplier-part-list' %}", "{% url 'api-manufacturer-part-list' %}",
{ {
params: { params: {
part: {{ part.id }}, part: {{ part.id }},
part_detail: true, part_detail: true,
supplier_detail: true,
manufacturer_detail: true, manufacturer_detail: true,
}, },
} }
); );
linkButtonsToSelection($("#supplier-table"), ['#supplier-part-options']) linkButtonsToSelection($("#manufacturer-table"), ['#manufacturer-part-options'])
{% endblock %} {% endblock %}

View File

@ -101,6 +101,103 @@ function loadCompanyTable(table, url, options={}) {
} }
function loadManufacturerPartTable(table, url, options) {
/*
* Load manufacturer part table
*
*/
// Query parameters
var params = options.params || {};
// Load filters
var filters = loadTableFilters("manufacturer-part");
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("manufacturer-part", $(table));
$(table).inventreeTable({
url: url,
method: 'get',
original: params,
queryParams: filters,
name: 'manufacturerparts',
groupBy: false,
formatNoMatches: function() { return "{% trans "No manufacturer parts found" %}"; },
columns: [
{
checkbox: true,
switchable: false,
},
{
sortable: true,
field: 'part_detail.full_name',
title: '{% trans "Part" %}',
switchable: false,
formatter: function(value, row, index, field) {
var url = `/part/${row.part}/`;
var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value, url);
if (row.part_detail.is_template) {
html += `<span class='fas fa-clone label-right' title='{% trans "Template part" %}'></span>`;
}
if (row.part_detail.assembly) {
html += `<span class='fas fa-tools label-right' title='{% trans "Assembled part" %}'></span>`;
}
if (!row.part_detail.active) {
html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
}
return html;
}
},
{
sortable: true,
field: 'manufacturer',
title: '{% trans "Manufacturer" %}',
formatter: function(value, row, index, field) {
if (value && row.manufacturer_detail) {
var name = row.manufacturer_detail.name;
var url = `/company/${value}/`;
var html = imageHoverIcon(row.manufacturer_detail.image) + renderLink(name, url);
return html;
} else {
return "-";
}
}
},
{
sortable: true,
field: 'MPN',
title: '{% trans "MPN" %}',
formatter: function(value, row, index, field) {
return renderLink(value, `/manufacturer-part/${row.pk}/`);
}
},
{
field: 'link',
title: '{% trans "Link" %}',
formatter: function(value, row, index, field) {
if (value) {
return renderLink(value, value);
} else {
return '';
}
}
},
],
});
}
function loadSupplierPartTable(table, url, options) { function loadSupplierPartTable(table, url, options) {
/* /*
* Load supplier part table * Load supplier part table