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 .models import Company
from .models import ManufacturerPart
from .models import SupplierPart, SupplierPriceBreak
from .serializers import CompanySerializer
from .serializers import ManufacturerPartSerializer
from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer
@ -82,6 +84,105 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
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):
""" 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 = [
url(r'^(?P<pk>\d+)/?', SupplierPartDetail.as_view(), name='api-supplier-part-detail'),
@ -236,6 +346,7 @@ supplier_part_api_urls = [
company_api_urls = [
url(r'^part/manufacturer/', include(manufacturer_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(
Company,
on_delete=models.SET_NULL,
on_delete=models.CASCADE,
related_name='manufacturer_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,
max_length=100,
verbose_name=_('MPN'),
help_text=_('Manufacturer Part Number')
)

View File

@ -7,6 +7,7 @@ from rest_framework import serializers
from sql_util.utils import SubqueryCount
from .models import Company
from .models import ManufacturerPart
from .models import SupplierPart, SupplierPriceBreak
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):
""" Serializer for SupplierPart object """

View File

@ -53,7 +53,9 @@ price_break_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 = [

View File

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

View File

@ -14,7 +14,7 @@
<div id='button-toolbar'>
<div class='btn-group'>
<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>
<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>
@ -73,19 +73,18 @@
});
});
loadSupplierPartTable(
"#supplier-table",
"{% url 'api-supplier-part-list' %}",
loadManufacturerPartTable(
"#manufacturer-table",
"{% url 'api-manufacturer-part-list' %}",
{
params: {
part: {{ part.id }},
part_detail: true,
supplier_detail: true,
manufacturer_detail: true,
},
}
);
linkButtonsToSelection($("#supplier-table"), ['#supplier-part-options'])
linkButtonsToSelection($("#manufacturer-table"), ['#manufacturer-part-options'])
{% 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) {
/*
* Load supplier part table