mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Can now create, view list of parts and view detail page
This commit is contained in:
parent
e28dde7f7b
commit
afd2dacfc7
@ -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)),
|
||||||
|
|
||||||
|
30
InvenTree/company/migrations/0032_manufacturerpart.py
Normal file
30
InvenTree/company/migrations/0032_manufacturerpart.py
Normal 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')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -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')
|
||||||
)
|
)
|
||||||
|
@ -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 """
|
||||||
|
|
||||||
|
@ -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 = [
|
||||||
|
@ -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.
|
||||||
|
@ -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 %}
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user