Compare commits

..

5 Commits

Author SHA1 Message Date
ack_ik
3231a41acf изменен файл план 2026-02-10 17:57:41 +03:00
ack_ik
52cca13c86 Change plan 2026-02-10 16:33:49 +03:00
ack_ik
43cb5be21c docs: add ignored folders section to plan.md 2026-02-10 15:03:59 +03:00
ack_ik
a22c9d9d82 Merge branch 'main' of https://gitea.tertelius.space/ack/ProdManager1 2026-02-10 13:43:11 +03:00
ack_ik
b27c685eb1 Первая попытка модельки 2026-02-10 13:40:12 +03:00
11 changed files with 249 additions and 3 deletions

View File

@@ -0,0 +1 @@
Не используй && в вызове команд.

33
.ignore/filters.py Normal file
View File

@@ -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']

39
.ignore/models.py Normal file
View File

@@ -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)

8
.ignore/urls.py Normal file
View File

@@ -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'),
]

58
.ignore/views.py Normal file
View File

@@ -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}"

15
plan.md
View File

@@ -1,12 +1,16 @@
Задача: Создать Django-проект для учета состава изделий (BOM) с иерархией, техпроцессами и системой фильтрации. Задача: Создать Django-проект для учета состава изделий (BOM) с иерархией, техпроцессами и системой фильтрации.
Технологии: Django 5.x, PostgreSQL, django-mptt, django-filter, Bootstrap 5. Технологии: Django 5.x, PostgreSQL, django-mptt, django-filter, Bootstrap 5.
игнорируй папки .ignore
1. Модель данных: 1. Модель данных:
Part (Номенклатура): Part (Номенклатура):
Поля: Наименование, Децимальный номер, Тип заготовки (Choices: Лист, Труба, Круг, Уголок и др.), Толщина, Длина, Вес, Длина реза, Число проколов. Поля: Децимальный номер, Наименование, Заготовка (ссылка на stock_materials), Толщина, Длина, Вес, Длина реза, Число проколов.
Stock_materials (Заготовки): Обозначение, металл (по умолчанию Ст3),
ProductStructure (Дерево состава): * MPTTModel. Поля: parent, item (FK на Part), quantity. ProductStructure (Дерево состава): * MPTTModel. Поля: parent, item (FK на Part), quantity.
@@ -35,11 +39,16 @@ FK на Part. Поля: Тип операции (Choices: Лазер, Сварк
Админка: Настроить TabularInline для ProductionOperation внутри PartAdmin. Админка: Настроить TabularInline для ProductionOperation внутри PartAdmin.
4. Инструкции по коду: 4. Инструкции по коду:
установи django, django-mptt, django-filter, psycopg2-binary
зафиксируй установленные пакеты в requirements.txt
создай проект с настройками в папке core
Создай приложение Prodman
Напиши models.py, filters.py, views.py и urls.py. Напиши models.py, filters.py, views.py и urls.py.
Создай шаблоны в папке templates/, соблюдая иерархию блоков. Создай шаблоны в папке templates/, соблюдая иерархию блоков.
В base.html добавь CDN для Bootstrap 5 и FontAwesome. В base.html добавь CDN для Bootstrap 5 и FontAwesome.
Напиши requirements.txt (включи django, django-mptt, django-filter, psycopg2-binary).

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Django>=5.0
django-mptt
django-filter
psycopg2-binary
bootstrap-icons

26
templates/_navbar.html Normal file
View File

@@ -0,0 +1,26 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'part-list' %}">BOM Manager</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" href="{% url 'part-list' %}">Список деталей</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Структура изделия</a>
</li>
</ul>
<div class="d-flex">
<a href="{% url 'admin:index' %}" class="btn btn-outline-secondary me-2">
<i class="fas fa-tools"></i> Админка
</a>
<a href="#" class="btn btn-outline-secondary">
<i class="fas fa-sign-out-alt"></i> Выйти
</a>
</div>
</div>
</div>
</nav>

22
templates/base.html Normal file
View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title>
<!-- Bootstrap 5 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- FontAwesome CDN -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
{% include '_navbar.html' %}
</head>
<body>
<div class="container mt-4">
{% block content %}
{% endblock %}
</div>
{% include '_footer.html' %}
<!-- Bootstrap 5 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

45
templates/parts_list.html Normal file
View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}
{% block title %}Список деталей{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-3">
<h4>Фильтры</h4>
<form method="get">
{{ filter.form.as_p }}
<button type="submit" class="btn btn-primary">Применить</button>
</form>
</div>
<div class="col-md-9">
<h4>Результаты</h4>
<table class="table table-striped">
<thead>
<tr>
<th>Наименование</th>
<th>Децимальный номер</th>
<th>Тип</th>
<th>Толщина</th>
<th>Длина</th>
<th>Вес</th>
<th>Длина реза</th>
<th>Число проколов</th>
</tr>
</thead>
<tbody>
{% for part in filter.qs %}
<tr>
<td>{{ part.name }}</td>
<td>{{ part.decimal_number }}</td>
<td>{{ part.get_type_display }}</td>
<td>{{ part.thickness }}</td>
<td>{{ part.length }}</td>
<td>{{ part.weight }}</td>
<td>{{ part.cut_length }}</td>
<td>{{ part.number_of_punches }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}