Fix: Auto-cleanup temp files after photo processing
- Added temp file deletion in Celery task after successful processing - Added temp file cleanup in sync fallback method - Added temp file removal in delete() if processing never completed - Prevents accumulation of orphaned files in media/<entity>/temp/ folders
This commit is contained in:
@@ -193,6 +193,11 @@ class BaseProductEntity(models.Model):
|
|||||||
"""Физическое удаление из БД (необратимо! только для старых товаров)"""
|
"""Физическое удаление из БД (необратимо! только для старых товаров)"""
|
||||||
super().delete()
|
super().delete()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_active(self):
|
||||||
|
"""Возвращает True если товар активен"""
|
||||||
|
return self.status == 'active'
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Автогенерация slug из name если не задан.
|
Автогенерация slug из name если не задан.
|
||||||
|
|||||||
@@ -124,10 +124,14 @@ class BasePhoto(models.Model):
|
|||||||
Используется только если Celery недоступен.
|
Используется только если Celery недоступен.
|
||||||
"""
|
"""
|
||||||
from ..utils.image_processor import ImageProcessor
|
from ..utils.image_processor import ImageProcessor
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
|
|
||||||
entity = self.get_entity()
|
entity = self.get_entity()
|
||||||
entity_type = self.get_entity_type()
|
entity_type = self.get_entity_type()
|
||||||
|
|
||||||
|
# Сохраняем путь к временному файлу до перезаписи поля image
|
||||||
|
temp_path = getattr(temp_image, 'name', None)
|
||||||
|
|
||||||
processed_paths = ImageProcessor.process_image(
|
processed_paths = ImageProcessor.process_image(
|
||||||
temp_image,
|
temp_image,
|
||||||
entity_type,
|
entity_type,
|
||||||
@@ -141,10 +145,18 @@ class BasePhoto(models.Model):
|
|||||||
|
|
||||||
super().save(update_fields=['image', 'quality_level', 'quality_warning'])
|
super().save(update_fields=['image', 'quality_level', 'quality_warning'])
|
||||||
|
|
||||||
|
# Удаляем временный файл из temp после успешной обработки
|
||||||
|
try:
|
||||||
|
if temp_path and default_storage.exists(temp_path):
|
||||||
|
default_storage.delete(temp_path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
"""Удаляет все версии изображения при удалении фото"""
|
"""Удаляет все версии изображения при удалении фото"""
|
||||||
import logging
|
import logging
|
||||||
from ..utils.image_processor import ImageProcessor
|
from ..utils.image_processor import ImageProcessor
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -159,6 +171,15 @@ class BasePhoto(models.Model):
|
|||||||
entity_id=entity.id,
|
entity_id=entity.id,
|
||||||
photo_id=self.id
|
photo_id=self.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Если фото так и осталось во временном пути (обработка не завершилась) — удаляем temp файл
|
||||||
|
try:
|
||||||
|
if '/temp/' in self.image.name and default_storage.exists(self.image.name):
|
||||||
|
default_storage.delete(self.image.name)
|
||||||
|
logger.info(f"[{self.__class__.__name__}.delete] Deleted temp file: {self.image.name}")
|
||||||
|
except Exception as del_exc:
|
||||||
|
logger.warning(f"[{self.__class__.__name__}.delete] Could not delete temp file {self.image.name}: {del_exc}")
|
||||||
|
|
||||||
logger.info(f"[{self.__class__.__name__}.delete] ✓ Все версии изображения удалены")
|
logger.info(f"[{self.__class__.__name__}.delete] ✓ Все версии изображения удалены")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import logging
|
|||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -53,6 +54,9 @@ def process_product_photo_async(self, photo_id, photo_model_class, schema_name):
|
|||||||
logger.warning(f"[Celery] Photo {photo_id} has no image file")
|
logger.warning(f"[Celery] Photo {photo_id} has no image file")
|
||||||
return {'status': 'error', 'reason': 'no_image'}
|
return {'status': 'error', 'reason': 'no_image'}
|
||||||
|
|
||||||
|
# Сохраняем путь к временному файлу до перезаписи поля image
|
||||||
|
temp_path = photo_obj.image.name
|
||||||
|
|
||||||
# Получаем entity type для правильного пути сохранения
|
# Получаем entity type для правильного пути сохранения
|
||||||
entity_type = photo_obj.get_entity_type()
|
entity_type = photo_obj.get_entity_type()
|
||||||
|
|
||||||
@@ -73,6 +77,14 @@ def process_product_photo_async(self, photo_id, photo_model_class, schema_name):
|
|||||||
photo_obj.quality_warning = processed_paths.get('quality_warning', False)
|
photo_obj.quality_warning = processed_paths.get('quality_warning', False)
|
||||||
photo_obj.save(update_fields=['image', 'quality_level', 'quality_warning'])
|
photo_obj.save(update_fields=['image', 'quality_level', 'quality_warning'])
|
||||||
|
|
||||||
|
# Удаляем временный файл из temp после успешной обработки
|
||||||
|
try:
|
||||||
|
if temp_path and default_storage.exists(temp_path):
|
||||||
|
default_storage.delete(temp_path)
|
||||||
|
logger.info(f"[Celery] Deleted temp file: {temp_path}")
|
||||||
|
except Exception as del_exc:
|
||||||
|
logger.warning(f"[Celery] Could not delete temp file {temp_path}: {del_exc}")
|
||||||
|
|
||||||
logger.info(f"[Celery] ✓ Photo {photo_id} processed successfully "
|
logger.info(f"[Celery] ✓ Photo {photo_id} processed successfully "
|
||||||
f"(quality: {processed_paths.get('quality_level')})")
|
f"(quality: {processed_paths.get('quality_level')})")
|
||||||
|
|
||||||
|
|||||||
@@ -72,10 +72,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if product.is_active %}
|
{% if product.status == 'active' %}
|
||||||
<span class="badge bg-info">Активен</span>
|
<span class="badge bg-success">Активный</span>
|
||||||
|
{% elif product.status == 'archived' %}
|
||||||
|
<span class="badge bg-warning text-dark">Архивный</span>
|
||||||
|
{% elif product.status == 'discontinued' %}
|
||||||
|
<span class="badge bg-danger">Снят</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">Неактивен</span>
|
<span class="badge bg-secondary">{{ product.status }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -69,10 +69,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if kit.is_active %}
|
{% if kit.status == 'active' %}
|
||||||
<span class="badge bg-info">Активен</span>
|
<span class="badge bg-success">Активный</span>
|
||||||
|
{% elif kit.status == 'archived' %}
|
||||||
|
<span class="badge bg-warning text-dark">Архивный</span>
|
||||||
|
{% elif kit.status == 'discontinued' %}
|
||||||
|
<span class="badge bg-danger">Снят</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">Неактивен</span>
|
<span class="badge bg-secondary">{{ kit.status }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
189
myproject/products/templates/products/products_unified_list.html
Normal file
189
myproject/products/templates/products/products_unified_list.html
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load quality_tags %}
|
||||||
|
|
||||||
|
{% block title %}Список товаров и комплектов{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<h2 class="mb-4">Товары и Комплекты</h2>
|
||||||
|
|
||||||
|
<!-- Панель фильтрации и действий -->
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
|
||||||
|
<h5 class="card-title mb-0 me-3">
|
||||||
|
<i class="bi bi-funnel-fill"></i> Фильтры
|
||||||
|
</h5>
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
{% if perms.products.add_product %}
|
||||||
|
<a href="{% url 'products:product-create' %}" class="btn btn-primary btn-sm me-2 mb-2 mb-md-0">
|
||||||
|
<i class="bi bi-plus-circle"></i> Создать товар
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.products.add_productkit %}
|
||||||
|
<a href="{% url 'products:productkit-create' %}" class="btn btn-outline-primary btn-sm mb-2 mb-md-0">
|
||||||
|
<i class="bi bi-box-seam"></i> Создать комплект
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-3">
|
||||||
|
|
||||||
|
<form method="get" id="filterForm">
|
||||||
|
<div class="row g-3">
|
||||||
|
<!-- Поиск -->
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="search" class="form-label"><i class="bi bi-search"></i> Поиск</label>
|
||||||
|
<input type="text" class="form-control" id="search" name="search" placeholder="Название, артикул, описание..." value="{{ filters.current.search|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Тип -->
|
||||||
|
<div class="col-12 col-md-2">
|
||||||
|
<label for="type" class="form-label"><i class="bi bi-box-seam"></i> Тип</label>
|
||||||
|
<select class="form-select" id="type" name="type">
|
||||||
|
<option value="all" {% if filters.current.type == 'all' %}selected{% endif %}>Все</option>
|
||||||
|
<option value="products" {% if filters.current.type == 'products' %}selected{% endif %}>Только товары</option>
|
||||||
|
<option value="kits" {% if filters.current.type == 'kits' %}selected{% endif %}>Только комплекты</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Категория -->
|
||||||
|
<div class="col-12 col-md-3">
|
||||||
|
<label for="category" class="form-label"><i class="bi bi-bookmark"></i> Категория</label>
|
||||||
|
<select class="form-select" id="category" name="category">
|
||||||
|
<option value="">Все категории</option>
|
||||||
|
{% for category in filters.categories %}
|
||||||
|
<option value="{{ category.id }}" {% if filters.current.category == category.id|stringformat:"s" %}selected{% endif %}>{{ category.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Статус -->
|
||||||
|
<div class="col-12 col-md-3">
|
||||||
|
<label for="status" class="form-label"><i class="bi bi-toggle-on"></i> Статус</label>
|
||||||
|
<select class="form-select" id="status" name="status">
|
||||||
|
<option value="">Все статусы</option>
|
||||||
|
{% for status_value, status_name in item_statuses %}
|
||||||
|
<option value="{{ status_value }}" {% if filters.current.status == status_value %}selected{% endif %}>{{ status_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-3 mt-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="bi bi-check-circle"></i> Применить</button>
|
||||||
|
<a href="{% url 'products:product-list' %}" class="btn btn-outline-secondary"><i class="bi bi-x-circle"></i> Сброс</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if items %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Фото</th>
|
||||||
|
<th>Название</th>
|
||||||
|
<th>Артикул</th>
|
||||||
|
<th>Тип</th>
|
||||||
|
<th>Категория</th>
|
||||||
|
<th>Цена</th>
|
||||||
|
<th>В наличии</th>
|
||||||
|
<th>Компоненты</th>
|
||||||
|
<th>Статус</th>
|
||||||
|
<th>Действия</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in items %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% with photo=item.photos.first %}
|
||||||
|
<div class="photo-list-item">
|
||||||
|
<img src="{{ photo.get_thumbnail_url }}" alt="{{ item.name }}" class="img-thumbnail rounded">
|
||||||
|
{% if item.item_type == 'product' and photo %}
|
||||||
|
<span class="quality-icon" title="{{ photo.get_quality_level_display }}">{{ photo|quality_icon_only }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ item.get_absolute_url }}">{{ item.name }}</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.sku }}</td>
|
||||||
|
<td>
|
||||||
|
{% if item.item_type == 'product' %}
|
||||||
|
<span class="badge bg-success" title="Товар"><i class="bi bi-box"></i> Товар</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-info" title="Комплект"><i class="bi bi-box-seam"></i> Комплект</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for category in item.categories.all %}
|
||||||
|
<span class="badge bg-secondary">{{ category.name }}</span>
|
||||||
|
{% empty %}
|
||||||
|
-
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.sale_price %}
|
||||||
|
<span class="text-decoration-line-through text-muted small">{{ item.price|floatformat:2 }} руб.</span><br>
|
||||||
|
<strong class="text-danger">{{ item.sale_price|floatformat:2 }} руб.</strong>
|
||||||
|
{% else %}
|
||||||
|
<strong>{{ item.price|floatformat:2 }} руб.</strong>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.item_type == 'product' %}
|
||||||
|
{% if item.in_stock %}
|
||||||
|
<span class="badge bg-success"><i class="bi bi-check-circle"></i> Да</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-danger"><i class="bi bi-x-circle"></i> Нет</span>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.item_type == 'kit' %}
|
||||||
|
<span class="badge bg-secondary">{{ item.get_total_components_count }} шт</span>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge" style="background-color: {{ item.get_status_color }}">{{ item.get_status_display }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<a href="{{ item.get_absolute_url }}" class="btn btn-outline-info" title="Просмотр"><i class="bi bi-eye"></i></a>
|
||||||
|
{% if item.item_type == 'product' and perms.products.change_product %}
|
||||||
|
<a href="{% url 'products:product-update' item.pk %}" class="btn btn-outline-primary" title="Изменить"><i class="bi bi-pencil"></i></a>
|
||||||
|
{% elif item.item_type == 'kit' and perms.products.change_productkit %}
|
||||||
|
<a href="{% url 'products:productkit-update' item.pk %}" class="btn btn-outline-primary" title="Изменить"><i class="bi bi-pencil"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if item.item_type == 'product' and perms.products.delete_product %}
|
||||||
|
<a href="{% url 'products:product-delete' item.pk %}" class="btn btn-outline-danger" title="Удалить"><i class="bi bi-trash"></i></a>
|
||||||
|
{% elif item.item_type == 'kit' and perms.products.delete_productkit %}
|
||||||
|
<a href="{% url 'products:productkit-delete' item.pk %}" class="btn btn-outline-danger" title="Удалить"><i class="bi bi-trash"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'components/pagination.html' %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<p>Товары или комплекты не найдены.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -43,10 +43,17 @@ class ProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
if category_id:
|
if category_id:
|
||||||
queryset = queryset.filter(categories__id=category_id)
|
queryset = queryset.filter(categories__id=category_id)
|
||||||
|
|
||||||
# Фильтр по статусу
|
# Фильтр по статусу (новая система)
|
||||||
status_filter = self.request.GET.get('status')
|
status_filter = self.request.GET.get('status')
|
||||||
if status_filter:
|
if status_filter:
|
||||||
queryset = queryset.filter(status=status_filter)
|
queryset = queryset.filter(status=status_filter)
|
||||||
|
else:
|
||||||
|
# Фильтр по is_active для обратной совместимости (старая система)
|
||||||
|
is_active_filter = self.request.GET.get('is_active')
|
||||||
|
if is_active_filter == '1':
|
||||||
|
queryset = queryset.filter(status='active')
|
||||||
|
elif is_active_filter == '0':
|
||||||
|
queryset = queryset.filter(status__in=['archived', 'discontinued'])
|
||||||
|
|
||||||
# Фильтр по тегам
|
# Фильтр по тегам
|
||||||
tags = self.request.GET.getlist('tags')
|
tags = self.request.GET.getlist('tags')
|
||||||
@@ -250,6 +257,7 @@ class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListV
|
|||||||
search_query = self.request.GET.get('search')
|
search_query = self.request.GET.get('search')
|
||||||
category_id = self.request.GET.get('category')
|
category_id = self.request.GET.get('category')
|
||||||
status_filter = self.request.GET.get('status')
|
status_filter = self.request.GET.get('status')
|
||||||
|
is_active_filter = self.request.GET.get('is_active')
|
||||||
|
|
||||||
# Фильтрация по поиску
|
# Фильтрация по поиску
|
||||||
if search_query:
|
if search_query:
|
||||||
@@ -273,10 +281,18 @@ class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListV
|
|||||||
products = products.filter(categories__id=category_id)
|
products = products.filter(categories__id=category_id)
|
||||||
kits = kits.filter(categories__id=category_id)
|
kits = kits.filter(categories__id=category_id)
|
||||||
|
|
||||||
# Фильтрация по статусу
|
# Фильтрация по статусу (новая система)
|
||||||
if status_filter:
|
if status_filter:
|
||||||
products = products.filter(status=status_filter)
|
products = products.filter(status=status_filter)
|
||||||
kits = kits.filter(status=status_filter)
|
kits = kits.filter(status=status_filter)
|
||||||
|
else:
|
||||||
|
# Фильтрация по is_active для обратной совместимости (старая система)
|
||||||
|
if is_active_filter == '1':
|
||||||
|
products = products.filter(status='active')
|
||||||
|
kits = kits.filter(status='active')
|
||||||
|
elif is_active_filter == '0':
|
||||||
|
products = products.filter(status__in=['archived', 'discontinued'])
|
||||||
|
kits = kits.filter(status__in=['archived', 'discontinued'])
|
||||||
|
|
||||||
# Добавляем type для различения в шаблоне
|
# Добавляем type для различения в шаблоне
|
||||||
products_list = list(products.order_by('-created_at'))
|
products_list = list(products.order_by('-created_at'))
|
||||||
|
|||||||
@@ -37,12 +37,17 @@ class ProductKitListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
if category_id:
|
if category_id:
|
||||||
queryset = queryset.filter(categories__id=category_id)
|
queryset = queryset.filter(categories__id=category_id)
|
||||||
|
|
||||||
# Фильтр по статусу
|
# Фильтр по статусу (новая система)
|
||||||
is_active = self.request.GET.get('is_active')
|
status_filter = self.request.GET.get('status')
|
||||||
if is_active == '1':
|
if status_filter:
|
||||||
queryset = queryset.filter(is_active=True)
|
queryset = queryset.filter(status=status_filter)
|
||||||
elif is_active == '0':
|
else:
|
||||||
queryset = queryset.filter(is_active=False)
|
# Фильтр по is_active для обратной совместимости (старая система)
|
||||||
|
is_active = self.request.GET.get('is_active')
|
||||||
|
if is_active == '1':
|
||||||
|
queryset = queryset.filter(status='active')
|
||||||
|
elif is_active == '0':
|
||||||
|
queryset = queryset.filter(status__in=['archived', 'discontinued'])
|
||||||
|
|
||||||
return queryset.order_by('-created_at')
|
return queryset.order_by('-created_at')
|
||||||
|
|
||||||
|
|||||||
@@ -88,13 +88,14 @@
|
|||||||
<!-- Фильтр по статусу -->
|
<!-- Фильтр по статусу -->
|
||||||
{% if show_status|default:True %}
|
{% if show_status|default:True %}
|
||||||
<div class="col-12 col-md-2">
|
<div class="col-12 col-md-2">
|
||||||
<label for="is_active" class="form-label">
|
<label for="status" class="form-label">
|
||||||
<i class="bi bi-toggle-on"></i> Статус
|
<i class="bi bi-toggle-on"></i> Статус
|
||||||
</label>
|
</label>
|
||||||
<select class="form-select" id="is_active" name="is_active">
|
<select class="form-select" id="status" name="status">
|
||||||
<option value="">Все</option>
|
<option value="">Все</option>
|
||||||
<option value="1" {% if filters.current.is_active == '1' %}selected{% endif %}>Активные</option>
|
<option value="active" {% if filters.current.status == 'active' %}selected{% endif %}>Активный</option>
|
||||||
<option value="0" {% if filters.current.is_active == '0' %}selected{% endif %}>Неактивные</option>
|
<option value="archived" {% if filters.current.status == 'archived' %}selected{% endif %}>Архивный</option>
|
||||||
|
<option value="discontinued" {% if filters.current.status == 'discontinued' %}selected{% endif %}>Снят</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user