diff --git a/README.md b/.ignore/README.md similarity index 100% rename from README.md rename to .ignore/README.md diff --git a/.ignore/filters.py b/.ignore/filters.py new file mode 100644 index 0000000..ccc1821 --- /dev/null +++ b/.ignore/filters.py @@ -0,0 +1,33 @@ +from django_filters import FilterSet, CharFilter, ModelChoiceFilter +from .models import Part + +class PartFilter(FilterSet): + type = ModelChoiceFilter( + field_name='type', + choices=Part.TYPE_CHOICES, + label='Тип заготовки' + ) + thickness_min = CharFilter( + field_name='thickness', + label='Минимальная толщина', + widget=forms.NumberInput(attrs={'placeholder': 'От'}) + ) + thickness_max = CharFilter( + field_name='thickness', + label='Максимальная толщина', + widget=forms.NumberInput(attrs={'placeholder': 'До'}) + ) + search = CharFilter( + field_name='name', + label='Поиск по наименованию', + widget=forms.TextInput(attrs={'placeholder': 'Введите текст'}) + ) + decimal_number = CharFilter( + field_name='decimal_number', + label='Поиск по децимальному номеру', + widget=forms.TextInput(attrs={'placeholder': 'Введите номер'}) + ) + + class Meta: + model = Part + fields = ['type', 'thickness_min', 'thickness_max', 'search', 'decimal_number'] \ No newline at end of file diff --git a/.ignore/models.py b/.ignore/models.py new file mode 100644 index 0000000..d5845e6 --- /dev/null +++ b/.ignore/models.py @@ -0,0 +1,39 @@ +from django.db import models +from mptt.models import MPTTModel, TreeForeignKey +from django.utils.text import slugify + +TYPE_CHOICES = ( + ('Лист', 'Лист'), + ('Труба', 'Труба'), + ('Круг', 'Круг'), + ('Уголок', 'Уголок'), + ('Профиль', 'Профиль'), + ('Плита', 'Плита'), + ('Швеллер', 'Швеллер'), + ('Ребро', 'Ребро'), + ('Тонкий', 'Тонкий'), + ('Толстый', 'Толстый'), +) + +class Part(models.Model): + name = models.CharField(max_length=255) + decimal_number = models.CharField(max_length=50, unique=True) + type = models.CharField(max_length=50, choices=TYPE_CHOICES) + thickness = models.FloatField(null=True, blank=True) + length = models.FloatField(null=True, blank=True) + weight = models.FloatField(null=True, blank=True) + cut_length = models.FloatField(null=True, blank=True) + number_of_punches = models.IntegerField(null=True, blank=True) + slug = models.SlugField(unique=True, max_length=255) + + def save(self, *args, **kwargs): + self.slug = slugify(f"{self.name}-{self.decimal_number}") + super().save(*args, **kwargs) + + def __str__(self): + return f"{self.name} ({self.decimal_number})" + +class ProductStructure(MPTTModel): + item = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='structures') + quantity = models.FloatField() + parent = TreeForeignKey('self', on_delete=models.CASCADE) diff --git a/.ignore/urls.py b/.ignore/urls.py new file mode 100644 index 0000000..ec1c4c1 --- /dev/null +++ b/.ignore/urls.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from django.urls import path +from .views import PartList + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/parts/', PartList.as_view(), name='part-list'), +] \ No newline at end of file diff --git a/.ignore/views.py b/.ignore/views.py new file mode 100644 index 0000000..8145398 --- /dev/null +++ b/.ignore/views.py @@ -0,0 +1,58 @@ +from django.shortcuts import render +from django.views import View +from django.http import JsonResponse +from .models import Part, ProductStructure +from .filters import PartFilter +from django.contrib import admin +from django.db import models +from django.forms import ModelForm +from django.utils.html import format_html + +class PartList(View): + def get(self, request): + filter = PartFilter(request.GET, queryset=Part.objects.all()) + return JsonResponse({ + 'results': [self.part_to_json(part) for part in filter.qs], + 'filters': filter.filters.items() + }) + + def part_to_json(self, part): + return { + 'id': part.id, + 'name': part.name, + 'decimal_number': part.decimal_number, + 'type': part.get_type_display(), + 'thickness': part.thickness, + 'length': part.length, + 'weight': part.weight, + 'cut_length': part.cut_length, + 'number_of_punches': part.number_of_punches, + 'slug': part.slug + } + +class PartAdmin(admin.ModelAdmin): + list_display = ('name', 'decimal_number', 'type', 'thickness', 'weight') + search_fields = ('name', 'decimal_number') + list_filter = ('type',) + + inlines = [ + ProductionOperationInline + ] + +class ProductionOperationInline(admin.TabularInline): + model = ProductionOperation + extra = 1 + +class ProductionOperation(models.Model): + part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='operations') + operation_type = models.CharField(max_length=50, choices=[ + ('Лазер', 'Лазер'), + ('Сварка', 'Сварка'), + ('Покраска', 'Покраска'), + ('Обработка', 'Обработка'), + ]) + time = models.FloatField() + description = models.TextField(blank=True) + + def __str__(self): + return f"{self.part.name} - {self.operation_type}" \ No newline at end of file diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..09b430f --- /dev/null +++ b/plan.md @@ -0,0 +1,49 @@ +Задача: Создать Django-проект для учета состава изделий (BOM) с иерархией, техпроцессами и системой фильтрации. + +Технологии: Django 5.x, PostgreSQL, django-mptt, django-filter, Bootstrap 5. +игнорируй папки .ignore + +1. Модель данных: + +Part (Номенклатура): + +Поля: Децимальный номер, Наименование, Тип заготовки (Choices: Лист, Труба, Круг, Уголок и др.), Толщина, Длина, Вес, Длина реза, Число проколов. + +ProductStructure (Дерево состава): * MPTTModel. Поля: parent, item (FK на Part), quantity. + +ProductionOperation (Техпроцесс): + +FK на Part. Поля: Тип операции (Choices: Лазер, Сварка, Покраска и др.), Время, Описание. + +2. Система фильтрации (Django-filter): + +Создать PartFilter, который позволит фильтровать список деталей по: + +Типу заготовки (выпадающий список). + +Диапазону толщины (от и до). + +Поиску по наименованию и децимальному номеру (регистронезависимый поиск). + +3. Интерфейс и Шаблоны (Bootstrap 5): + +Структура Layout: Разделить на base.html, _navbar.html (fixed-top), _footer.html (sticky footer через Flexbox min-vh-100). + +Страница списка деталей: Слева или сверху — узкая панель с фильтрами, справа — таблица с результатами. + +Страница изделия: Визуальное дерево состава (используя mptt-tags и рекурсию) с выводом суммарных характеристик. + +Админка: Настроить TabularInline для ProductionOperation внутри PartAdmin. + +4. Инструкции по коду: +создай проект с настройками в папке core + +Создай приложение Prodman + +Напиши models.py, filters.py, views.py и urls.py. + +Создай шаблоны в папке templates/, соблюдая иерархию блоков. + +В base.html добавь CDN для Bootstrap 5 и FontAwesome. + +Напиши requirements.txt (включи django, django-mptt, django-filter, psycopg2-binary). \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ccf99cf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django>=5.0 +django-mptt +django-filter +psycopg2-binary +bootstrap-icons \ No newline at end of file diff --git a/templates/_navbar.html b/templates/_navbar.html new file mode 100644 index 0000000..2804384 --- /dev/null +++ b/templates/_navbar.html @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..ec8a0c0 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,22 @@ + + + + + + {% block title %}{% endblock %} + + + + + {% include '_navbar.html' %} + + +
+ {% block content %} + {% endblock %} +
+ {% include '_footer.html' %} + + + + \ No newline at end of file diff --git a/templates/parts_list.html b/templates/parts_list.html new file mode 100644 index 0000000..572c4d8 --- /dev/null +++ b/templates/parts_list.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block title %}Список деталей{% endblock %} + +{% block content %} +
+
+

Фильтры

+
+ {{ filter.form.as_p }} + +
+
+
+

Результаты

+ + + + + + + + + + + + + + + {% for part in filter.qs %} + + + + + + + + + + + {% endfor %} + +
НаименованиеДецимальный номерТипТолщинаДлинаВесДлина резаЧисло проколов
{{ part.name }}{{ part.decimal_number }}{{ part.get_type_display }}{{ part.thickness }}{{ part.length }}{{ part.weight }}{{ part.cut_length }}{{ part.number_of_punches }}
+
+
+{% endblock %} \ No newline at end of file