mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Can now create, view list of parts and view detail page
This commit is contained in:
		@@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -80,8 +82,107 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
 | 
			
		||||
        queryset = CompanySerializer.annotate_queryset(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):
 | 
			
		||||
    """ 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,7 +346,8 @@ supplier_part_api_urls = [
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
company_api_urls = [
 | 
			
		||||
    
 | 
			
		||||
    url(r'^part/manufacturer/', include(manufacturer_part_api_urls)),
 | 
			
		||||
 | 
			
		||||
    url(r'^part/', include(supplier_part_api_urls)),
 | 
			
		||||
 | 
			
		||||
    url(r'^price-break/', SupplierPriceBreakList.as_view(), name='api-part-supplier-price'),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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(
 | 
			
		||||
        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')
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -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 """
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = [
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user