mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #1096 from eeintech/categories_parameters
Categories parameter templates
This commit is contained in:
commit
643589b4a9
@ -12,6 +12,7 @@ from crispy_forms.layout import Layout, Field
|
|||||||
from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText, StrictButton, Div
|
from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText, StrictButton, Div
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from common.models import ColorTheme
|
from common.models import ColorTheme
|
||||||
|
from part.models import PartCategory
|
||||||
|
|
||||||
|
|
||||||
class HelperForm(forms.ModelForm):
|
class HelperForm(forms.ModelForm):
|
||||||
@ -200,3 +201,33 @@ class ColorThemeSelectForm(forms.ModelForm):
|
|||||||
css_class='row',
|
css_class='row',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SettingCategorySelectForm(forms.ModelForm):
|
||||||
|
""" Form for setting category settings """
|
||||||
|
|
||||||
|
category = forms.ModelChoiceField(queryset=PartCategory.objects.all())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PartCategory
|
||||||
|
fields = [
|
||||||
|
'category'
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(SettingCategorySelectForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
# Form rendering
|
||||||
|
self.helper.form_show_labels = False
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
Div(
|
||||||
|
Div(Field('category'),
|
||||||
|
css_class='col-sm-6',
|
||||||
|
style='width: 70%;'),
|
||||||
|
Div(StrictButton(_('Select Category'), css_class='btn btn-primary', type='submit'),
|
||||||
|
css_class='col-sm-6',
|
||||||
|
style='width: 30%; padding-left: 0;'),
|
||||||
|
css_class='row',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@ -36,7 +36,8 @@ from django.views.generic.base import RedirectView
|
|||||||
from rest_framework.documentation import include_docs_urls
|
from rest_framework.documentation import include_docs_urls
|
||||||
|
|
||||||
from .views import IndexView, SearchView, DatabaseStatsView
|
from .views import IndexView, SearchView, DatabaseStatsView
|
||||||
from .views import SettingsView, EditUserView, SetPasswordView, ColorThemeSelectView
|
from .views import SettingsView, EditUserView, SetPasswordView
|
||||||
|
from .views import ColorThemeSelectView, SettingCategorySelectView
|
||||||
from .views import DynamicJsView
|
from .views import DynamicJsView
|
||||||
|
|
||||||
from common.views import SettingEdit
|
from common.views import SettingEdit
|
||||||
@ -75,6 +76,7 @@ settings_urls = [
|
|||||||
|
|
||||||
url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'),
|
url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'),
|
||||||
url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'),
|
url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'),
|
||||||
|
url(r'^category/?', SettingCategorySelectView.as_view(), name='settings-category'),
|
||||||
url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'),
|
url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'),
|
||||||
url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'),
|
url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'),
|
||||||
url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'),
|
url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'),
|
||||||
|
@ -24,7 +24,8 @@ from stock.models import StockLocation, StockItem
|
|||||||
from common.models import InvenTreeSetting, ColorTheme
|
from common.models import InvenTreeSetting, ColorTheme
|
||||||
from users.models import check_user_role, RuleSet
|
from users.models import check_user_role, RuleSet
|
||||||
|
|
||||||
from .forms import DeleteForm, EditUserForm, SetPasswordForm, ColorThemeSelectForm
|
from .forms import DeleteForm, EditUserForm, SetPasswordForm
|
||||||
|
from .forms import ColorThemeSelectForm, SettingCategorySelectForm
|
||||||
from .helpers import str2bool
|
from .helpers import str2bool
|
||||||
|
|
||||||
from rest_framework import views
|
from rest_framework import views
|
||||||
@ -750,6 +751,42 @@ class ColorThemeSelectView(FormView):
|
|||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class SettingCategorySelectView(FormView):
|
||||||
|
""" View for selecting categories in settings """
|
||||||
|
|
||||||
|
form_class = SettingCategorySelectForm
|
||||||
|
success_url = reverse_lazy('settings-category')
|
||||||
|
template_name = "InvenTree/settings/category.html"
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
""" Set category selection """
|
||||||
|
|
||||||
|
initial = super(SettingCategorySelectView, self).get_initial()
|
||||||
|
|
||||||
|
category = self.request.GET.get('category', None)
|
||||||
|
if category:
|
||||||
|
initial['category'] = category
|
||||||
|
|
||||||
|
return initial
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
""" Handle POST request (which contains category selection).
|
||||||
|
|
||||||
|
Pass the selected category to the page template
|
||||||
|
"""
|
||||||
|
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
context = self.get_context_data()
|
||||||
|
|
||||||
|
context['category'] = form.cleaned_data['category']
|
||||||
|
|
||||||
|
return super(SettingCategorySelectView, self).render_to_response(context)
|
||||||
|
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseStatsView(AjaxView):
|
class DatabaseStatsView(AjaxView):
|
||||||
""" View for displaying database statistics """
|
""" View for displaying database statistics """
|
||||||
|
|
||||||
|
@ -92,6 +92,13 @@ class InvenTreeSetting(models.Model):
|
|||||||
'validator': bool
|
'validator': bool
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'PART_CATEGORY_PARAMETERS': {
|
||||||
|
'name': _('Copy Category Parameter Templates'),
|
||||||
|
'description': _('Copy category parameter templates when creating a part'),
|
||||||
|
'default': True,
|
||||||
|
'validator': bool
|
||||||
|
},
|
||||||
|
|
||||||
'PART_COMPONENT': {
|
'PART_COMPONENT': {
|
||||||
'name': _('Component'),
|
'name': _('Component'),
|
||||||
'description': _('Parts can be used as sub-components by default'),
|
'description': _('Parts can be used as sub-components by default'),
|
||||||
|
@ -12,6 +12,7 @@ from .models import PartCategory, Part
|
|||||||
from .models import PartAttachment, PartStar, PartRelated
|
from .models import PartAttachment, PartStar, PartRelated
|
||||||
from .models import BomItem
|
from .models import BomItem
|
||||||
from .models import PartParameterTemplate, PartParameter
|
from .models import PartParameterTemplate, PartParameter
|
||||||
|
from .models import PartCategoryParameterTemplate
|
||||||
from .models import PartTestTemplate
|
from .models import PartTestTemplate
|
||||||
from .models import PartSellPriceBreak
|
from .models import PartSellPriceBreak
|
||||||
|
|
||||||
@ -274,6 +275,11 @@ class ParameterAdmin(ImportExportModelAdmin):
|
|||||||
list_display = ('part', 'template', 'data')
|
list_display = ('part', 'template', 'data')
|
||||||
|
|
||||||
|
|
||||||
|
class PartCategoryParameterAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PartSellPriceBreakAdmin(admin.ModelAdmin):
|
class PartSellPriceBreakAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -290,5 +296,6 @@ admin.site.register(PartStar, PartStarAdmin)
|
|||||||
admin.site.register(BomItem, BomItemAdmin)
|
admin.site.register(BomItem, BomItemAdmin)
|
||||||
admin.site.register(PartParameterTemplate, ParameterTemplateAdmin)
|
admin.site.register(PartParameterTemplate, ParameterTemplateAdmin)
|
||||||
admin.site.register(PartParameter, ParameterAdmin)
|
admin.site.register(PartParameter, ParameterAdmin)
|
||||||
|
admin.site.register(PartCategoryParameterTemplate, PartCategoryParameterAdmin)
|
||||||
admin.site.register(PartTestTemplate, PartTestTemplateAdmin)
|
admin.site.register(PartTestTemplate, PartTestTemplateAdmin)
|
||||||
admin.site.register(PartSellPriceBreak, PartSellPriceBreakAdmin)
|
admin.site.register(PartSellPriceBreak, PartSellPriceBreakAdmin)
|
||||||
|
@ -21,6 +21,7 @@ from .models import Part, PartCategory, BomItem, PartStar
|
|||||||
from .models import PartParameter, PartParameterTemplate
|
from .models import PartParameter, PartParameterTemplate
|
||||||
from .models import PartAttachment, PartTestTemplate
|
from .models import PartAttachment, PartTestTemplate
|
||||||
from .models import PartSellPriceBreak
|
from .models import PartSellPriceBreak
|
||||||
|
from .models import PartCategoryParameterTemplate
|
||||||
|
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
|
|
||||||
@ -111,6 +112,36 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
queryset = PartCategory.objects.all()
|
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):
|
class PartSalePriceList(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for list view of PartSalePriceBreak model
|
API endpoint for list view of PartSalePriceBreak model
|
||||||
@ -864,6 +895,7 @@ part_api_urls = [
|
|||||||
|
|
||||||
# Base URL for PartCategory API endpoints
|
# Base URL for PartCategory API endpoints
|
||||||
url(r'^category/', include([
|
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'^(?P<pk>\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'),
|
||||||
url(r'^$', CategoryList.as_view(), name='api-part-category-list'),
|
url(r'^$', CategoryList.as_view(), name='api-part-category-list'),
|
||||||
])),
|
])),
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
name: Thickness
|
name: Thickness
|
||||||
units: mm
|
units: mm
|
||||||
|
|
||||||
# And some parameters (requires part.yaml)
|
# Add some parameters to parts (requires part.yaml)
|
||||||
- model: part.PartParameter
|
- model: part.PartParameter
|
||||||
pk: 1
|
pk: 1
|
||||||
fields:
|
fields:
|
||||||
@ -31,4 +31,19 @@
|
|||||||
fields:
|
fields:
|
||||||
part: 2
|
part: 2
|
||||||
template: 1
|
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 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 PartCategoryParameterTemplate
|
||||||
from .models import PartTestTemplate
|
from .models import PartTestTemplate
|
||||||
from .models import PartSellPriceBreak
|
from .models import PartSellPriceBreak
|
||||||
|
|
||||||
@ -201,10 +202,22 @@ class EditPartForm(HelperForm):
|
|||||||
help_text=_('Confirm part creation'),
|
help_text=_('Confirm part creation'),
|
||||||
widget=forms.HiddenInput())
|
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:
|
class Meta:
|
||||||
model = Part
|
model = Part
|
||||||
fields = [
|
fields = [
|
||||||
'category',
|
'category',
|
||||||
|
'selected_category_templates',
|
||||||
|
'parent_category_templates',
|
||||||
'name',
|
'name',
|
||||||
'IPN',
|
'IPN',
|
||||||
'description',
|
'description',
|
||||||
@ -266,6 +279,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):
|
class EditBomItemForm(HelperForm):
|
||||||
""" Form for editing a BomItem object """
|
""" 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
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import InvenTree.fields
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
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')),
|
('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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('part', '0053_merge_20201103_1028'),
|
('part', '0053_partcategoryparametertemplate'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -12,7 +12,8 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from django.db import models, transaction
|
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.db.models.functions import Coalesce
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
@ -164,6 +165,26 @@ class PartCategory(InvenTreeTree):
|
|||||||
|
|
||||||
return category_parameters
|
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')
|
@receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log')
|
||||||
def before_delete_part_category(sender, instance, using, **kwargs):
|
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.
|
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:
|
if self.pk:
|
||||||
previous = Part.objects.get(pk=self.pk)
|
previous = Part.objects.get(pk=self.pk)
|
||||||
|
|
||||||
@ -322,6 +346,44 @@ class Part(MPTTModel):
|
|||||||
|
|
||||||
super().save(*args, **kwargs)
|
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):
|
def __str__(self):
|
||||||
return f"{self.full_name} - {self.description}"
|
return f"{self.full_name} - {self.description}"
|
||||||
|
|
||||||
@ -1664,6 +1726,49 @@ class PartParameter(models.Model):
|
|||||||
return part_parameter
|
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):
|
class BomItem(models.Model):
|
||||||
""" A BomItem links a part to its component items.
|
""" A BomItem links a part to its component items.
|
||||||
A part can have a BOM (bill of materials) which defines
|
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,
|
from .models import (BomItem, Part, PartAttachment, PartCategory,
|
||||||
PartParameter, PartParameterTemplate, PartSellPriceBreak,
|
PartParameter, PartParameterTemplate, PartSellPriceBreak,
|
||||||
PartStar, PartTestTemplate)
|
PartStar, PartTestTemplate, PartCategoryParameterTemplate)
|
||||||
|
|
||||||
|
|
||||||
class CategorySerializer(InvenTreeModelSerializer):
|
class CategorySerializer(InvenTreeModelSerializer):
|
||||||
@ -425,3 +425,21 @@ class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
|||||||
'name',
|
'name',
|
||||||
'units',
|
'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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase, TransactionTestCase
|
||||||
import django.core.exceptions as django_exceptions
|
import django.core.exceptions as django_exceptions
|
||||||
|
|
||||||
|
from .models import Part, PartCategory
|
||||||
from .models import PartParameter, PartParameterTemplate
|
from .models import PartParameter, PartParameterTemplate
|
||||||
|
from .models import PartCategoryParameterTemplate
|
||||||
|
|
||||||
|
|
||||||
class TestParams(TestCase):
|
class TestParams(TestCase):
|
||||||
@ -24,7 +26,10 @@ class TestParams(TestCase):
|
|||||||
self.assertEquals(str(t1), 'Length (mm)')
|
self.assertEquals(str(t1), 'Length (mm)')
|
||||||
|
|
||||||
p1 = PartParameter.objects.get(pk=1)
|
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):
|
def test_validate(self):
|
||||||
|
|
||||||
@ -40,3 +45,47 @@ class TestParams(TestCase):
|
|||||||
t3 = PartParameterTemplate(name='aBcde', units='dd')
|
t3 = PartParameterTemplate(name='aBcde', units='dd')
|
||||||
t3.full_clean()
|
t3.full_clean()
|
||||||
t3.save()
|
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'),
|
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 = [
|
part_category_urls = [
|
||||||
url(r'^edit/?', views.CategoryEdit.as_view(), name='category-edit'),
|
url(r'^edit/?', views.CategoryEdit.as_view(), name='category-edit'),
|
||||||
url(r'^delete/?', views.CategoryDelete.as_view(), name='category-delete'),
|
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'^parametric/?', views.CategoryParametric.as_view(), name='category-parametric'),
|
||||||
url(r'^.*$', views.CategoryDetail.as_view(), name='category-detail'),
|
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.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.shortcuts import HttpResponseRedirect
|
from django.shortcuts import HttpResponseRedirect
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -23,6 +24,7 @@ from decimal import Decimal, InvalidOperation
|
|||||||
|
|
||||||
from .models import PartCategory, Part, PartAttachment, PartRelated
|
from .models import PartCategory, Part, PartAttachment, PartRelated
|
||||||
from .models import PartParameterTemplate, PartParameter
|
from .models import PartParameterTemplate, PartParameter
|
||||||
|
from .models import PartCategoryParameterTemplate
|
||||||
from .models import BomItem
|
from .models import BomItem
|
||||||
from .models import match_part_names
|
from .models import match_part_names
|
||||||
from .models import PartTestTemplate
|
from .models import PartTestTemplate
|
||||||
@ -625,6 +627,10 @@ class PartCreate(AjaxCreateView):
|
|||||||
# Hide the default_supplier field (there are no matching supplier parts yet!)
|
# Hide the default_supplier field (there are no matching supplier parts yet!)
|
||||||
form.fields['default_supplier'].widget = HiddenInput()
|
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
|
return form
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
@ -667,7 +673,14 @@ class PartCreate(AjaxCreateView):
|
|||||||
# Record the user who created this part
|
# Record the user who created this part
|
||||||
part.creation_user = request.user
|
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['pk'] = part.pk
|
||||||
data['text'] = str(part)
|
data['text'] = str(part)
|
||||||
@ -700,6 +713,10 @@ class PartCreate(AjaxCreateView):
|
|||||||
if label in self.request.GET:
|
if label in self.request.GET:
|
||||||
initials[label] = self.request.GET.get(label)
|
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
|
return initials
|
||||||
|
|
||||||
|
|
||||||
@ -2215,6 +2232,185 @@ class CategoryCreate(AjaxCreateView):
|
|||||||
return initials
|
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):
|
class BomItemDetail(InvenTreeRoleMixin, DetailView):
|
||||||
""" Detail view for BomItem """
|
""" Detail view for BomItem """
|
||||||
context_object_name = 'item'
|
context_object_name = 'item'
|
||||||
|
114
InvenTree/templates/InvenTree/settings/category.html
Normal file
114
InvenTree/templates/InvenTree/settings/category.html
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
{% extends "InvenTree/settings/settings.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
{% include "InvenTree/settings/tabs.html" with tab='category' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{% trans "Category Settings" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block settings %}
|
||||||
|
|
||||||
|
<form action="{% url 'settings-category' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
<div id="category-select">
|
||||||
|
{% crispy form %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if category %}
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h4>{% trans "Category Parameter Templates" %}</h4>
|
||||||
|
|
||||||
|
<div id='param-buttons'>
|
||||||
|
<button class='btn btn-success' id='new-param'>
|
||||||
|
<span class='fas fa-plus-circle'></span> {% trans "New Parameter" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed' id='param-table' data-toolbar='#param-buttons'>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_ready %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
{# Convert dropdown to select2 format #}
|
||||||
|
$(document).ready(function() {
|
||||||
|
attachSelect('#category-select');
|
||||||
|
});
|
||||||
|
|
||||||
|
{% if category %}
|
||||||
|
$("#param-table").inventreeTable({
|
||||||
|
url: "{% url 'api-part-category-parameters' category.pk %}",
|
||||||
|
queryParams: {
|
||||||
|
ordering: 'name',
|
||||||
|
},
|
||||||
|
formatNoMatches: function() { return '{% trans "No category parameter templates found" %}'; },
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
field: 'pk',
|
||||||
|
title: 'ID',
|
||||||
|
visible: false,
|
||||||
|
switchable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'parameter_template_detail.name',
|
||||||
|
title: '{% trans "Parameter Template" %}',
|
||||||
|
sortable: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'default_value',
|
||||||
|
title: '{% trans "Default Value" %}',
|
||||||
|
sortable: 'true',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
var bEdit = "<button title='{% trans "Edit Template" %}' class='template-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-edit'></span></button>";
|
||||||
|
var bDel = "<button title='{% trans "Delete Template" %}' class='template-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-trash-alt icon-red'></span></button>";
|
||||||
|
|
||||||
|
var html = value
|
||||||
|
html += "<div class='btn-group float-right' role='group'>" + bEdit + bDel + "</div>";
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#new-param").click(function() {
|
||||||
|
launchModalForm("{% url 'category-param-template-create' category.pk %}", {
|
||||||
|
success: function() {
|
||||||
|
$("#param-table").bootstrapTable('refresh');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#param-table").on('click', '.template-edit', function() {
|
||||||
|
var button = $(this);
|
||||||
|
|
||||||
|
var url = "/part/category/{{ category.pk }}/parameters/" + button.attr('pk') + "/edit/";
|
||||||
|
|
||||||
|
launchModalForm(url, {
|
||||||
|
success: function() {
|
||||||
|
$("#param-table").bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#param-table").on('click', '.template-delete', function() {
|
||||||
|
var button = $(this);
|
||||||
|
|
||||||
|
var url = "/part/category/{{ category.pk }}/parameters/" + button.attr('pk') + "/delete/";
|
||||||
|
|
||||||
|
launchModalForm(url, {
|
||||||
|
success: function() {
|
||||||
|
$("#param-table").bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -27,6 +27,7 @@
|
|||||||
{% include "InvenTree/settings/setting.html" with key="PART_COPY_BOM" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_COPY_BOM" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_COPY_PARAMETERS" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_COPY_PARAMETERS" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PART_COPY_TESTS" %}
|
{% include "InvenTree/settings/setting.html" with key="PART_COPY_TESTS" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="PART_CATEGORY_PARAMETERS" %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
<li{% ifequal tab 'currency' %} class='active'{% endifequal %}>
|
<li{% ifequal tab 'currency' %} class='active'{% endifequal %}>
|
||||||
<a href="{% url 'settings-currency' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currency" %}</a>
|
<a href="{% url 'settings-currency' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currency" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li{% ifequal tab 'category' %} class='active'{% endifequal %}>
|
||||||
|
<a href="{% url 'settings-category' %}"><span class='fa fa-sitemap'></span> {% trans "Categories" %}</a>
|
||||||
|
</li>
|
||||||
<li{% ifequal tab 'part' %} class='active'{% endifequal %}>
|
<li{% ifequal tab 'part' %} class='active'{% endifequal %}>
|
||||||
<a href="{% url 'settings-part' %}"><span class='fas fa-shapes'></span> {% trans "Parts" %}</a>
|
<a href="{% url 'settings-part' %}"><span class='fas fa-shapes'></span> {% trans "Parts" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -58,6 +58,7 @@ class RuleSet(models.Model):
|
|||||||
'part_partparametertemplate',
|
'part_partparametertemplate',
|
||||||
'part_partparameter',
|
'part_partparameter',
|
||||||
'part_partrelated',
|
'part_partrelated',
|
||||||
|
'part_partcategoryparametertemplate',
|
||||||
],
|
],
|
||||||
'stock': [
|
'stock': [
|
||||||
'stock_stockitem',
|
'stock_stockitem',
|
||||||
|
Loading…
Reference in New Issue
Block a user