Добавлен расчёт и отображение доступного количества комплектов
- Добавлен метод calculate_available_quantity() в модель ProductKit для точного расчёта максимального количества комплектов на основе свободных остатков компонентов - Обновлён метод check_availability() для использования нового расчёта (обратная совместимость) - Удалён устаревший сервис kit_availability.py Исправлено отображение остатков комплектов: - products_list.html: вместо прочерка показывается количество комплектов - catalog.html: добавлено отображение доступного количества комплектов с цветовой индикацией - POS terminal.js: в карточке товара показывается конкретное количество вместо общего 'В наличии' Обновлены представления: - ProductsListView: аннотирует комплекты атрибутом total_free - CatalogView: рассчитывает доступное количество для каждого комплекта - POS get_products(): убран хардкод, используется реальный расчёт по складу
This commit is contained in:
@@ -547,7 +547,8 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs):
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"❌ Ошибка финализации ShowcaseItem #{showcase_item.id}: {e}"
|
f"❌ Ошибка финализации ShowcaseItem #{showcase_item.id}: {e}",
|
||||||
|
exc_info=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if finalized_count > 0:
|
if finalized_count > 0:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from .models import Order, OrderItem, Address, OrderStatus, Transaction, Payment
|
|||||||
from .forms import OrderForm, OrderItemFormSet, OrderItemForm, OrderStatusForm, TransactionForm
|
from .forms import OrderForm, OrderItemFormSet, OrderItemForm, OrderStatusForm, TransactionForm
|
||||||
from .filters import OrderFilter
|
from .filters import OrderFilter
|
||||||
from .services.address_service import AddressService
|
from .services.address_service import AddressService
|
||||||
|
from inventory.models import Reservation
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
@@ -1067,6 +1068,7 @@ def create_order_from_pos(request):
|
|||||||
showcase_item_ids = item_data.get('showcase_item_ids', [])
|
showcase_item_ids = item_data.get('showcase_item_ids', [])
|
||||||
|
|
||||||
if not showcase_item_ids:
|
if not showcase_item_ids:
|
||||||
|
logger.warning(f"⚠️ Пустой список showcase_item_ids для комплекта {kit.name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Создаём OrderItem с флагом is_from_showcase
|
# Создаём OrderItem с флагом is_from_showcase
|
||||||
@@ -1086,8 +1088,29 @@ def create_order_from_pos(request):
|
|||||||
locked_by_user=request.user
|
locked_by_user=request.user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reserved_count = 0
|
||||||
|
component_count = 0
|
||||||
|
|
||||||
for showcase_item in showcase_items:
|
for showcase_item in showcase_items:
|
||||||
showcase_item.reserve_for_order(order_item)
|
showcase_item.reserve_for_order(order_item)
|
||||||
|
reserved_count += 1
|
||||||
|
|
||||||
|
# КРИТИЧНО: Привязываем существующие резервы компонентов к OrderItem
|
||||||
|
# Эти резервы были созданы при добавлении букета на витрину
|
||||||
|
component_reservations = Reservation.objects.filter(
|
||||||
|
showcase_item=showcase_item,
|
||||||
|
status='reserved'
|
||||||
|
)
|
||||||
|
|
||||||
|
for reservation in component_reservations:
|
||||||
|
reservation.order_item = order_item
|
||||||
|
reservation.save(update_fields=['order_item'])
|
||||||
|
component_count += 1
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"✓ Витринный комплект '{kit.name}': зарезервировано {reserved_count} экз., "
|
||||||
|
f"привязано {component_count} резервов компонентов к OrderItem #{order_item.id}"
|
||||||
|
)
|
||||||
|
|
||||||
# 5. Пересчитываем стоимость заказа
|
# 5. Пересчитываем стоимость заказа
|
||||||
order.calculate_total()
|
order.calculate_total()
|
||||||
|
|||||||
@@ -902,10 +902,22 @@ function renderProducts() {
|
|||||||
stock.style.color = '#28a745'; // Зелёный (достаточно)
|
stock.style.color = '#28a745'; // Зелёный (достаточно)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback для старых данных или комплектов
|
// Комплекты: показываем доступное количество
|
||||||
stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ';
|
if (item.type === 'kit' && item.free_qty !== undefined) {
|
||||||
if (!item.in_stock) {
|
const availableKits = parseFloat(item.free_qty) || 0;
|
||||||
stock.style.color = '#dc3545';
|
if (availableKits > 0) {
|
||||||
|
stock.textContent = `В наличии: ${Math.floor(availableKits)} компл.`;
|
||||||
|
stock.style.color = '#28a745'; // Зелёный
|
||||||
|
} else {
|
||||||
|
stock.textContent = 'Под заказ';
|
||||||
|
stock.style.color = '#dc3545'; // Красный
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback для старых данных
|
||||||
|
stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ';
|
||||||
|
if (!item.in_stock) {
|
||||||
|
stock.style.color = '#dc3545';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -854,17 +854,20 @@ def get_items_api(request):
|
|||||||
if not image_url:
|
if not image_url:
|
||||||
image_url = None
|
image_url = None
|
||||||
|
|
||||||
|
# Рассчитываем доступное количество комплектов на текущем складе
|
||||||
|
available_kits = k.calculate_available_quantity(warehouse=current_warehouse)
|
||||||
|
|
||||||
kits.append({
|
kits.append({
|
||||||
'id': k.id,
|
'id': k.id,
|
||||||
'name': k.name,
|
'name': k.name,
|
||||||
'price': str(k.actual_price),
|
'price': str(k.actual_price),
|
||||||
'category_ids': [c.id for c in k.categories.all()],
|
'category_ids': [c.id for c in k.categories.all()],
|
||||||
'in_stock': False, # Комплекты всегда "Под заказ"
|
'in_stock': available_kits > 0, # Доступен если можно собрать хоть один комплект
|
||||||
'sku': k.sku or '',
|
'sku': k.sku or '',
|
||||||
'image': image_url,
|
'image': image_url,
|
||||||
'type': 'kit',
|
'type': 'kit',
|
||||||
'free_qty': '0', # Строка для консистентности с товарами
|
'free_qty': str(available_kits), # Количество комплектов которые можно собрать
|
||||||
'free_qty_sort': 0 # Комплекты всегда внизу при сортировке
|
'free_qty_sort': float(available_kits) # Для сортировки
|
||||||
})
|
})
|
||||||
|
|
||||||
# Объединяем и сортируем по free_qty_sort DESC
|
# Объединяем и сортируем по free_qty_sort DESC
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from .categories import ProductCategory, ProductTag
|
|||||||
from .variants import ProductVariantGroup
|
from .variants import ProductVariantGroup
|
||||||
from .products import Product
|
from .products import Product
|
||||||
from ..utils.sku_generator import generate_kit_sku
|
from ..utils.sku_generator import generate_kit_sku
|
||||||
from ..services.kit_availability import KitAvailabilityChecker
|
|
||||||
|
|
||||||
|
|
||||||
class ProductKit(BaseProductEntity):
|
class ProductKit(BaseProductEntity):
|
||||||
@@ -225,10 +224,69 @@ class ProductKit(BaseProductEntity):
|
|||||||
|
|
||||||
def check_availability(self, stock_manager=None):
|
def check_availability(self, stock_manager=None):
|
||||||
"""
|
"""
|
||||||
Проверяет доступность всего комплекта.
|
Проверяет доступность всего комплекта (возвращает True/False).
|
||||||
Делегирует проверку в сервис.
|
Для обратной совместимости. Использует calculate_available_quantity().
|
||||||
"""
|
"""
|
||||||
return KitAvailabilityChecker.check_availability(self, stock_manager)
|
return self.calculate_available_quantity() > 0
|
||||||
|
|
||||||
|
def calculate_available_quantity(self, warehouse=None):
|
||||||
|
"""
|
||||||
|
Рассчитывает максимальное количество комплектов, которое можно собрать
|
||||||
|
на основе свободных остатков компонентов на складе.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
warehouse: Склад для проверки остатков. Если None, суммируются остатки по всем складам.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decimal: Максимальное количество комплектов (0 если хоть один компонент недоступен)
|
||||||
|
"""
|
||||||
|
from inventory.models import Stock
|
||||||
|
|
||||||
|
if not self.kit_items.exists():
|
||||||
|
return Decimal('0')
|
||||||
|
|
||||||
|
min_available = None
|
||||||
|
|
||||||
|
for kit_item in self.kit_items.select_related('product', 'variant_group'):
|
||||||
|
# Определяем товар для проверки
|
||||||
|
product = None
|
||||||
|
if kit_item.product:
|
||||||
|
product = kit_item.product
|
||||||
|
elif kit_item.variant_group:
|
||||||
|
# Берём первый активный товар из группы вариантов
|
||||||
|
available_products = kit_item.get_available_products()
|
||||||
|
product = available_products[0] if available_products else None
|
||||||
|
|
||||||
|
if not product:
|
||||||
|
# Если товар не найден - комплект недоступен
|
||||||
|
return Decimal('0')
|
||||||
|
|
||||||
|
# Получаем остатки на складе
|
||||||
|
stock_filter = {'product': product}
|
||||||
|
if warehouse:
|
||||||
|
stock_filter['warehouse'] = warehouse
|
||||||
|
|
||||||
|
stocks = Stock.objects.filter(**stock_filter)
|
||||||
|
|
||||||
|
# Суммируем свободное количество (available - reserved)
|
||||||
|
total_free = Decimal('0')
|
||||||
|
for stock in stocks:
|
||||||
|
free_qty = stock.quantity_available - stock.quantity_reserved
|
||||||
|
total_free += free_qty
|
||||||
|
|
||||||
|
# Вычисляем сколько комплектов можно собрать из этого компонента
|
||||||
|
component_quantity = kit_item.quantity or Decimal('1')
|
||||||
|
if component_quantity <= 0:
|
||||||
|
return Decimal('0')
|
||||||
|
|
||||||
|
kits_from_this_component = total_free / component_quantity
|
||||||
|
|
||||||
|
# Ищем минимум (узкое место)
|
||||||
|
if min_available is None or kits_from_this_component < min_available:
|
||||||
|
min_available = kits_from_this_component
|
||||||
|
|
||||||
|
# Возвращаем целую часть (нельзя собрать половину комплекта)
|
||||||
|
return Decimal(int(min_available)) if min_available is not None else Decimal('0')
|
||||||
|
|
||||||
def make_permanent(self):
|
def make_permanent(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
"""
|
|
||||||
Сервис для проверки доступности комплектов.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class KitAvailabilityChecker:
|
|
||||||
"""
|
|
||||||
Проверяет доступность комплектов на основе остатков товаров.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_availability(kit, stock_manager=None):
|
|
||||||
"""
|
|
||||||
Проверяет доступность всего комплекта.
|
|
||||||
|
|
||||||
Комплект доступен, если для каждой позиции в комплекте
|
|
||||||
есть хотя бы один доступный вариант товара.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
kit (ProductKit): Комплект для проверки
|
|
||||||
stock_manager: Объект управления складом (если не указан, используется стандартный)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True, если комплект полностью доступен, иначе False
|
|
||||||
"""
|
|
||||||
from ..utils.stock_manager import StockManager
|
|
||||||
|
|
||||||
if stock_manager is None:
|
|
||||||
stock_manager = StockManager()
|
|
||||||
|
|
||||||
for kit_item in kit.kit_items.all():
|
|
||||||
best_product = kit_item.get_best_available_product(stock_manager)
|
|
||||||
if not best_product:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -290,6 +290,14 @@
|
|||||||
<span class="text-muted">/ {{ item.total_available|floatformat:"-3" }} всего</span>
|
<span class="text-muted">/ {{ item.total_available|floatformat:"-3" }} всего</span>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
{% elif item.item_type == 'kit' %}
|
||||||
|
{# Информация об остатках для комплектов #}
|
||||||
|
<div class="mt-1">
|
||||||
|
<small class="stock-info {% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">
|
||||||
|
<i class="bi bi-box-seam"></i>
|
||||||
|
<strong>{{ item.total_free|floatformat:"0" }}</strong> компл. доступно
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-1">
|
<div class="d-flex justify-content-between align-items-center mt-1">
|
||||||
|
|||||||
@@ -196,6 +196,8 @@
|
|||||||
<strong class="{% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">{{ item.total_free|floatformat:-2 }}</strong>{% if item.total_reserved > 0 %}<small class="text-muted"> / {{ item.total_available|floatformat:-2 }}</small>
|
<strong class="{% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">{{ item.total_free|floatformat:-2 }}</strong>{% if item.total_reserved > 0 %}<small class="text-muted"> / {{ item.total_available|floatformat:-2 }}</small>
|
||||||
<small class="text-warning d-block">{{ item.total_reserved|floatformat:-2 }} в резерве</small>
|
<small class="text-warning d-block">{{ item.total_reserved|floatformat:-2 }} в резерве</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% elif item.item_type == 'kit' %}
|
||||||
|
<strong class="{% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">{{ item.total_free|floatformat:0 }}</strong> <small class="text-muted">компл.</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">-</span>
|
<span class="text-muted">-</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ class CatalogView(LoginRequiredMixin, TemplateView):
|
|||||||
if k.id not in kits_dict:
|
if k.id not in kits_dict:
|
||||||
k.item_type = 'kit'
|
k.item_type = 'kit'
|
||||||
k.main_photo = k.photos.all()[0] if k.photos.all() else None
|
k.main_photo = k.photos.all()[0] if k.photos.all() else None
|
||||||
|
# Рассчитываем доступное количество комплектов
|
||||||
|
k.total_free = k.calculate_available_quantity()
|
||||||
kits_dict[k.id] = k
|
kits_dict[k.id] = k
|
||||||
|
|
||||||
# Теперь добавляем все товары, которых еще нет (товары без категорий или не загруженные)
|
# Теперь добавляем все товары, которых еще нет (товары без категорий или не загруженные)
|
||||||
@@ -104,6 +106,8 @@ class CatalogView(LoginRequiredMixin, TemplateView):
|
|||||||
if k.id not in kits_dict:
|
if k.id not in kits_dict:
|
||||||
k.item_type = 'kit'
|
k.item_type = 'kit'
|
||||||
k.main_photo = k.photos.all()[0] if k.photos.all() else None
|
k.main_photo = k.photos.all()[0] if k.photos.all() else None
|
||||||
|
# Рассчитываем доступное количество комплектов
|
||||||
|
k.total_free = k.calculate_available_quantity()
|
||||||
kits_dict[k.id] = k
|
kits_dict[k.id] = k
|
||||||
|
|
||||||
# Объединяем и сортируем
|
# Объединяем и сортируем
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ class CombinedProductListView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Lis
|
|||||||
kits_list = list(kits.order_by('-created_at'))
|
kits_list = list(kits.order_by('-created_at'))
|
||||||
for k in kits_list:
|
for k in kits_list:
|
||||||
k.item_type = 'kit'
|
k.item_type = 'kit'
|
||||||
|
# Рассчитываем доступное количество комплектов (для отображения остатков)
|
||||||
|
k.total_free = k.calculate_available_quantity()
|
||||||
|
|
||||||
# Объединяем и сортируем по дате создания
|
# Объединяем и сортируем по дате создания
|
||||||
combined = sorted(
|
combined = sorted(
|
||||||
|
|||||||
@@ -61,131 +61,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Модальное окно с паролем -->
|
|
||||||
{% if generated_password %}
|
|
||||||
<div class="modal fade" id="passwordModal" tabindex="-1" aria-labelledby="passwordModalLabel"
|
|
||||||
aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header bg-success text-white">
|
|
||||||
<h5 class="modal-title" id="passwordModalLabel">
|
|
||||||
<i class="bi bi-check-circle-fill"></i> Пользователь успешно создан
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<i class="bi bi-exclamation-triangle-fill"></i>
|
|
||||||
<strong>Важно!</strong> Сохраните этот пароль. Он больше не будет показан.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><strong>Email:</strong> {{ created_user_email }}</p>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><strong>Пароль:</strong></label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" class="form-control form-control-lg font-monospace"
|
|
||||||
id="generatedPassword" value="{{ generated_password }}" readonly>
|
|
||||||
<button class="btn btn-outline-primary" type="button"
|
|
||||||
onclick="copyPassword()">
|
|
||||||
<i class="bi bi-clipboard"></i> Копировать
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<button class="btn btn-outline-success w-100" type="button"
|
|
||||||
onclick="copyCredentials()">
|
|
||||||
<i class="bi bi-clipboard-check"></i> Скопировать логин и пароль
|
|
||||||
</button>
|
|
||||||
<div class="form-text text-center mt-2">Скопирует в формате: <code>{{ created_user_email }} / пароль</code></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="passwordSavedCheck"
|
|
||||||
onchange="toggleCloseButton()">
|
|
||||||
<label class="form-check-label" for="passwordSavedCheck">
|
|
||||||
Я скопировал(а) пароль
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="{% url 'user_roles:list' %}"
|
|
||||||
class="btn btn-success disabled"
|
|
||||||
id="closeModalBtn"
|
|
||||||
disabled>
|
|
||||||
Вернуться к списку
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function copyPassword() {
|
|
||||||
const passwordInput = document.getElementById('generatedPassword');
|
|
||||||
passwordInput.select();
|
|
||||||
passwordInput.setSelectionRange(0, 99999); // Для мобильных устройств
|
|
||||||
|
|
||||||
navigator.clipboard.writeText(passwordInput.value).then(function() {
|
|
||||||
// Меняем текст кнопки на короткое время
|
|
||||||
const btn = event.target.closest('button');
|
|
||||||
const originalHTML = btn.innerHTML;
|
|
||||||
btn.innerHTML = '<i class="bi bi-check-lg"></i> Скопировано!';
|
|
||||||
btn.classList.remove('btn-outline-primary');
|
|
||||||
btn.classList.add('btn-success');
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
btn.innerHTML = originalHTML;
|
|
||||||
btn.classList.remove('btn-success');
|
|
||||||
btn.classList.add('btn-outline-primary');
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyCredentials() {
|
|
||||||
const email = '{{ created_user_email }}';
|
|
||||||
const password = '{{ generated_password }}';
|
|
||||||
const credentials = `${email} / ${password}`;
|
|
||||||
|
|
||||||
navigator.clipboard.writeText(credentials).then(function() {
|
|
||||||
const btn = event.target.closest('button');
|
|
||||||
const originalHTML = btn.innerHTML;
|
|
||||||
btn.innerHTML = '<i class="bi bi-check-lg"></i> Скопировано!';
|
|
||||||
btn.classList.remove('btn-outline-success');
|
|
||||||
btn.classList.add('btn-success');
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
btn.innerHTML = originalHTML;
|
|
||||||
btn.classList.remove('btn-success');
|
|
||||||
btn.classList.add('btn-outline-success');
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleCloseButton() {
|
|
||||||
const checkbox = document.getElementById('passwordSavedCheck');
|
|
||||||
const closeBtn = document.getElementById('closeModalBtn');
|
|
||||||
if (checkbox.checked) {
|
|
||||||
closeBtn.classList.remove('disabled');
|
|
||||||
closeBtn.removeAttribute('disabled');
|
|
||||||
} else {
|
|
||||||
closeBtn.classList.add('disabled');
|
|
||||||
closeBtn.setAttribute('disabled', 'disabled');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Автоматически показываем модальное окно при загрузке страницы
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const modalElement = document.getElementById('passwordModal');
|
|
||||||
if (modalElement) {
|
|
||||||
const passwordModal = new bootstrap.Modal(modalElement);
|
|
||||||
passwordModal.show();
|
|
||||||
console.log('Password modal shown');
|
|
||||||
} else {
|
|
||||||
console.error('Password modal element not found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ def user_role_create(request):
|
|||||||
email = request.POST.get('email')
|
email = request.POST.get('email')
|
||||||
name = request.POST.get('name')
|
name = request.POST.get('name')
|
||||||
role_code = request.POST.get('role')
|
role_code = request.POST.get('role')
|
||||||
password = request.POST.get('password', User.objects.make_random_password(12))
|
password = request.POST.get('password', '').strip()
|
||||||
|
|
||||||
|
# Если пароль не указан, генерируем случайный
|
||||||
|
if not password:
|
||||||
|
password = User.objects.make_random_password(12)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Проверяем, не существует ли уже пользователь с таким email
|
# Проверяем, не существует ли уже пользователь с таким email
|
||||||
|
|||||||
Reference in New Issue
Block a user