mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Added related parts urls, views, form and templates
Adding related part relationships work but are still not shown in the part detail page
This commit is contained in:
parent
3d9223c2ee
commit
8579abb9c2
@ -13,7 +13,7 @@ from mptt.fields import TreeNodeChoiceField
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from .models import Part, PartCategory, PartAttachment
|
from .models import Part, PartCategory, PartAttachment, PartRelated
|
||||||
from .models import BomItem
|
from .models import BomItem
|
||||||
from .models import PartParameterTemplate, PartParameter
|
from .models import PartParameterTemplate, PartParameter
|
||||||
from .models import PartTestTemplate
|
from .models import PartTestTemplate
|
||||||
@ -104,6 +104,25 @@ class BomUploadSelectFile(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePartRelatedForm(HelperForm):
|
||||||
|
""" Form for creating a PartRelated object """
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PartRelated
|
||||||
|
fields = [
|
||||||
|
'part_1',
|
||||||
|
'part_2',
|
||||||
|
]
|
||||||
|
labels = {
|
||||||
|
'part_2': _('Related Part'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
""" Disable model saving """
|
||||||
|
|
||||||
|
return super(CreatePartRelatedForm, self).save(commit=False)
|
||||||
|
|
||||||
|
|
||||||
class EditPartAttachmentForm(HelperForm):
|
class EditPartAttachmentForm(HelperForm):
|
||||||
""" Form for editing a PartAttachment object """
|
""" Form for editing a PartAttachment object """
|
||||||
|
|
||||||
|
@ -1264,11 +1264,11 @@ class Part(MPTTModel):
|
|||||||
parts_2 = self.related_parts_2.filter(part_2__id=self.pk)
|
parts_2 = self.related_parts_2.filter(part_2__id=self.pk)
|
||||||
|
|
||||||
for part in parts_1:
|
for part in parts_1:
|
||||||
# Append
|
# Append
|
||||||
related_parts.append(part.part_2)
|
related_parts.append(part.part_2)
|
||||||
|
|
||||||
for part in parts_2:
|
for part in parts_2:
|
||||||
# Append
|
# Append
|
||||||
related_parts.append(part.part_1)
|
related_parts.append(part.part_1)
|
||||||
|
|
||||||
return related_parts
|
return related_parts
|
||||||
@ -1749,28 +1749,41 @@ class PartRelated(models.Model):
|
|||||||
|
|
||||||
part_1 = models.ForeignKey(Part, related_name='related_parts_1', on_delete=models.DO_NOTHING)
|
part_1 = models.ForeignKey(Part, related_name='related_parts_1', on_delete=models.DO_NOTHING)
|
||||||
|
|
||||||
part_2 = models.ForeignKey(Part, related_name='related_parts_2', on_delete=models.DO_NOTHING)
|
part_2 = models.ForeignKey(Part, related_name='related_parts_2', on_delete=models.DO_NOTHING,
|
||||||
|
help_text=_('Choose Related Part'))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.part_1} <-> {self.part_2}'
|
||||||
|
|
||||||
def create_relationship(self, part_1, part_2):
|
def create_relationship(self, part_1, part_2):
|
||||||
|
''' Create relationship between two parts '''
|
||||||
|
|
||||||
|
validate = True
|
||||||
|
|
||||||
parts = Part.objects.all()
|
parts = Part.objects.all()
|
||||||
|
related_parts = PartRelated.objects.all()
|
||||||
|
|
||||||
# Check if part exist
|
# Check if part exist and there are not the same part
|
||||||
if (part_1 in parts and part_2 in parts) and (part_1 is not part_2):
|
if (part_1 in parts and part_2 in parts) and (part_1 is not part_2):
|
||||||
|
# Check if relation exists already
|
||||||
|
for relation in related_parts:
|
||||||
|
if (part_1 == relation.part_1 and part_2 == relation.part_2) \
|
||||||
|
or (part_1 == relation.part_2 and part_2 == relation.part_1):
|
||||||
|
validate = False
|
||||||
|
else:
|
||||||
|
validate = False
|
||||||
|
|
||||||
|
if validate:
|
||||||
# Add relationship
|
# Add relationship
|
||||||
self.part_1 = part_1
|
self.part_1 = part_1
|
||||||
self.part_2 = part_2
|
self.part_2 = part_2
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
return True
|
return validate
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, part_1, part_2):
|
def create(cls, part_1, part_2):
|
||||||
|
''' Create PartRelated object '''
|
||||||
related_part = cls()
|
related_part = cls()
|
||||||
related_part.create_relationship(part_1, part_2)
|
related_part.create_relationship(part_1, part_2)
|
||||||
return related_part
|
return related_part
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{part_1} <-> {part_2}'
|
|
||||||
|
46
InvenTree/part/templates/part/related.html
Normal file
46
InvenTree/part/templates/part/related.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends "part/part_base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block details %}
|
||||||
|
|
||||||
|
{% include 'part/tabs.html' with tab='related-parts' %}
|
||||||
|
|
||||||
|
<h4>{% trans "Related Parts" %}</h4>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div id='button-bar'>
|
||||||
|
<div class='button-toolbar container-fluid' style='float: left;'>
|
||||||
|
<button class='btn btn-primary' type='button' id='add-related-part' title='{% trans "Add Related" %}'>{% trans "Add Related" %}</button>
|
||||||
|
<div class='filter-list' id='filter-list-related'>
|
||||||
|
<!-- An empty div in which the filter list will be constructed -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed related-table' id='table-related-part' data-toolbar='#button-bar'>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_ready %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
loadPartRelatedTable($("#table-related-part"), {
|
||||||
|
url: "{% url 'api-part-list' %}",
|
||||||
|
params: {
|
||||||
|
part: {{ part.id }},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#add-related-part").click(function() {
|
||||||
|
launchModalForm("{% url 'part-related-create' %}", {
|
||||||
|
data: {
|
||||||
|
part: {{ part.id }},
|
||||||
|
},
|
||||||
|
reload: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -63,6 +63,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li{% ifequal tab 'related-parts' %} class="active"{% endifequal %}>
|
||||||
|
<a href="{% url 'part-related' part.id %}">{% trans "Related Parts" %} {% if part.related_count > 0 %}<span class="badge">{{ part.related_count }}</span>{% endif %}</a>
|
||||||
|
</li>
|
||||||
<li{% ifequal tab 'attachments' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'attachments' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-attachments' part.id %}">{% trans "Attachments" %} {% if part.attachment_count > 0 %}<span class="badge">{{ part.attachment_count }}</span>{% endif %}</a>
|
<a href="{% url 'part-attachments' part.id %}">{% trans "Attachments" %} {% if part.attachment_count > 0 %}<span class="badge">{{ part.attachment_count }}</span>{% endif %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -12,6 +12,11 @@ from django.conf.urls import url, include
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
part_related_urls = [
|
||||||
|
url(r'^new/?', views.PartRelatedCreate.as_view(), name='part-related-create'),
|
||||||
|
url(r'^(?P<pk>\d+)/delete/?', views.PartRelatedDelete.as_view(), name='part-related-delete'),
|
||||||
|
]
|
||||||
|
|
||||||
part_attachment_urls = [
|
part_attachment_urls = [
|
||||||
url(r'^new/?', views.PartAttachmentCreate.as_view(), name='part-attachment-create'),
|
url(r'^new/?', views.PartAttachmentCreate.as_view(), name='part-attachment-create'),
|
||||||
url(r'^(?P<pk>\d+)/edit/?', views.PartAttachmentEdit.as_view(), name='part-attachment-edit'),
|
url(r'^(?P<pk>\d+)/edit/?', views.PartAttachmentEdit.as_view(), name='part-attachment-edit'),
|
||||||
@ -60,6 +65,7 @@ part_detail_urls = [
|
|||||||
url(r'^sale-prices/', views.PartDetail.as_view(template_name='part/sale_prices.html'), name='part-sale-prices'),
|
url(r'^sale-prices/', views.PartDetail.as_view(template_name='part/sale_prices.html'), name='part-sale-prices'),
|
||||||
url(r'^tests/', views.PartDetail.as_view(template_name='part/part_tests.html'), name='part-test-templates'),
|
url(r'^tests/', views.PartDetail.as_view(template_name='part/part_tests.html'), name='part-test-templates'),
|
||||||
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
||||||
|
url(r'^related-parts/?', views.PartDetail.as_view(template_name='part/related.html'), name='part-related'),
|
||||||
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
||||||
url(r'^notes/?', views.PartNotes.as_view(), name='part-notes'),
|
url(r'^notes/?', views.PartNotes.as_view(), name='part-notes'),
|
||||||
|
|
||||||
@ -112,6 +118,9 @@ part_urls = [
|
|||||||
# Part category
|
# Part category
|
||||||
url(r'^category/(?P<pk>\d+)/', include(part_category_urls)),
|
url(r'^category/(?P<pk>\d+)/', include(part_category_urls)),
|
||||||
|
|
||||||
|
# Part related
|
||||||
|
url(r'^related-parts/', include(part_related_urls)),
|
||||||
|
|
||||||
# Part attachments
|
# Part attachments
|
||||||
url(r'^attachment/', include(part_attachment_urls)),
|
url(r'^attachment/', include(part_attachment_urls)),
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import os
|
|||||||
from rapidfuzz import fuzz
|
from rapidfuzz import fuzz
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
|
|
||||||
from .models import PartCategory, Part, PartAttachment
|
from .models import PartCategory, Part, PartAttachment, PartRelated
|
||||||
from .models import PartParameterTemplate, PartParameter
|
from .models import PartParameterTemplate, PartParameter
|
||||||
from .models import BomItem
|
from .models import BomItem
|
||||||
from .models import match_part_names
|
from .models import match_part_names
|
||||||
@ -70,6 +70,73 @@ class PartIndex(InvenTreeRoleMixin, ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class PartRelatedCreate(AjaxCreateView):
|
||||||
|
""" View for creating a new PartRelated object
|
||||||
|
|
||||||
|
- The view only makes sense if a Part object is passed to it
|
||||||
|
"""
|
||||||
|
model = PartRelated
|
||||||
|
form_class = part_forms.CreatePartRelatedForm
|
||||||
|
ajax_form_title = _("Add Related Part")
|
||||||
|
ajax_template_name = "modal_form.html"
|
||||||
|
role_required = 'part.change'
|
||||||
|
|
||||||
|
# TODO: QuerySet should not show parts already related to object
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
""" Point part_1 to parent part """
|
||||||
|
|
||||||
|
initials = {}
|
||||||
|
|
||||||
|
part_id = self.request.GET.get('part', None)
|
||||||
|
|
||||||
|
if part_id:
|
||||||
|
try:
|
||||||
|
initials['part_1'] = Part.objects.get(pk=part_id)
|
||||||
|
except (Part.DoesNotExist, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return initials
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
""" Create a form to upload a new PartRelated
|
||||||
|
|
||||||
|
- Hide the 'part_1' field (parent part)
|
||||||
|
"""
|
||||||
|
|
||||||
|
form = super(AjaxCreateView, self).get_form()
|
||||||
|
|
||||||
|
form.fields['part_1'].widget = HiddenInput()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def post_save(self):
|
||||||
|
""" Save PartRelated model (POST method does not) """
|
||||||
|
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
print('form is valid!')
|
||||||
|
|
||||||
|
part_1 = form.cleaned_data['part_1']
|
||||||
|
part_2 = form.cleaned_data['part_2']
|
||||||
|
|
||||||
|
print(f'{part_1=}')
|
||||||
|
print(f'{part_2=}')
|
||||||
|
|
||||||
|
PartRelated.create(part_1, part_2)
|
||||||
|
|
||||||
|
|
||||||
|
class PartRelatedDelete(AjaxDeleteView):
|
||||||
|
""" View for deleting a PartRelated object """
|
||||||
|
|
||||||
|
model = PartRelated
|
||||||
|
ajax_form_title = _("Delete Related Part")
|
||||||
|
ajax_template_name = "related_delete.html"
|
||||||
|
context_object_name = "related"
|
||||||
|
role_required = 'part.change'
|
||||||
|
|
||||||
|
|
||||||
class PartAttachmentCreate(AjaxCreateView):
|
class PartAttachmentCreate(AjaxCreateView):
|
||||||
""" View for creating a new PartAttachment object
|
""" View for creating a new PartAttachment object
|
||||||
|
|
||||||
|
@ -163,6 +163,41 @@ function loadSimplePartTable(table, url, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadPartRelatedTable(table, options={}) {
|
||||||
|
/* Load related parts table */
|
||||||
|
|
||||||
|
var columns = [
|
||||||
|
{
|
||||||
|
field: options.params['part'],
|
||||||
|
title: '{% trans 'Part' %}',
|
||||||
|
sortable: true,
|
||||||
|
sortName: 'name',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
|
var name = '';
|
||||||
|
|
||||||
|
if (row.IPN) {
|
||||||
|
name += row.IPN + ' | ' + row.name;
|
||||||
|
} else {
|
||||||
|
name += row.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderLink(name, '/part/' + row.pk + '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
$(table).inventreeTable({
|
||||||
|
sortName: 'part',
|
||||||
|
groupBy: false,
|
||||||
|
name: options.name || 'related_parts',
|
||||||
|
formatNoMatches: function() { return "{% trans "No parts found" %}"; },
|
||||||
|
columns: columns,
|
||||||
|
showColumns: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function loadParametricPartTable(table, options={}) {
|
function loadParametricPartTable(table, options={}) {
|
||||||
/* Load parametric table for part parameters
|
/* Load parametric table for part parameters
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user