mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-21 06:16:29 +00:00
Merge remote-tracking branch 'inventree/master' into currency-support
# Conflicts: # InvenTree/InvenTree/settings.py # InvenTree/InvenTree/urls.py # InvenTree/templates/InvenTree/settings/tabs.html # InvenTree/users/models.py # requirements.txt IMPORTANT: Had to merge some migration files due to different migrations applied on the part model tables
This commit is contained in:
@ -12,6 +12,7 @@ from .models import PartCategory, Part
|
||||
from .models import PartAttachment, PartStar, PartRelated
|
||||
from .models import BomItem
|
||||
from .models import PartParameterTemplate, PartParameter
|
||||
from .models import PartCategoryParameterTemplate
|
||||
from .models import PartTestTemplate
|
||||
from .models import PartSellPriceBreak
|
||||
|
||||
@ -274,6 +275,11 @@ class ParameterAdmin(ImportExportModelAdmin):
|
||||
list_display = ('part', 'template', 'data')
|
||||
|
||||
|
||||
class PartCategoryParameterAdmin(admin.ModelAdmin):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PartSellPriceBreakAdmin(admin.ModelAdmin):
|
||||
|
||||
class Meta:
|
||||
@ -290,5 +296,6 @@ admin.site.register(PartStar, PartStarAdmin)
|
||||
admin.site.register(BomItem, BomItemAdmin)
|
||||
admin.site.register(PartParameterTemplate, ParameterTemplateAdmin)
|
||||
admin.site.register(PartParameter, ParameterAdmin)
|
||||
admin.site.register(PartCategoryParameterTemplate, PartCategoryParameterAdmin)
|
||||
admin.site.register(PartTestTemplate, PartTestTemplateAdmin)
|
||||
admin.site.register(PartSellPriceBreak, PartSellPriceBreakAdmin)
|
||||
|
@ -21,6 +21,7 @@ from .models import Part, PartCategory, BomItem, PartStar
|
||||
from .models import PartParameter, PartParameterTemplate
|
||||
from .models import PartAttachment, PartTestTemplate
|
||||
from .models import PartSellPriceBreak
|
||||
from .models import PartCategoryParameterTemplate
|
||||
|
||||
from build.models import Build
|
||||
|
||||
@ -111,6 +112,36 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = PartCategory.objects.all()
|
||||
|
||||
|
||||
class CategoryParameters(generics.ListAPIView):
|
||||
""" API endpoint for accessing a list of PartCategory objects.
|
||||
|
||||
- GET: Return a list of PartCategory objects
|
||||
"""
|
||||
|
||||
queryset = PartCategoryParameterTemplate.objects.all()
|
||||
serializer_class = part_serializers.CategoryParameterTemplateSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Custom filtering:
|
||||
- Allow filtering by "null" parent to retrieve top-level part categories
|
||||
"""
|
||||
|
||||
cat_id = self.kwargs.get('pk', None)
|
||||
|
||||
queryset = super().get_queryset()
|
||||
|
||||
if cat_id is not None:
|
||||
|
||||
try:
|
||||
cat_id = int(cat_id)
|
||||
queryset = queryset.filter(category=cat_id)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class PartSalePriceList(generics.ListCreateAPIView):
|
||||
"""
|
||||
API endpoint for list view of PartSalePriceBreak model
|
||||
@ -864,6 +895,7 @@ part_api_urls = [
|
||||
|
||||
# Base URL for PartCategory API endpoints
|
||||
url(r'^category/', include([
|
||||
url(r'^(?P<pk>\d+)/parameters/?', CategoryParameters.as_view(), name='api-part-category-parameters'),
|
||||
url(r'^(?P<pk>\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'),
|
||||
url(r'^$', CategoryList.as_view(), name='api-part-category-list'),
|
||||
])),
|
||||
|
@ -18,7 +18,7 @@
|
||||
name: Thickness
|
||||
units: mm
|
||||
|
||||
# And some parameters (requires part.yaml)
|
||||
# Add some parameters to parts (requires part.yaml)
|
||||
- model: part.PartParameter
|
||||
pk: 1
|
||||
fields:
|
||||
@ -31,4 +31,19 @@
|
||||
fields:
|
||||
part: 2
|
||||
template: 1
|
||||
data: 12
|
||||
data: 12
|
||||
|
||||
# Add some template parameters to categories (requires category.yaml)
|
||||
- model: part.PartCategoryParameterTemplate
|
||||
pk: 1
|
||||
fields:
|
||||
category: 7
|
||||
parameter_template: 1
|
||||
default_value: '2.8'
|
||||
|
||||
- model: part.PartCategoryParameterTemplate
|
||||
pk: 2
|
||||
fields:
|
||||
category: 7
|
||||
parameter_template: 3
|
||||
default_value: '0.5'
|
||||
|
@ -16,6 +16,7 @@ from django.utils.translation import ugettext as _
|
||||
from .models import Part, PartCategory, PartAttachment, PartRelated
|
||||
from .models import BomItem
|
||||
from .models import PartParameterTemplate, PartParameter
|
||||
from .models import PartCategoryParameterTemplate
|
||||
from .models import PartTestTemplate
|
||||
from .models import PartSellPriceBreak
|
||||
|
||||
@ -199,10 +200,22 @@ class EditPartForm(HelperForm):
|
||||
help_text=_('Confirm part creation'),
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
selected_category_templates = forms.BooleanField(required=False,
|
||||
initial=False,
|
||||
label=_('Include category parameter templates'),
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
parent_category_templates = forms.BooleanField(required=False,
|
||||
initial=False,
|
||||
label=_('Include parent categories parameter templates'),
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
class Meta:
|
||||
model = Part
|
||||
fields = [
|
||||
'category',
|
||||
'selected_category_templates',
|
||||
'parent_category_templates',
|
||||
'name',
|
||||
'IPN',
|
||||
'description',
|
||||
@ -264,6 +277,28 @@ class EditCategoryForm(HelperForm):
|
||||
]
|
||||
|
||||
|
||||
class EditCategoryParameterTemplateForm(HelperForm):
|
||||
""" Form for editing a PartCategoryParameterTemplate object """
|
||||
|
||||
add_to_same_level_categories = forms.BooleanField(required=False,
|
||||
initial=False,
|
||||
help_text=_('Add parameter template to same level categories'))
|
||||
|
||||
add_to_all_categories = forms.BooleanField(required=False,
|
||||
initial=False,
|
||||
help_text=_('Add parameter template to all categories'))
|
||||
|
||||
class Meta:
|
||||
model = PartCategoryParameterTemplate
|
||||
fields = [
|
||||
'category',
|
||||
'parameter_template',
|
||||
'default_value',
|
||||
'add_to_same_level_categories',
|
||||
'add_to_all_categories',
|
||||
]
|
||||
|
||||
|
||||
class EditBomItemForm(HelperForm):
|
||||
""" Form for editing a BomItem object """
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-27 04:57
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0051_bomitem_optional'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='part',
|
||||
name='link',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True),
|
||||
),
|
||||
]
|
@ -2,7 +2,7 @@
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
import InvenTree.fields
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@ -19,4 +19,9 @@ class Migration(migrations.Migration):
|
||||
('part_2', models.ForeignKey(help_text='Select Related Part', on_delete=django.db.models.deletion.DO_NOTHING, related_name='related_parts_2', to='part.Part')),
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='part',
|
||||
name='link',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True),
|
||||
),
|
||||
]
|
||||
|
@ -1,14 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-11-03 10:28
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0052_auto_20201027_1557'),
|
||||
('part', '0052_partrelated'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
@ -0,0 +1,27 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-30 18:04
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0052_partrelated'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PartCategoryParameterTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('default_value', models.CharField(blank=True, help_text='Default Parameter Value', max_length=500)),
|
||||
('category', models.ForeignKey(help_text='Part Category', on_delete=django.db.models.deletion.CASCADE, related_name='parameter_templates', to='part.PartCategory')),
|
||||
('parameter_template', models.ForeignKey(help_text='Parameter Template', on_delete=django.db.models.deletion.CASCADE, related_name='part_categories', to='part.PartParameterTemplate')),
|
||||
],
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='partcategoryparametertemplate',
|
||||
constraint=models.UniqueConstraint(fields=('category', 'parameter_template'), name='unique_category_parameter_template_pair'),
|
||||
),
|
||||
]
|
@ -7,7 +7,7 @@ import part.settings
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0053_merge_20201103_1028'),
|
||||
('part', '0052_partrelated'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
14
InvenTree/part/migrations/0060_merge_20201112_1722.py
Normal file
14
InvenTree/part/migrations/0060_merge_20201112_1722.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Generated by Django 3.0.7 on 2020-11-12 06:22
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0053_partcategoryparametertemplate'),
|
||||
('part', '0059_auto_20201112_1112'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
@ -12,7 +12,8 @@ from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Sum
|
||||
from django.db.utils import IntegrityError
|
||||
from django.db.models import Sum, UniqueConstraint
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
@ -164,6 +165,26 @@ class PartCategory(InvenTreeTree):
|
||||
|
||||
return category_parameters
|
||||
|
||||
@classmethod
|
||||
def get_parent_categories(cls):
|
||||
""" Return tuple list of parent (root) categories """
|
||||
|
||||
# Get root nodes
|
||||
root_categories = cls.objects.filter(level=0)
|
||||
|
||||
parent_categories = []
|
||||
for category in root_categories:
|
||||
parent_categories.append((category.id, category.name))
|
||||
|
||||
return parent_categories
|
||||
|
||||
def get_parameter_templates(self):
|
||||
""" Return parameter templates associated to category """
|
||||
|
||||
prefetch = PartCategoryParameterTemplate.objects.prefetch_related('category', 'parameter_template')
|
||||
|
||||
return prefetch.filter(category=self.id)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log')
|
||||
def before_delete_part_category(sender, instance, using, **kwargs):
|
||||
@ -307,6 +328,9 @@ class Part(MPTTModel):
|
||||
If not, it is considered "orphaned" and will be deleted.
|
||||
"""
|
||||
|
||||
# Get category templates settings
|
||||
add_category_templates = kwargs.pop('add_category_templates', None)
|
||||
|
||||
if self.pk:
|
||||
previous = Part.objects.get(pk=self.pk)
|
||||
|
||||
@ -322,6 +346,44 @@ class Part(MPTTModel):
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if add_category_templates:
|
||||
# Get part category
|
||||
category = self.category
|
||||
|
||||
if add_category_templates:
|
||||
# Store templates added to part
|
||||
template_list = []
|
||||
|
||||
# Create part parameters for selected category
|
||||
category_templates = add_category_templates['main']
|
||||
if category_templates:
|
||||
for template in category.get_parameter_templates():
|
||||
parameter = PartParameter.create(part=self,
|
||||
template=template.parameter_template,
|
||||
data=template.default_value,
|
||||
save=True)
|
||||
if parameter:
|
||||
template_list.append(template.parameter_template)
|
||||
|
||||
# Create part parameters for parent category
|
||||
category_templates = add_category_templates['parent']
|
||||
if category_templates:
|
||||
# Get parent categories
|
||||
parent_categories = category.get_ancestors()
|
||||
|
||||
for category in parent_categories:
|
||||
for template in category.get_parameter_templates():
|
||||
# Check that template wasn't already added
|
||||
if template.parameter_template not in template_list:
|
||||
try:
|
||||
PartParameter.create(part=self,
|
||||
template=template.parameter_template,
|
||||
data=template.default_value,
|
||||
save=True)
|
||||
except IntegrityError:
|
||||
# PartParameter already exists
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.full_name} - {self.description}"
|
||||
|
||||
@ -571,7 +633,8 @@ class Part(MPTTModel):
|
||||
super().clean()
|
||||
|
||||
if self.trackable:
|
||||
for parent_part in self.used_in.all():
|
||||
for item in self.used_in.all():
|
||||
parent_part = item.part
|
||||
if not parent_part.trackable:
|
||||
parent_part.trackable = True
|
||||
parent_part.clean()
|
||||
@ -1041,8 +1104,16 @@ class Part(MPTTModel):
|
||||
- Exclude parts which this part is in the BOM for
|
||||
"""
|
||||
|
||||
parts = Part.objects.filter(component=True).exclude(id=self.id)
|
||||
parts = parts.exclude(id__in=[part.id for part in self.used_in.all()])
|
||||
# Start with a list of all parts designated as 'sub components'
|
||||
parts = Part.objects.filter(component=True)
|
||||
|
||||
# Exclude this part
|
||||
parts = parts.exclude(id=self.id)
|
||||
|
||||
# Exclude any parts that this part is used *in* (to prevent recursive BOMs)
|
||||
used_in = self.used_in.all()
|
||||
|
||||
parts = parts.exclude(id__in=[item.part.id for item in used_in])
|
||||
|
||||
return parts
|
||||
|
||||
@ -1655,6 +1726,49 @@ class PartParameter(models.Model):
|
||||
return part_parameter
|
||||
|
||||
|
||||
class PartCategoryParameterTemplate(models.Model):
|
||||
"""
|
||||
A PartCategoryParameterTemplate creates a unique relationship between a PartCategory
|
||||
and a PartParameterTemplate.
|
||||
Multiple PartParameterTemplate instances can be associated to a PartCategory to drive
|
||||
a default list of parameter templates attached to a Part instance upon creation.
|
||||
|
||||
Attributes:
|
||||
category: Reference to a single PartCategory object
|
||||
parameter_template: Reference to a single PartParameterTemplate object
|
||||
default_value: The default value for the parameter in the context of the selected
|
||||
category
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
UniqueConstraint(fields=['category', 'parameter_template'],
|
||||
name='unique_category_parameter_template_pair')
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
""" String representation of a PartCategoryParameterTemplate (admin interface) """
|
||||
|
||||
if self.default_value:
|
||||
return f'{self.category.name} | {self.parameter_template.name} | {self.default_value}'
|
||||
else:
|
||||
return f'{self.category.name} | {self.parameter_template.name}'
|
||||
|
||||
category = models.ForeignKey(PartCategory,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='parameter_templates',
|
||||
help_text=_('Part Category'))
|
||||
|
||||
parameter_template = models.ForeignKey(PartParameterTemplate,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='part_categories',
|
||||
help_text=_('Parameter Template'))
|
||||
|
||||
default_value = models.CharField(max_length=500,
|
||||
blank=True,
|
||||
help_text=_('Default Parameter Value'))
|
||||
|
||||
|
||||
class BomItem(models.Model):
|
||||
""" A BomItem links a part to its component items.
|
||||
A part can have a BOM (bill of materials) which defines
|
||||
|
@ -15,7 +15,7 @@ from stock.models import StockItem
|
||||
|
||||
from .models import (BomItem, Part, PartAttachment, PartCategory,
|
||||
PartParameter, PartParameterTemplate, PartSellPriceBreak,
|
||||
PartStar, PartTestTemplate)
|
||||
PartStar, PartTestTemplate, PartCategoryParameterTemplate)
|
||||
|
||||
|
||||
class CategorySerializer(InvenTreeModelSerializer):
|
||||
@ -418,3 +418,21 @@ class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
||||
'name',
|
||||
'units',
|
||||
]
|
||||
|
||||
|
||||
class CategoryParameterTemplateSerializer(InvenTreeModelSerializer):
|
||||
""" Serializer for PartCategoryParameterTemplate """
|
||||
|
||||
parameter_template_detail = PartParameterTemplateSerializer(source='parameter_template',
|
||||
many=False,
|
||||
read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PartCategoryParameterTemplate
|
||||
fields = [
|
||||
'pk',
|
||||
'category',
|
||||
'parameter_template',
|
||||
'parameter_template_detail',
|
||||
'default_value',
|
||||
]
|
||||
|
@ -3,10 +3,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
import django.core.exceptions as django_exceptions
|
||||
|
||||
from .models import Part, PartCategory
|
||||
from .models import PartParameter, PartParameterTemplate
|
||||
from .models import PartCategoryParameterTemplate
|
||||
|
||||
|
||||
class TestParams(TestCase):
|
||||
@ -24,7 +26,10 @@ class TestParams(TestCase):
|
||||
self.assertEquals(str(t1), 'Length (mm)')
|
||||
|
||||
p1 = PartParameter.objects.get(pk=1)
|
||||
self.assertEqual(str(p1), "M2x4 LPHS : Length = 4mm")
|
||||
self.assertEqual(str(p1), 'M2x4 LPHS : Length = 4mm')
|
||||
|
||||
c1 = PartCategoryParameterTemplate.objects.get(pk=1)
|
||||
self.assertEqual(str(c1), 'Mechanical | Length | 2.8')
|
||||
|
||||
def test_validate(self):
|
||||
|
||||
@ -40,3 +45,47 @@ class TestParams(TestCase):
|
||||
t3 = PartParameterTemplate(name='aBcde', units='dd')
|
||||
t3.full_clean()
|
||||
t3.save()
|
||||
|
||||
|
||||
class TestCategoryTemplates(TransactionTestCase):
|
||||
|
||||
fixtures = [
|
||||
'location',
|
||||
'category',
|
||||
'part',
|
||||
'params'
|
||||
]
|
||||
|
||||
def test_validate(self):
|
||||
|
||||
# Category templates
|
||||
n = PartCategoryParameterTemplate.objects.all().count()
|
||||
self.assertEqual(n, 2)
|
||||
|
||||
category = PartCategory.objects.get(pk=8)
|
||||
|
||||
t1 = PartParameterTemplate.objects.get(pk=2)
|
||||
c1 = PartCategoryParameterTemplate(category=category,
|
||||
parameter_template=t1,
|
||||
default_value='xyz')
|
||||
c1.save()
|
||||
|
||||
n = PartCategoryParameterTemplate.objects.all().count()
|
||||
self.assertEqual(n, 3)
|
||||
|
||||
# Get test part
|
||||
part = Part.objects.get(pk=1)
|
||||
|
||||
# Get part parameters count
|
||||
n_param = part.get_parameters().count()
|
||||
|
||||
add_category_templates = {
|
||||
'main': True,
|
||||
'parent': True,
|
||||
}
|
||||
# Save it with category parameters
|
||||
part.save(**{'add_category_templates': add_category_templates})
|
||||
|
||||
# Check new part parameters count
|
||||
# Only 2 parameters should be added as one already existed with same template
|
||||
self.assertEqual(n_param + 2, part.get_parameters().count())
|
||||
|
@ -80,10 +80,18 @@ part_detail_urls = [
|
||||
url(r'^.*$', views.PartDetail.as_view(), name='part-detail'),
|
||||
]
|
||||
|
||||
category_parameter_urls = [
|
||||
url(r'^new/', views.CategoryParameterTemplateCreate.as_view(), name='category-param-template-create'),
|
||||
url(r'^(?P<pid>\d+)/edit/', views.CategoryParameterTemplateEdit.as_view(), name='category-param-template-edit'),
|
||||
url(r'^(?P<pid>\d+)/delete/', views.CategoryParameterTemplateDelete.as_view(), name='category-param-template-delete'),
|
||||
]
|
||||
|
||||
part_category_urls = [
|
||||
url(r'^edit/?', views.CategoryEdit.as_view(), name='category-edit'),
|
||||
url(r'^delete/?', views.CategoryDelete.as_view(), name='category-delete'),
|
||||
|
||||
url(r'^parameters/', include(category_parameter_urls)),
|
||||
|
||||
url(r'^parametric/?', views.CategoryParametric.as_view(), name='category-parametric'),
|
||||
url(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'),
|
||||
]
|
||||
|
@ -7,6 +7,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.db.utils import IntegrityError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.shortcuts import HttpResponseRedirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -25,6 +26,7 @@ from decimal import Decimal, InvalidOperation
|
||||
|
||||
from .models import PartCategory, Part, PartAttachment, PartRelated
|
||||
from .models import PartParameterTemplate, PartParameter
|
||||
from .models import PartCategoryParameterTemplate
|
||||
from .models import BomItem
|
||||
from .models import match_part_names
|
||||
from .models import PartTestTemplate
|
||||
@ -627,6 +629,10 @@ class PartCreate(AjaxCreateView):
|
||||
# Hide the default_supplier field (there are no matching supplier parts yet!)
|
||||
form.fields['default_supplier'].widget = HiddenInput()
|
||||
|
||||
# Display category templates widgets
|
||||
form.fields['selected_category_templates'].widget = CheckboxInput()
|
||||
form.fields['parent_category_templates'].widget = CheckboxInput()
|
||||
|
||||
return form
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@ -669,7 +675,14 @@ class PartCreate(AjaxCreateView):
|
||||
# Record the user who created this part
|
||||
part.creation_user = request.user
|
||||
|
||||
part.save()
|
||||
# Store category templates settings
|
||||
add_category_templates = {
|
||||
'main': form.cleaned_data['selected_category_templates'],
|
||||
'parent': form.cleaned_data['parent_category_templates'],
|
||||
}
|
||||
|
||||
# Save part and pass category template settings
|
||||
part.save(**{'add_category_templates': add_category_templates})
|
||||
|
||||
data['pk'] = part.pk
|
||||
data['text'] = str(part)
|
||||
@ -702,6 +715,10 @@ class PartCreate(AjaxCreateView):
|
||||
if label in self.request.GET:
|
||||
initials[label] = self.request.GET.get(label)
|
||||
|
||||
# Automatically create part parameters from category templates
|
||||
initials['selected_category_templates'] = str2bool(InvenTreeSetting.get_setting('PART_CATEGORY_PARAMETERS', False))
|
||||
initials['parent_category_templates'] = initials['selected_category_templates']
|
||||
|
||||
return initials
|
||||
|
||||
|
||||
@ -2205,6 +2222,185 @@ class CategoryCreate(AjaxCreateView):
|
||||
return initials
|
||||
|
||||
|
||||
class CategoryParameterTemplateCreate(AjaxCreateView):
|
||||
""" View for creating a new PartCategoryParameterTemplate """
|
||||
|
||||
role_required = 'part.add'
|
||||
|
||||
model = PartCategoryParameterTemplate
|
||||
form_class = part_forms.EditCategoryParameterTemplateForm
|
||||
ajax_form_title = _('Create Category Parameter Template')
|
||||
|
||||
def get_initial(self):
|
||||
""" Get initial data for Category """
|
||||
initials = super().get_initial()
|
||||
|
||||
category_id = self.kwargs.get('pk', None)
|
||||
|
||||
if category_id:
|
||||
try:
|
||||
initials['category'] = PartCategory.objects.get(pk=category_id)
|
||||
except (PartCategory.DoesNotExist, ValueError):
|
||||
pass
|
||||
|
||||
return initials
|
||||
|
||||
def get_form(self):
|
||||
""" Create a form to upload a new CategoryParameterTemplate
|
||||
- Hide the 'category' field (parent part)
|
||||
- Display parameter templates which are not yet related
|
||||
"""
|
||||
|
||||
form = super(AjaxCreateView, self).get_form()
|
||||
|
||||
form.fields['category'].widget = HiddenInput()
|
||||
|
||||
if form.is_valid():
|
||||
form.cleaned_data['category'] = self.kwargs.get('pk', None)
|
||||
|
||||
try:
|
||||
# Get selected category
|
||||
category = self.get_initial()['category']
|
||||
|
||||
# Get existing parameter templates
|
||||
parameters = [template.parameter_template.pk
|
||||
for template in category.get_parameter_templates()]
|
||||
|
||||
# Exclude templates already linked to category
|
||||
updated_choices = []
|
||||
for choice in form.fields["parameter_template"].choices:
|
||||
if (choice[0] not in parameters):
|
||||
updated_choices.append(choice)
|
||||
|
||||
# Update choices for parameter templates
|
||||
form.fields['parameter_template'].choices = updated_choices
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return form
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
""" Capture the POST request
|
||||
|
||||
- If the add_to_all_categories object is set, link parameter template to
|
||||
all categories
|
||||
- If the add_to_same_level_categories object is set, link parameter template to
|
||||
same level categories
|
||||
"""
|
||||
|
||||
form = self.get_form()
|
||||
|
||||
valid = form.is_valid()
|
||||
|
||||
if valid:
|
||||
add_to_same_level_categories = form.cleaned_data['add_to_same_level_categories']
|
||||
add_to_all_categories = form.cleaned_data['add_to_all_categories']
|
||||
|
||||
selected_category = PartCategory.objects.get(pk=int(self.kwargs['pk']))
|
||||
parameter_template = form.cleaned_data['parameter_template']
|
||||
default_value = form.cleaned_data['default_value']
|
||||
|
||||
categories = PartCategory.objects.all()
|
||||
|
||||
if add_to_same_level_categories and not add_to_all_categories:
|
||||
# Get level
|
||||
level = selected_category.level
|
||||
# Filter same level categories
|
||||
categories = categories.filter(level=level)
|
||||
|
||||
if add_to_same_level_categories or add_to_all_categories:
|
||||
# Add parameter template and default value to categories
|
||||
for category in categories:
|
||||
# Skip selected category (will be processed in the post call)
|
||||
if category.pk != selected_category.pk:
|
||||
try:
|
||||
cat_template = PartCategoryParameterTemplate.objects.create(category=category,
|
||||
parameter_template=parameter_template,
|
||||
default_value=default_value)
|
||||
cat_template.save()
|
||||
except IntegrityError:
|
||||
# Parameter template is already linked to category
|
||||
pass
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
class CategoryParameterTemplateEdit(AjaxUpdateView):
|
||||
""" View for editing a PartCategoryParameterTemplate """
|
||||
|
||||
role_required = 'part.change'
|
||||
|
||||
model = PartCategoryParameterTemplate
|
||||
form_class = part_forms.EditCategoryParameterTemplateForm
|
||||
ajax_form_title = _('Edit Category Parameter Template')
|
||||
|
||||
def get_object(self):
|
||||
try:
|
||||
self.object = self.model.objects.get(pk=self.kwargs['pid'])
|
||||
except:
|
||||
return None
|
||||
|
||||
return self.object
|
||||
|
||||
def get_form(self):
|
||||
""" Create a form to upload a new CategoryParameterTemplate
|
||||
- Hide the 'category' field (parent part)
|
||||
- Display parameter templates which are not yet related
|
||||
"""
|
||||
|
||||
form = super(AjaxUpdateView, self).get_form()
|
||||
|
||||
form.fields['category'].widget = HiddenInput()
|
||||
form.fields['add_to_all_categories'].widget = HiddenInput()
|
||||
form.fields['add_to_same_level_categories'].widget = HiddenInput()
|
||||
|
||||
if form.is_valid():
|
||||
form.cleaned_data['category'] = self.kwargs.get('pk', None)
|
||||
|
||||
try:
|
||||
# Get selected category
|
||||
category = PartCategory.objects.get(pk=self.kwargs.get('pk', None))
|
||||
# Get selected template
|
||||
selected_template = self.get_object().parameter_template
|
||||
|
||||
# Get existing parameter templates
|
||||
parameters = [template.parameter_template.pk
|
||||
for template in category.get_parameter_templates()
|
||||
if template.parameter_template.pk != selected_template.pk]
|
||||
|
||||
# Exclude templates already linked to category
|
||||
updated_choices = []
|
||||
for choice in form.fields["parameter_template"].choices:
|
||||
if (choice[0] not in parameters):
|
||||
updated_choices.append(choice)
|
||||
|
||||
# Update choices for parameter templates
|
||||
form.fields['parameter_template'].choices = updated_choices
|
||||
# Set initial choice to current template
|
||||
form.fields['parameter_template'].initial = selected_template
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return form
|
||||
|
||||
|
||||
class CategoryParameterTemplateDelete(AjaxDeleteView):
|
||||
""" View for deleting an existing PartCategoryParameterTemplate """
|
||||
|
||||
role_required = 'part.delete'
|
||||
|
||||
model = PartCategoryParameterTemplate
|
||||
ajax_form_title = _("Delete Category Parameter Template")
|
||||
|
||||
def get_object(self):
|
||||
try:
|
||||
self.object = self.model.objects.get(pk=self.kwargs['pid'])
|
||||
except:
|
||||
return None
|
||||
|
||||
return self.object
|
||||
|
||||
|
||||
class BomItemDetail(InvenTreeRoleMixin, DetailView):
|
||||
""" Detail view for BomItem """
|
||||
context_object_name = 'item'
|
||||
|
Reference in New Issue
Block a user