Поменял модели операций с JSON на полиморфные

This commit is contained in:
2026-02-14 18:13:51 +03:00
parent 01d560cfb4
commit e77237ca5e
5 changed files with 3681 additions and 116 deletions

View File

@@ -1,118 +1,159 @@
from django.contrib import admin from django.contrib import admin
from mptt.admin import MPTTModelAdmin, DraggableMPTTAdmin from mptt.admin import DraggableMPTTAdmin
from .models import Item, BOMNode, EntityType, RoutingStep, WorkCenter from polymorphic.admin import (
PolymorphicChildModelAdmin,
PolymorphicParentModelAdmin,
PolymorphicChildModelFilter,
PolymorphicInlineSupportMixin,
StackedPolymorphicInline
)
from .models import (
Item, BOMNode, EntityType, WorkCenter,
BaseOperation, LaserCutSheet, LaserCutTube,
Turning, Weld, Paint, Coating
)
from django import forms
# Register your models here. # создаем форму с фильтром для dxf и dwg файлов
class LaserCutSheetForm(forms.ModelForm):
class Meta:
model = LaserCutSheet
fields = '__all__'
widgets = {
'dxf_file': forms.FileInput(attrs={'accept': '.dxf,.DXF'}),
}
# Создаем инлайн для отображения RoutingStep в Item # создаем форму с фильтром для iges и igs файлов
class RoutingStepInline(admin.TabularInline): class LaserCutTubeForm(forms.ModelForm):
model = RoutingStep class Meta:
extra = 1 model = LaserCutTube
# Поле tech_params в стандартной админке — это просто текстовое поле. fields = '__all__'
# Позже его можно будет "причесать" с помощью JS, чтобы оно выглядело красиво. widgets = {
fields = ('order', 'operation_type', 'work_center', 'drawing_file', 'tech_params') 'iges_file': forms.FileInput(attrs={'accept': '.iges,.IGS'}),
}
# Создаем инлайн для отображения BOMNode в Item # --- 1. ИНЛАЙНЫ ДЛЯ ОПЕРАЦИЙ (ДЛЯ КАРТОЧКИ ITEM) ---
class LaserCutSheetInline(StackedPolymorphicInline.Child):
model = LaserCutSheet
form = LaserCutSheetForm
class LaserCutTubeInline(StackedPolymorphicInline.Child):
model = LaserCutTube
form = LaserCutTubeForm
class TurningInline(StackedPolymorphicInline.Child):
model = Turning
class WeldInline(StackedPolymorphicInline.Child):
model = Weld
class PaintInline(StackedPolymorphicInline.Child):
model = Paint
class BaseOperationInline(StackedPolymorphicInline):
model = BaseOperation
child_inlines = (
LaserCutSheetInline,
LaserCutTubeInline,
TurningInline,
WeldInline,
PaintInline,
)
# --- 2. ДОЧЕРНИЕ АДМИНКИ (НУЖНЫ ДЛЯ РАБОТЫ ПОЛИМОРФИЗМА) ---
@admin.register(LaserCutSheet)
class LaserCutSheetAdmin(PolymorphicChildModelAdmin):
base_model = BaseOperation
form = LaserCutSheetForm
@admin.register(LaserCutTube)
class LaserCutTubeAdmin(PolymorphicChildModelAdmin):
base_model = BaseOperation
form = LaserCutTubeForm
@admin.register(Turning)
class TurningAdmin(PolymorphicChildModelAdmin):
base_model = BaseOperation
@admin.register(Weld)
class WeldAdmin(PolymorphicChildModelAdmin):
base_model = BaseOperation
@admin.register(Paint)
class PaintAdmin(PolymorphicChildModelAdmin):
base_model = BaseOperation
# --- 3. РОДИТЕЛЬСКАЯ АДМИНКА ОПЕРАЦИЙ ---
@admin.register(BaseOperation)
class BaseOperationParentAdmin(PolymorphicParentModelAdmin):
base_model = BaseOperation
# Здесь указываем именно МОДЕЛИ, а не классы админок
child_models = (LaserCutSheet, LaserCutTube, Turning, Weld, Paint)
list_filter = (PolymorphicChildModelFilter,)
list_display = ('item', 'order', 'polymorphic_ctype')
# --- 4. ИНЛАЙН ДЛЯ BOM (СОСТАВ ИЗДЕЛИЯ) ---
class BOMNodeInline(admin.TabularInline): class BOMNodeInline(admin.TabularInline):
model = BOMNode model = BOMNode
# fk_name = 'parent'
# Указываем fk_key т
fk_name = 'parent'
# Указываем поля для отображения в инлайне
fields = ('item', 'quantity') fields = ('item', 'quantity')
extra = 1
# Колличество пустых строк в инлайне
extra = 3
# добавим автокомплит для удобства
autocomplete_fields = ['item'] autocomplete_fields = ['item']
def formfield_for_foreignkey(self, db_field, request, **kwargs): def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "parent": if db_field.name == "parent":
# Родителями в инлайне могут быть только Сборки/Изделия/Комплексы
kwargs["queryset"] = BOMNode.objects.filter( kwargs["queryset"] = BOMNode.objects.filter(
item__entity_type__in=[EntityType.UNIT, EntityType.ASSEMBLY, EntityType.COMPLEX] item__entity_type__in=[EntityType.UNIT, EntityType.ASSEMBLY, EntityType.COMPLEX]
) )
return super().formfield_for_foreignkey(db_field, request, **kwargs) return super().formfield_for_foreignkey(db_field, request, **kwargs)
# --- 5. ОСНОВНАЯ АДМИНКА КОМПОНЕНТА (ITEM) ---
@admin.register(Item) @admin.register(Item)
class ItemAdmin(admin.ModelAdmin): class ItemAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
list_display = ('get_full_name', 'entity_type', 'drawing') list_display = ('get_full_name', 'entity_type', 'drawing')
list_filter = ('entity_type', 'is_assembly') list_filter = ('entity_type', 'is_assembly')
# Поиск по номеру и названию
search_fields = ['designation', 'title'] search_fields = ['designation', 'title']
@admin.display(description='Наименование') @admin.display(description='Наименование')
def get_full_name(self, obj): def get_full_name(self, obj):
return f"{obj.designation or ''} {obj.title}" return f"{obj.designation or ''} {obj.title}".strip()
inlines = [RoutingStepInline] # Добавляем оба инлайна: и состав, и техпроцесс
# def get_queryset(self, request): inlines = [BOMNodeInline, BaseOperationInline]
# qs = super().get_queryset(request)
# return qs.filter(entity_type=EntityType.ASSEMBLY) # --- 6. АДМИНКА ДЛЯ ДЕРЕВА BOM ---
@admin.register(BOMNode) @admin.register(BOMNode)
class BOMNodeAdmin(DraggableMPTTAdmin): class BOMNodeAdmin(DraggableMPTTAdmin):
# Настройки отображения древовидной структуры mptt_level_indent = 40
mptt_level_indent = 40 # отступ в пикселях
# Поля для отображения в админке
fields = ('parent', 'item', 'quantity') fields = ('parent', 'item', 'quantity')
# Чтобы в списке было видно не только название связи, но и данные из Item
list_display = ('tree_actions', 'indented_title', 'get_designation', 'quantity') list_display = ('tree_actions', 'indented_title', 'get_designation', 'quantity')
list_display_links = ('indented_title',) list_display_links = ('indented_title',)
# Отображаем инлайн.
inlines = [BOMNodeInline]
# Метод для отображения децимального номера из связанной модели Item
def get_designation(self, obj): def get_designation(self, obj):
return obj.item.designation return obj.item.designation
get_designation.short_description = 'Децимальный номер' get_designation.short_description = 'Децимальный номер'
# Оптимизация запросов (чтобы админка не тормозила)
def get_queryset(self, request): def get_queryset(self, request):
qs = super().get_queryset(request) return super().get_queryset(request).select_related('item', 'parent')
return qs.select_related('item', 'parent')
# def formfield_for_foreignkey(self, db_field, request, **kwargs):
# if db_field.name == "parent":
# # Фильтруем: родителями могут быть только узлы,
# # где связанный Item является сборкой или изделием
# kwargs["queryset"] = BOMNode.objects.filter(
# item__entity_type__in=[EntityType.UNIT, EntityType.ASSEMBLY]
# )
# return super().formfield_for_foreignkey(db_field, request, **kwargs)
# переопределим метод get_inlines в BOMNodeAdmin.
# Если узел привязан к «Детали» или «Стандартному изделию», таблица внизу просто не появится.
def get_inlines(self, request, obj=None): def get_inlines(self, request, obj=None):
# Если мы создаем новый узел (obj is None), инлайн можно показать if obj and obj.item.entity_type in [EntityType.UNIT, EntityType.ASSEMBLY, EntityType.COMPLEX]:
if obj is None:
return []
# Разрешенные типы (те, в которые МОЖНО вкладывать)
allowed_types = [EntityType.UNIT, EntityType.ASSEMBLY, EntityType.COMPLEX]
# Если тип изделия связанного узла в списке разрешенных — показываем инлайн
if obj.item.entity_type in allowed_types:
return [BOMNodeInline] return [BOMNodeInline]
# Для остальных (детали, стандартные) возвращаем пустой список
return [] return []
@admin.register(RoutingStep) # --- 7. ПРОЧИЕ СПРАВОЧНИКИ ---
class RoutingStepAdmin(admin.ModelAdmin):
# Поля для отображения в админке
list_display = ('item', 'operation_type', 'work_center', 'order')
list_filter = ('operation_type', 'work_center')
search_fields = ['item__title']
@admin.register(WorkCenter) @admin.register(WorkCenter)
class WorkCenterAdmin(admin.ModelAdmin): class WorkCenterAdmin(admin.ModelAdmin):
list_display = ('name', 'rate_per_hour') list_display = ('name', 'rate_per_hour')
search_fields = ['name']
@admin.register(Coating)
class CoatingAdmin(admin.ModelAdmin):
list_display = ('name', 'consumption')

View File

@@ -0,0 +1,115 @@
# Generated by Django 6.0.2 on 2026-02-14 10:18
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bom_manager', '0005_workcenter_routingstep'),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='BaseOperation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField(default=10, help_text='Например: 10, 20, 30...', verbose_name='Номер операции')),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='operations', to='bom_manager.item', verbose_name='Компонент')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
('work_center', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='operations', to='bom_manager.workcenter', verbose_name='Станок/Участок')),
],
options={
'verbose_name': 'Операция',
'verbose_name_plural': 'Операции',
'ordering': ['order'],
},
),
migrations.CreateModel(
name='Coating',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Название покрытия')),
('consumption', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Расход покрытия, м2/л')),
],
options={
'verbose_name': 'Покрытие',
'verbose_name_plural': 'Покрытия',
},
),
migrations.CreateModel(
name='LaserCutSheet',
fields=[
('baseoperation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bom_manager.baseoperation')),
('thickness', models.IntegerField(default=3, verbose_name='Толщина листа, мм')),
('cut_length', models.IntegerField(default=0, verbose_name='Длина реза, мм')),
('pierces', models.IntegerField(default=1, verbose_name='Количество проколов')),
('dxf_file', models.FileField(blank=True, null=True, upload_to='dxf_files/%Y/%m', verbose_name='DXF файл')),
],
options={
'verbose_name': 'Лазерная резка листа',
'verbose_name_plural': 'ЛРЛ',
},
bases=('bom_manager.baseoperation',),
),
migrations.CreateModel(
name='LaserCutTube',
fields=[
('baseoperation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bom_manager.baseoperation')),
('thinckness', models.IntegerField(default=3, verbose_name='Толщина трубы, мм')),
('cut_length', models.IntegerField(default=0, verbose_name='Длина реза, мм')),
('pierces', models.IntegerField(default=1, verbose_name='Количество проколов')),
('iges_file', models.FileField(blank=True, null=True, upload_to='iges_files/%Y/%m', verbose_name='IGES файл')),
],
options={
'verbose_name': 'Лазерная резка трубы',
'verbose_name_plural': 'ЛРТ',
},
bases=('bom_manager.baseoperation',),
),
migrations.CreateModel(
name='Paint',
fields=[
('baseoperation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bom_manager.baseoperation')),
('area', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Площадь покраски, м2')),
('color', models.CharField(blank=True, max_length=100, null=True, verbose_name='Код RAL')),
('number_of_layers', models.IntegerField(default=1, verbose_name='Число слоев')),
('coating', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='paints', to='bom_manager.coating', verbose_name='Покрытие')),
],
options={
'verbose_name': 'Покраска',
'verbose_name_plural': 'Покраски',
},
bases=('bom_manager.baseoperation',),
),
migrations.CreateModel(
name='Turning',
fields=[
('baseoperation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bom_manager.baseoperation')),
('work_time', models.IntegerField(default=0, verbose_name='Время работы, мин')),
],
options={
'verbose_name': 'Токарная обработка',
'verbose_name_plural': 'ТО',
},
bases=('bom_manager.baseoperation',),
),
migrations.CreateModel(
name='Weld',
fields=[
('baseoperation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bom_manager.baseoperation')),
('total_weld_length', models.IntegerField(default=0, verbose_name='Общая длина сварки, мм')),
('avg_leg', models.IntegerField(default=0, verbose_name='Средний катет, мм')),
],
options={
'verbose_name': 'Сварка',
'verbose_name_plural': 'Сварки',
},
bases=('bom_manager.baseoperation',),
),
migrations.DeleteModel(
name='RoutingStep',
),
]

View File

@@ -1,8 +1,162 @@
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from polymorphic.models import PolymorphicModel
from django.core.exceptions import ValidationError
# Create your models here. # Create your models here.
# Базовая модель операции
class BaseOperation(PolymorphicModel):
item = models.ForeignKey('Item', on_delete=models.CASCADE, related_name='operations', verbose_name="Компонент")
order = models.PositiveIntegerField("Номер операции", default=10, help_text="Например: 10, 20, 30...")
work_center = models.ForeignKey('WorkCenter', on_delete=models.CASCADE, related_name='operations', verbose_name="Станок/Участок")
class Meta:
ordering = ['order']
verbose_name = "Операция"
verbose_name_plural = "Операции"
def __str__(self):
return f"{self.order}. {self._meta.verbose_name}"
def get_absolute_url(self):
return reverse("operation_detail", kwargs={"pk": self.pk})
def clean(self):
if self.order < 1:
raise ValidationError("Номер операции должен быть больше 0!")
if self.work_center is None:
raise ValidationError("Станок/Участок не может быть пустым!")
# Операция лазерной резки листа
class LaserCutSheet(BaseOperation):
thickness = models.IntegerField("Толщина листа, мм", default=3)
cut_length = models.IntegerField("Длина реза, мм", default=0)
pierces = models.IntegerField("Количество проколов", default=1)
dxf_file = models.FileField("DXF файл", upload_to='dxf_files/%Y/%m', null=True, blank=True)
def clean(self):
if self.cut_length < 1:
raise ValidationError("Длина реза должна быть больше 0!")
if self.pierces < 1:
raise ValidationError("Количество проколов должно быть больше 0!")
if self.item.entity_type != EntityType.PART:
raise ValidationError("Компонент должен быть деталью!")
def get_absolute_url(self):
return reverse("lrl_detail", kwargs={"pk": self.pk})
class Meta:
verbose_name = "Лазерная резка листа"
verbose_name_plural = "ЛРЛ"
def __str__(self):
return f"{self.order}. {self._meta.verbose_name}"
# Операция лазерной резки трубы
class LaserCutTube(BaseOperation):
thinckness = models.IntegerField("Толщина трубы, мм", default=3)
cut_length = models.IntegerField("Длина реза, мм", default=0)
pierces = models.IntegerField("Количество проколов", default=1)
iges_file = models.FileField("IGES файл", upload_to='iges_files/%Y/%m', null=True, blank=True)
def clean(self):
if self.cut_length < 1:
raise ValidationError("Длина реза должна быть больше 0!")
if self.pierces < 1:
raise ValidationError("Количество проколов должно быть больше 0!")
if self.item.entity_type != EntityType.PART:
raise ValidationError("Компонент должен быть деталью!")
def get_absolute_url(self):
return reverse("lrt_detail", kwargs={"pk": self.pk})
class Meta:
verbose_name = "Лазерная резка трубы"
verbose_name_plural = "ЛРТ"
def __str__(self):
return f"{self.order}. {self._meta.verbose_name}"
# Операция токарной обработки
class Turning(BaseOperation):
work_time = models.IntegerField("Время работы, мин", default=0)
def clean(self):
if self.work_time < 1:
raise ValidationError("Время работы должно быть больше 0!")
def get_absolute_url(self):
return reverse("turning_detail", kwargs={"pk": self.pk})
class Meta:
verbose_name = "Токарная обработка"
verbose_name_plural = "ТО"
def __str__(self):
return f"{self.order}. {self._meta.verbose_name}"
# Операция сварки
class Weld(BaseOperation):
total_weld_length = models.IntegerField("Общая длина сварки, мм", default=0)
avg_leg = models.IntegerField("Средний катет, мм", default=0)
# todo: добавить реализацию швов с разными катетами
def clean(self):
if self.total_weld_length < 1:
raise ValidationError("Общая длина сварки должна быть больше 0!")
if self.avg_leg < 1:
raise ValidationError("Средний катет должен быть больше 0!")
if self.item.entity_type not in [EntityType.ASSEMBLY, EntityType.COMPLEX, EntityType.UNIT]:
raise ValidationError("Компонент должен быть составным изделием!")
def get_absolute_url(self):
return reverse("weld_detail", kwargs={"pk": self.pk})
class Meta:
verbose_name = "Сварка"
verbose_name_plural = "Сварки"
def __str__(self):
return f"{self.order}. {self._meta.verbose_name}"
# Операция покраски
class Paint(BaseOperation):
"""Покраска"""
# площадь покраски
area = models.DecimalField("Площадь покраски, м2", max_digits=10, decimal_places=2, default=0)
# цвет по RAL
color = models.CharField("Код RAL", max_length=100, blank=True, null=True)
# число слоев
number_of_layers = models.IntegerField("Число слоев", default=1)
# покрытие из другой таблицы
coating = models.ForeignKey('Coating', on_delete=models.CASCADE, related_name='paints', verbose_name="Покрытие")
class Meta:
verbose_name = "Покраска"
verbose_name_plural = "Покраски"
def __str__(self):
return f"{self.order}. {self._meta.verbose_name}"
class Coating(models.Model):
"""Покрытие"""
# наименование краски/грунта
name = models.CharField("Название покрытия", max_length=100)
# расход покрытия
consumption = models.DecimalField("Расход покрытия, м2/л", max_digits=10, decimal_places=2, default=0)
class Meta:
verbose_name = "Покрытие"
verbose_name_plural = "Покрытия"
def __str__(self):
return self.name
# todo: добавить операции гибки, зачистки, перемещения и лентопильного станка
class EntityType(models.TextChoices): class EntityType(models.TextChoices):
"""Тип изделия""" """Тип изделия"""
UNIT = 'UNIT', 'Изделие' UNIT = 'UNIT', 'Изделие'
@@ -12,17 +166,17 @@ class EntityType(models.TextChoices):
COMPLEX = 'CPLX', 'Комплекс' COMPLEX = 'CPLX', 'Комплекс'
class OperationType(models.TextChoices): # class OperationType(models.TextChoices):
"""Тип операции""" # """Тип операции"""
LRL = 'LRL', 'Лазерная резка листа (ЛРЛ)' # LRL = 'LRL', 'Лазерная резка листа (ЛРЛ)' # todo: add LRL
LRT = 'LRT', 'Лазерная резка трубы (ЛРТ)' # LRT = 'LRT', 'Лазерная резка трубы (ЛРТ)' # todo: add LRT
TO = 'TO', 'Токарная обработка (ТО)' # TO = 'TO', 'Токарная обработка (ТО)'
WELD = 'WELD', 'Сварка' # WELD = 'WELD', 'Сварка'
PAINT = 'PAINT', 'Покраска' # PAINT = 'PAINT', 'Покраска'
BEND = 'BEND', 'Гибка' # BEND = 'BEND', 'Гибка'
CLEAN = 'CLEAN', 'Зачистка' # CLEAN = 'CLEAN', 'Зачистка'
BANDSAW = 'BANDSAW', 'Лентопильный станок' # BANDSAW = 'BANDSAW', 'Лентопильный станок'
MOVE = 'MOVE', 'Перемещение' # MOVE = 'MOVE', 'Перемещение'
class WorkCenter(models.Model): class WorkCenter(models.Model):
"""Справочник станков или участков""" """Справочник станков или участков"""
@@ -105,43 +259,43 @@ class BOMNode(MPTTModel):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("bom_node_detail", kwargs={"pk": self.pk}) return reverse("bom_node_detail", kwargs={"pk": self.pk})
class RoutingStep(models.Model): # class RoutingStep(models.Model):
"""Технологическая операция""" # """Технологическая операция"""
# Связь с компонентом # # Связь с компонентом
item = models.ForeignKey('Item', on_delete=models.CASCADE, related_name='routing_steps', verbose_name="Деталь/Сборка") # item = models.ForeignKey('Item', on_delete=models.CASCADE, related_name='routing_steps', verbose_name="Деталь/Сборка")
# Тип операции # # Тип операции
operation_type = models.CharField("Тип операции", max_length=10, choices=OperationType.choices) # operation_type = models.CharField("Тип операции", max_length=10, choices=OperationType.choices)
# Связь с станком # # Связь с станком
work_center = models.ForeignKey(WorkCenter, on_delete=models.SET_NULL, null=True, verbose_name="Станок/Участок") # work_center = models.ForeignKey(WorkCenter, on_delete=models.SET_NULL, null=True, verbose_name="Станок/Участок")
# Номер операции # # Номер операции
order = models.PositiveIntegerField("Номер операции", default=10, help_text="Например: 10, 20, 30...") # order = models.PositiveIntegerField("Номер операции", default=10, help_text="Например: 10, 20, 30...")
# Файлы # # Файлы
drawing_file = models.FileField("Тех. файл (DXF/IGES)", upload_to='tech_files/%Y/%m', null=True, blank=True) # drawing_file = models.FileField("Тех. файл (DXF/IGES)", upload_to='tech_files/%Y/%m', null=True, blank=True)
# Гибкие данные (JSON) # # Гибкие данные (JSON)
# Сюда будем писать: {"cut_length": 1500, "pierces": 20} или {"welds": [{"leg": 5, "length": 100}]} # # Сюда будем писать: {"cut_length": 1500, "pierces": 20} или {"welds": [{"leg": 5, "length": 100}]}
tech_params = models.JSONField("Технологические параметры", default=dict, blank=True) # tech_params = models.JSONField("Технологические параметры", default=dict, blank=True)
# Общие поля для всех операций # # Общие поля для всех операций
setup_time = models.DurationField("Время наладки", null=True, blank=True) # setup_time = models.DurationField("Время наладки", null=True, blank=True)
cycle_time = models.DurationField("Время цикла (на 1 шт)", null=True, blank=True) # cycle_time = models.DurationField("Время цикла (на 1 шт)", null=True, blank=True)
def clean(self): # def clean(self):
if self.operation_type == OperationType.LRL: # if self.operation_type == OperationType.LRL:
if 'cut_length' not in self.tech_params: # if 'cut_length' not in self.tech_params:
raise ValidationError("Для ЛРЛ обязательно укажите 'cut_length' в параметрах!") # raise ValidationError("Для ЛРЛ обязательно укажите 'cut_length' в параметрах!")
if 'pierces' not in self.tech_params: # if 'pierces' not in self.tech_params:
raise ValidationError("Для ЛРЛ обязательно укажите 'pierces' в параметрах!") # raise ValidationError("Для ЛРЛ обязательно укажите 'pierces' в параметрах!")
if self.operation_type == OperationType.LRT: # if self.operation_type == OperationType.LRT:
if 'cut_length' not in self.tech_params: # if 'cut_length' not in self.tech_params:
raise ValidationError("Для ЛРТ обязательно укажите 'cut_length' в параметрах!") # raise ValidationError("Для ЛРТ обязательно укажите 'cut_length' в параметрах!")
class Meta: # class Meta:
ordering = ['order'] # ordering = ['order']
verbose_name = "Технологическая операция" # verbose_name = "Технологическая операция"
verbose_name_plural = "Технологический маршрут" # verbose_name_plural = "Технологический маршрут"
def __str__(self): # def __str__(self):
return f"{self.order}. {self.get_operation_type_display()}" # return f"{self.order}. {self.get_operation_type_display()}"

View File

@@ -37,6 +37,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'polymorphic', # added app Polymorphic
'mptt', # added app MPTT 'mptt', # added app MPTT
'bom_manager', # added app Bom Manager 'bom_manager', # added app Bom Manager
] ]

File diff suppressed because it is too large Load Diff