From 87dae7b461a2640e3b1cae3e0650f023be047a9e Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Wed, 8 Jan 2025 17:29:25 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=9C=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20?= =?UTF-8?q?"=D0=A1=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D1=87=D0=BD=D0=B8=D0=BA?= =?UTF-8?q?=20=D1=82=D0=BE=D0=B2=D0=B0=D1=80=D0=BE=D0=B2"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Создать структуру базы данных для товаров: Поля: наименование, штрих-код, срок годности, размеры, единицы измерения, производитель, категория товара, температурные условия хранения, акции (тип "1+1", "2+1"). Реализовать функционал: Добавление, редактирование и удаление карточек товаров. Проверка на дублирование данных (штрих-код). Валидация данных (например, срок годности). --- inventory/models.py | 28 +++++++------ logs.log | 2 + product_directory.log | 4 ++ product_directory/__init__.py | 0 product_directory/admin.py | 8 ++++ product_directory/api.py | 7 ++++ product_directory/apps.py | 6 +++ product_directory/forms.py | 15 +++++++ product_directory/migrations/0001_initial.py | 29 +++++++++++++ product_directory/migrations/__init__.py | 0 product_directory/models.py | 26 ++++++++++++ product_directory/serializers.py | 7 ++++ .../product_directory/product_list.html | 13 ++++++ product_directory/tests.py | 18 ++++++++ product_directory/views.py | 42 +++++++++++++++++++ settings/base.py | 25 +++++++++++ 16 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 logs.log create mode 100644 product_directory.log create mode 100644 product_directory/__init__.py create mode 100644 product_directory/admin.py create mode 100644 product_directory/api.py create mode 100644 product_directory/apps.py create mode 100644 product_directory/forms.py create mode 100644 product_directory/migrations/0001_initial.py create mode 100644 product_directory/migrations/__init__.py create mode 100644 product_directory/models.py create mode 100644 product_directory/serializers.py create mode 100644 product_directory/templates/product_directory/product_list.html create mode 100644 product_directory/tests.py create mode 100644 product_directory/views.py diff --git a/inventory/models.py b/inventory/models.py index b6928e0..31563e0 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -1,19 +1,23 @@ from django.db import models from django.contrib.auth.models import AbstractUser, Permission, Group -# Справочник товаров -class Product(models.Model): - name = models.CharField(max_length=255, verbose_name="Наименование товара") - manufacturer_name = models.CharField(max_length=255, verbose_name="Производитель") - manufacturer_country = models.CharField(max_length=255, verbose_name="Страна производителя") - manufacturer_code = models.CharField(max_length=50, verbose_name="Код производителя", blank=True, null=True) - dimensions = models.CharField(max_length=255, verbose_name="Размеры", blank=True, null=True) - unit_of_measure = models.CharField(max_length=50, verbose_name="Единица измерения") - shelf_life_days = models.IntegerField(verbose_name="Срок годности (дни)") - barcode = models.CharField(max_length=50, unique=True, verbose_name="Штрихкод") +from product_directory.models import Product - def __str__(self): - return self.name +# todo: нужно перенести уникальные поля в новую модель + +# Справочник товаров +# class Product(models.Model): +# name = models.CharField(max_length=255, verbose_name="Наименование товара") +# manufacturer_name = models.CharField(max_length=255, verbose_name="Производитель") +# manufacturer_country = models.CharField(max_length=255, verbose_name="Страна производителя") +# manufacturer_code = models.CharField(max_length=50, verbose_name="Код производителя", blank=True, null=True) +# dimensions = models.CharField(max_length=255, verbose_name="Размеры", blank=True, null=True) +# unit_of_measure = models.CharField(max_length=50, verbose_name="Единица измерения") +# shelf_life_days = models.IntegerField(verbose_name="Срок годности (дни)") +# barcode = models.CharField(max_length=50, unique=True, verbose_name="Штрихкод") +# +# def __str__(self): +# return self.name # Справочник должностей class Position(models.Model): diff --git a/logs.log b/logs.log new file mode 100644 index 0000000..7cec389 --- /dev/null +++ b/logs.log @@ -0,0 +1,2 @@ +Watching for file changes with StatReloader +Watching for file changes with StatReloader diff --git a/product_directory.log b/product_directory.log new file mode 100644 index 0000000..5e7d650 --- /dev/null +++ b/product_directory.log @@ -0,0 +1,4 @@ +Watching for file changes with StatReloader +/Users/darius/Documents/franchise_store/settings/base.py changed, reloading. +Watching for file changes with StatReloader +/Users/darius/Documents/franchise_store/settings/base.py changed, reloading. diff --git a/product_directory/__init__.py b/product_directory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/product_directory/admin.py b/product_directory/admin.py new file mode 100644 index 0000000..137462c --- /dev/null +++ b/product_directory/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import Product + +@admin.register(Product) +class ProductAdmin(admin.ModelAdmin): + list_display = ('name', 'barcode', 'category', 'shelf_life_days', 'promotion') + search_fields = ('name', 'barcode') diff --git a/product_directory/api.py b/product_directory/api.py new file mode 100644 index 0000000..e92f206 --- /dev/null +++ b/product_directory/api.py @@ -0,0 +1,7 @@ +from rest_framework.viewsets import ModelViewSet +from .models import Product +from .serializers import ProductSerializer + +class ProductViewSet(ModelViewSet): + queryset = Product.objects.all() + serializer_class = ProductSerializer \ No newline at end of file diff --git a/product_directory/apps.py b/product_directory/apps.py new file mode 100644 index 0000000..6c478b2 --- /dev/null +++ b/product_directory/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ProductDirectoryConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'product_directory' diff --git a/product_directory/forms.py b/product_directory/forms.py new file mode 100644 index 0000000..1242276 --- /dev/null +++ b/product_directory/forms.py @@ -0,0 +1,15 @@ +from django import forms +from .models import Product + +class ProductForm(forms.ModelForm): + class Meta: + model = Product + fields = '__all__' + + def clean_shelf_life_days(self): + shelf_life_days = self.cleaned_data.get('shelf_life_days') + if shelf_life_days <= 0: + raise forms.ValidationError("Срок годности должен быть положительным числом.") + if shelf_life_days > 3650: # 10 лет + raise forms.ValidationError("Срок годности превышает допустимый предел.") + return shelf_life_days \ No newline at end of file diff --git a/product_directory/migrations/0001_initial.py b/product_directory/migrations/0001_initial.py new file mode 100644 index 0000000..7cb2c54 --- /dev/null +++ b/product_directory/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 5.1.4 on 2025-01-08 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Наименование товара')), + ('barcode', models.CharField(max_length=50, unique=True, verbose_name='Штрих-код')), + ('shelf_life_days', models.PositiveIntegerField(verbose_name='Срок годности (дни)')), + ('dimensions', models.CharField(max_length=255, verbose_name='Размеры')), + ('unit_of_measure', models.CharField(max_length=50, verbose_name='Единицы измерения')), + ('manufacturer', models.CharField(max_length=255, verbose_name='Производитель')), + ('category', models.CharField(choices=[('Food', 'Продукты'), ('Electronics', 'Электроника'), ('Clothing', 'Одежда'), ('Other', 'Другое')], max_length=50, verbose_name='Категория')), + ('storage_temperature', models.CharField(max_length=50, verbose_name='Температурные условия хранения')), + ('promotion', models.CharField(blank=True, choices=[('1+1', '1+1'), ('2+1', '2+1')], max_length=10, null=True, verbose_name='Акция')), + ], + ), + ] diff --git a/product_directory/migrations/__init__.py b/product_directory/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/product_directory/models.py b/product_directory/models.py new file mode 100644 index 0000000..4882d40 --- /dev/null +++ b/product_directory/models.py @@ -0,0 +1,26 @@ +from django.db import models + + +class Product(models.Model): + CATEGORY_CHOICES = [ + ('Food', 'Продукты'), + ('Electronics', 'Электроника'), + ('Clothing', 'Одежда'), + ('Other', 'Другое'), + ] + PROMOTION_CHOICES = [ + ('1+1', '1+1'), + ('2+1', '2+1'), + ] + name = models.CharField(max_length=255, verbose_name="Наименование товара") + barcode = models.CharField(max_length=50, unique=True, verbose_name="Штрих-код") + shelf_life_days = models.PositiveIntegerField(verbose_name="Срок годности (дни)") + dimensions = models.CharField(max_length=255, verbose_name="Размеры") + unit_of_measure = models.CharField(max_length=50, verbose_name="Единицы измерения") + manufacturer = models.CharField(max_length=255, verbose_name="Производитель") + category = models.CharField(max_length=50, choices=CATEGORY_CHOICES, verbose_name="Категория") + storage_temperature = models.CharField(max_length=50, verbose_name="Температурные условия хранения") + promotion = models.CharField(max_length=10, choices=PROMOTION_CHOICES, verbose_name="Акция", blank=True, null=True) + + def __str__(self): + return self.name \ No newline at end of file diff --git a/product_directory/serializers.py b/product_directory/serializers.py new file mode 100644 index 0000000..c4c09f1 --- /dev/null +++ b/product_directory/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import Product + +class ProductSerializer(serializers.ModelSerializer): + class Meta: + model = Product + fields = '__all__' \ No newline at end of file diff --git a/product_directory/templates/product_directory/product_list.html b/product_directory/templates/product_directory/product_list.html new file mode 100644 index 0000000..12b8a38 --- /dev/null +++ b/product_directory/templates/product_directory/product_list.html @@ -0,0 +1,13 @@ +{% for product in products %} + + {{ product.name }} + {{ product.barcode }} + {{ product.category }} + {{ product.shelf_life_days }} + {{ product.promotion }} + + Изменить + Удалить + + +{% endfor %} \ No newline at end of file diff --git a/product_directory/tests.py b/product_directory/tests.py new file mode 100644 index 0000000..28d3acb --- /dev/null +++ b/product_directory/tests.py @@ -0,0 +1,18 @@ +from django.test import TestCase +from .models import Product + +class ProductTestCase(TestCase): + def test_product_creation(self): + product = Product.objects.create( + name="Тестовый товар", + barcode="1234567890123", + shelf_life_days=365, + dimensions="10x10x10", + unit_of_measure="шт.", + manufacturer="Производитель", + category="Food", + storage_temperature="+4C", + promotion="1+1" + ) + self.assertEqual(Product.objects.count(), 1) + self.assertEqual(product.name, "Тестовый товар") \ No newline at end of file diff --git a/product_directory/views.py b/product_directory/views.py new file mode 100644 index 0000000..828d22c --- /dev/null +++ b/product_directory/views.py @@ -0,0 +1,42 @@ +import logging # Импортируем библиотеку для логирования +from django.shortcuts import render, get_object_or_404, redirect +from .models import Product +from .forms import ProductForm + +# Настраиваем логгер +logger = logging.getLogger(__name__) + +def product_list(request): + products = Product.objects.all() + return render(request, 'product_directory/product_list.html', {'products': products}) + +def product_create(request): + if request.method == 'POST': + form = ProductForm(request.POST) + if form.is_valid(): + product = form.save() + logger.info(f"Пользователь {request.user} создал товар '{product.name}'") + return redirect('product_list') + else: + form = ProductForm() + return render(request, 'product_directory/product_form.html', {'form': form}) + +def product_update(request, pk): + product = get_object_or_404(Product, pk=pk) + if request.method == 'POST': + form = ProductForm(request.POST, instance=product) + if form.is_valid(): + product = form.save() + logger.info(f"Пользователь {request.user} обновил товар '{product.name}'") + return redirect('product_list') + else: + form = ProductForm(instance=product) + return render(request, 'product_directory/product_form.html', {'form': form}) + +def product_delete(request, pk): + product = get_object_or_404(Product, pk=pk) + if request.method == 'POST': + logger.warning(f"Пользователь {request.user} удалил товар '{product.name}'") + product.delete() + return redirect('product_list') + return render(request, 'product_directory/product_confirm_delete.html', {'product': product}) diff --git a/settings/base.py b/settings/base.py index fe83006..f9e4780 100644 --- a/settings/base.py +++ b/settings/base.py @@ -56,6 +56,31 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'rest_framework', 'inventory', + 'product_directory', ] ROOT_URLCONF = 'urls' + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'INFO', + 'class': 'logging.FileHandler', + 'filename': 'logs.log', # путь к файлу для логов + }, + }, + 'loggers': { + 'django': { + 'handlers': ['file'], + 'level': 'INFO', + 'propagate': True, + }, + 'product_directory': { + 'handlers': ['file'], + 'level': 'INFO', + 'propagate': True, + }, + }, +}