Files
octopus/myproject/products/urls.py
Andrey Smakotin 0f19542ac9 Добавлен асинхронный импорт товаров с параллельной загрузкой фото + исправлен баг со счётчиком SKU
- Реализован импорт Product из CSV/XLSX через Celery с прогресс-баром
- Параллельная загрузка фото товаров с внешних URL (масштабируемость до 500+ товаров)
- Добавлена модель ProductImportJob для отслеживания статуса импорта
- Создан таск download_product_photo_async для загрузки фото в фоне
- Интеграция с существующим ImageProcessor (синхронная обработка через use_async=False)
- Добавлены view и template для импорта с real-time обновлением через AJAX

FIX: Исправлен баг со счётчиком SKU - инкремент только после успешного сохранения
- Добавлен SKUCounter.peek_next_value() - возвращает следующий номер БЕЗ инкремента
- Добавлен SKUCounter.increment_counter() - инкрементирует счётчик
- generate_product_sku() использует peek_next_value() вместо get_next_value()
- Добавлен post_save сигнал increment_sku_counter_after_save() для инкремента после создания
- Предотвращает пропуски номеров при ошибках валидации (например cost_price NULL)

FIX: Исправлена ошибка с is_main в ProductPhoto
- ProductPhoto не имеет поля is_main, используется только order
- Первое фото (order=0) автоматически считается главным
- Удалён параметр is_main из download_product_photo_async и _collect_photo_tasks

Изменены файлы:
- products/models/base.py - методы для управления счётчиком SKU
- products/models/import_job.py - модель для отслеживания импорта
- products/services/import_export.py - сервис импорта с поддержкой Celery
- products/tasks.py - таски для асинхронного импорта и загрузки фото
- products/signals.py - сигнал для инкремента счётчика после сохранения
- products/utils/sku_generator.py - использование peek_next_value()
- products/views/product_import_views.py - view для импорта
- products/templates/products/product_import*.html - UI для импорта
- docker/entrypoint.sh - настройка Celery worker (concurrency=4)
- requirements.txt - добавлен requests для загрузки фото
2026-01-06 07:10:12 +03:00

127 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.urls import path
from . import views
from .views import api_views
from .views import photo_status_api
app_name = 'products'
urlpatterns = [
# Main unified list for products and kits (default view)
path('', views.CombinedProductListView.as_view(), name='products-list'),
# Каталог с drag-n-drop
path('catalog/', views.CatalogView.as_view(), name='catalog'),
# Legacy URLs for backward compatibility
path('all/', views.CombinedProductListView.as_view(), name='all-products'),
path('products/', views.ProductListView.as_view(), name='product-list-legacy'),
path('kits/', views.ProductKitListView.as_view(), name='productkit-list'),
# CRUD URLs for Product
path('product/create/', views.ProductCreateView.as_view(), name='product-create'),
path('product/<int:pk>/', views.ProductDetailView.as_view(), name='product-detail'),
path('product/<int:pk>/update/', views.ProductUpdateView.as_view(), name='product-update'),
path('product/<int:pk>/delete/', views.ProductDeleteView.as_view(), name='product-delete'),
# Import/Export
path('import/', views.product_import_view, name='product-import'),
path('import/status/<int:job_id>/', views.product_import_status_view, name='product-import-status'),
path('import/status/<int:job_id>/api/', views.product_import_status_api, name='product-import-status-api'),
path('import/errors/download/', views.download_import_errors, name='product-import-errors-download'),
# Photo management for Product
path('product/photo/<int:pk>/delete/', views.product_photo_delete, name='product-photo-delete'),
path('product/photo/<int:pk>/set-main/', views.product_photo_set_main, name='product-photo-set-main'),
path('product/photo/<int:pk>/move-up/', views.product_photo_move_up, name='product-photo-move-up'),
path('product/photo/<int:pk>/move-down/', views.product_photo_move_down, name='product-photo-move-down'),
path('product/photos/delete-bulk/', views.product_photos_delete_bulk, name='product-photos-delete-bulk'),
# CRUD URLs for ProductKit (комплекты/букеты)
path('kit/create/', views.ProductKitCreateView.as_view(), name='productkit-create'),
path('kit/<int:pk>/', views.ProductKitDetailView.as_view(), name='productkit-detail'),
path('kit/<int:pk>/update/', views.ProductKitUpdateView.as_view(), name='productkit-update'),
path('kit/<int:pk>/delete/', views.ProductKitDeleteView.as_view(), name='productkit-delete'),
path('kit/<int:pk>/make-permanent/', views.ProductKitMakePermanentView.as_view(), name='productkit-make-permanent'),
# Photo management for ProductKit
path('kit/photo/<int:pk>/delete/', views.productkit_photo_delete, name='productkit-photo-delete'),
path('kit/photo/<int:pk>/set-main/', views.productkit_photo_set_main, name='productkit-photo-set-main'),
path('kit/photo/<int:pk>/move-up/', views.productkit_photo_move_up, name='productkit-photo-move-up'),
path('kit/photo/<int:pk>/move-down/', views.productkit_photo_move_down, name='productkit-photo-move-down'),
# API endpoints
path('api/search-products-variants/', views.search_products_and_variants, name='api-search-products-variants'),
path('api/products/<int:product_id>/sales-units/', api_views.get_product_sales_units_api, name='api-product-sales-units'),
path('api/kits/temporary/create/', views.create_temporary_kit_api, name='api-temporary-kit-create'),
path('api/tags/create/', api_views.create_tag_api, name='api-tag-create'),
path('api/tags/<int:pk>/toggle/', api_views.toggle_tag_status_api, name='api-tag-toggle'),
path('api/categories/create/', api_views.create_category_api, name='api-category-create'),
path('api/categories/<int:pk>/rename/', api_views.rename_category_api, name='api-category-rename'),
path('api/products/<int:pk>/update-price/', api_views.update_product_price_api, name='api-update-product-price'),
path('api/payment-methods/', api_views.get_payment_methods, name='api-payment-methods'),
# Photo processing status API (for AJAX polling)
path('api/photos/status/<str:task_id>/', photo_status_api.photo_processing_status, name='api-photo-status'),
path('api/photos/batch-status/', photo_status_api.batch_photo_status, name='api-batch-photo-status'),
# CRUD URLs for ProductVariantGroup (Варианты товаров)
path('variant-groups/', views.ProductVariantGroupListView.as_view(), name='variantgroup-list'),
path('variant-groups/create/', views.ProductVariantGroupCreateView.as_view(), name='variantgroup-create'),
path('variant-groups/<int:pk>/', views.ProductVariantGroupDetailView.as_view(), name='variantgroup-detail'),
path('variant-groups/<int:pk>/update/', views.ProductVariantGroupUpdateView.as_view(), name='variantgroup-update'),
path('variant-groups/<int:pk>/delete/', views.ProductVariantGroupDeleteView.as_view(), name='variantgroup-delete'),
# AJAX endpoints for ProductVariantGroup item management
path('variant-groups/item/<int:item_id>/move/<str:direction>/', views.product_variant_group_item_move, name='variantgroup-item-move'),
# CRUD URLs for ProductCategory
path('categories/', views.ProductCategoryListView.as_view(), name='category-list'),
path('categories/create/', views.ProductCategoryCreateView.as_view(), name='category-create'),
path('categories/<int:pk>/', views.ProductCategoryDetailView.as_view(), name='category-detail'),
path('categories/<int:pk>/update/', views.ProductCategoryUpdateView.as_view(), name='category-update'),
path('categories/<int:pk>/delete/', views.ProductCategoryDeleteView.as_view(), name='category-delete'),
# Category photo management
path('categories/photo/<int:pk>/delete/', views.category_photo_delete, name='category-photo-delete'),
path('categories/photo/<int:pk>/set-main/', views.category_photo_set_main, name='category-photo-set-main'),
path('categories/photo/<int:pk>/move-up/', views.category_photo_move_up, name='category-photo-move-up'),
path('categories/photo/<int:pk>/move-down/', views.category_photo_move_down, name='category-photo-move-down'),
# CRUD URLs for ProductTag
path('tags/', views.ProductTagListView.as_view(), name='tag-list'),
path('tags/create/', views.ProductTagCreateView.as_view(), name='tag-create'),
path('tags/<int:pk>/', views.ProductTagDetailView.as_view(), name='tag-detail'),
path('tags/<int:pk>/update/', views.ProductTagUpdateView.as_view(), name='tag-update'),
path('tags/<int:pk>/delete/', views.ProductTagDeleteView.as_view(), name='tag-delete'),
# CRUD URLs for ProductAttribute (справочник атрибутов)
path('attributes/', views.ProductAttributeListView.as_view(), name='attribute-list'),
path('attributes/create/', views.ProductAttributeCreateView.as_view(), name='attribute-create'),
path('attributes/<int:pk>/', views.ProductAttributeDetailView.as_view(), name='attribute-detail'),
path('attributes/<int:pk>/update/', views.ProductAttributeUpdateView.as_view(), name='attribute-update'),
path('attributes/<int:pk>/delete/', views.ProductAttributeDeleteView.as_view(), name='attribute-delete'),
# API для атрибутов
path('api/attributes/', views.get_attributes_list_api, name='api-attributes-list'),
path('api/attributes/create/', views.create_attribute_api, name='api-attribute-create'),
path('api/attributes/<int:pk>/values/add/', views.add_attribute_value_api, name='attribute-add-value'),
path('api/attributes/<int:pk>/values/<int:value_id>/delete/', views.delete_attribute_value_api, name='attribute-delete-value'),
# CRUD URLs for ConfigurableProduct
path('configurable/', views.ConfigurableProductListView.as_view(), name='configurableproduct-list'),
path('configurable/create/', views.ConfigurableProductCreateView.as_view(), name='configurableproduct-create'),
path('configurable/<int:pk>/', views.ConfigurableProductDetailView.as_view(), name='configurableproduct-detail'),
path('configurable/<int:pk>/update/', views.ConfigurableProductUpdateView.as_view(), name='configurableproduct-update'),
path('configurable/<int:pk>/delete/', views.ConfigurableProductDeleteView.as_view(), name='configurableproduct-delete'),
# API для управления вариантами ConfigurableProduct
path('configurable/<int:pk>/options/add/', views.add_option_to_configurable, name='configurableproduct-add-option'),
path('configurable/<int:pk>/options/<int:option_id>/remove/', views.remove_option_from_configurable, name='configurableproduct-remove-option'),
path('configurable/<int:pk>/options/<int:option_id>/set-default/', views.set_option_as_default, name='configurableproduct-set-default-option'),
# Управление единицами измерения
path('units/', views.unit_of_measure_list, name='unit-list'),
path('sales-units/', views.product_sales_unit_list, name='sales-unit-list'),
path('sales-units/create/', views.product_sales_unit_create, name='sales-unit-create'),
path('sales-units/<int:pk>/edit/', views.product_sales_unit_update, name='sales-unit-update'),
path('sales-units/<int:pk>/delete/', views.product_sales_unit_delete, name='sales-unit-delete'),
]