-
Элегантный букет лотосов
-
-
-
-
+
@@ -1412,57 +1409,14 @@ document.addEventListener('DOMContentLoaded', function() {
}
// ========== ГЕНЕРАТОР НАЗВАНИЙ ==========
- // Обработчик для кнопок "Взять"
- document.querySelectorAll('.name-suggestions .btn-success').forEach(button => {
- button.addEventListener('click', function() {
- const suggestionText = this.closest('.d-flex').querySelector('.text-muted').textContent;
- const nameInput = document.getElementById('id_name');
- if (nameInput) {
- nameInput.value = suggestionText;
- // Улучшаем визуальный эффект
- nameInput.style.borderColor = '#198754';
- nameInput.style.boxShadow = '0 0 0 0.25rem rgba(25, 135, 84, 0.15)';
- setTimeout(() => {
- nameInput.style.borderColor = '';
- nameInput.style.boxShadow = '';
- }, 2000);
- }
- // Закрываем collapse
- const collapse = document.getElementById('nameGeneratorCollapse');
- const bsCollapse = new bootstrap.Collapse(collapse, { toggle: false });
- bsCollapse.hide();
- });
- });
- // Обработчик для кнопок "Убрать"
- document.querySelectorAll('.name-suggestions .btn-outline-danger').forEach(button => {
- button.addEventListener('click', function() {
- this.closest('.d-flex').remove();
- });
- });
-
- // Обработчик для кнопок "Потом"
- document.querySelectorAll('.name-suggestions .btn-outline-secondary').forEach(button => {
- button.addEventListener('click', function() {
- const row = this.closest('.d-flex');
- row.style.opacity = '0.5';
- row.style.textDecoration = 'line-through';
- setTimeout(() => {
- row.style.opacity = '1';
- row.style.textDecoration = 'none';
- }, 1000);
- });
- });
-
- // ========== ГЕНЕРАТОР НАЗВАНИЙ - НОВЫЕ ОБРАБОТЧИКИ ==========
-
- // Обработчик для кнопки "Сгенерировать" (LLM)
- const generateBtn = document.querySelector('#nameGeneratorCollapse .btn-outline-primary');
- if (generateBtn) {
- generateBtn.addEventListener('click', async function() {
- const originalHTML = generateBtn.innerHTML;
- generateBtn.innerHTML = '
Генерация...';
- generateBtn.disabled = true;
+ // Обработчик для кнопки "Пополнить базу названиям��"
+ const populateNamesBtn = document.getElementById('populateNamesBtn');
+ if (populateNamesBtn) {
+ populateNamesBtn.addEventListener('click', async function() {
+ const originalHTML = populateNamesBtn.innerHTML;
+ populateNamesBtn.innerHTML = '
Пополнение...';
+ populateNamesBtn.disabled = true;
try {
const response = await fetch("{% url 'products:api-generate-bouquet-names' %}", {
@@ -1478,105 +1432,211 @@ document.addEventListener('DOMContentLoaded', function() {
if (data.success) {
// Обновляем счётчик
updateBouquetNamesCount();
- // Загружаем случайные 3
- await loadRandomNames();
alert(data.message);
} else {
alert('Ошибка: ' + (data.error || 'Неизвестная ошибка'));
}
} catch (error) {
console.error('Error:', error);
- alert('Ошибка при генерации названий. Проверьте, что настроена AI-интеграция.');
+ alert('Ошибка при пополнении базы названий. Проверьте, что настроена AI-интеграция.');
} finally {
- generateBtn.innerHTML = originalHTML;
- generateBtn.disabled = false;
+ populateNamesBtn.innerHTML = originalHTML;
+ populateNamesBtn.disabled = false;
}
});
}
- // Обработчик для кнопки "Случайное"
- const randomBtn = document.querySelector('#nameGeneratorCollapse .btn-outline-secondary');
- if (randomBtn) {
- randomBtn.addEventListener('click', loadRandomNames);
+ // Обработчик для кнопки "Дать три варианта"
+ const getThreeNamesBtn = document.getElementById('getThreeNamesBtn');
+ if (getThreeNamesBtn) {
+ getThreeNamesBtn.addEventListener('click', loadThreeRandomNames);
}
- async function loadRandomNames() {
- const originalHTML = randomBtn.innerHTML;
- randomBtn.innerHTML = '
Загрузка...';
- randomBtn.disabled = true;
+ // Функция для загрузки трёх случайных названий
+ async function loadThreeRandomNames() {
+ const originalHTML = getThreeNamesBtn.innerHTML;
+ getThreeNamesBtn.innerHTML = '
Загрузка...';
+ getThreeNamesBtn.disabled = true;
try {
const response = await fetch("{% url 'products:api-random-bouquet-names' %}?count=3");
const data = await response.json();
if (data.names && data.names.length > 0) {
- updateNameSuggestions(data.names);
+ updateNameRows(data.names);
} else {
- alert('В базе пока нет названий. Сначала запустите загрузку из JSON или нажмите "Сгенерировать"');
+ alert('В базе пока нет названий. Сначала запустите загрузку из JSON или нажмите "Пополнить базу названиями"');
}
} catch (error) {
console.error('Error:', error);
alert('Ошибка при загрузке названий');
} finally {
- randomBtn.innerHTML = originalHTML;
- randomBtn.disabled = false;
+ getThreeNamesBtn.innerHTML = originalHTML;
+ getThreeNamesBtn.disabled = false;
}
}
+ // Функция для обновления строк с названиями
+ function updateNameRows(names) {
+ const rows = document.querySelectorAll('.name-row');
+
+ rows.forEach((row, index) => {
+ const nameTextElement = row.querySelector('.name-text');
+ const buttonsElement = row.querySelector('.name-buttons');
+
+ if (index < names.length) {
+ // Если есть название для этой строки
+ const nameObj = names[index];
+ nameTextElement.textContent = nameObj.name;
+ row.setAttribute('data-name-id', nameObj.id);
+
+ // Показываем кнопки
+ buttonsElement.style.display = 'flex';
+ } else {
+ // Если нет названия для этой строки
+ nameTextElement.textContent = '-';
+ row.setAttribute('data-name-id', '');
+
+ // Скрываем кнопки
+ buttonsElement.style.display = 'none';
+ }
+ });
+
+ // Устанавливаем обработчики событий для новых кнопок
+ attachNameRowHandlers();
+ }
+
+ // Обработчики для кнопок "Взять" и "Удалить"
+ function attachNameRowHandlers() {
+ // Обработчик для кнопки "Взять"
+ document.querySelectorAll('.btn-take-name').forEach(button => {
+ // Проверяем, был ли уже добавлен обработчик
+ if (!button.dataset.handlerAttached) {
+ button.addEventListener('click', async function() {
+ const row = this.closest('.name-row');
+ const nameText = row.querySelector('.name-text').textContent;
+ const nameId = row.getAttribute('data-name-id');
+ const nameInput = document.getElementById('id_name');
+
+ if (nameInput) {
+ nameInput.value = nameText;
+ nameInput.style.borderColor = '#198754';
+ nameInput.style.boxShadow = '0 0 0 0.25rem rgba(25, 135, 84, 0.15)';
+ setTimeout(() => {
+ nameInput.style.borderColor = '';
+ nameInput.style.boxShadow = '';
+ }, 2000);
+ }
+
+ // Удаляем название из базы данных
+ if (nameId) {
+ await removeNameFromDatabase(nameId);
+
+ // Заменяем название новым из базы данных
+ await replaceNameInRow(row);
+ }
+ });
+
+ // Отмечаем, что обработчик уже добавлен
+ button.dataset.handlerAttached = 'true';
+ }
+ });
+
+ // Обработчик для кнопки "Удалить"
+ document.querySelectorAll('.btn-delete-name').forEach(button => {
+ // Проверяем, был ли уже добавлен обработчик
+ if (!button.dataset.handlerAttached) {
+ button.addEventListener('click', async function() {
+ const row = this.closest('.name-row');
+ const nameId = row.getAttribute('data-name-id');
+
+ // Удаляем название из базы данных
+ if (nameId) {
+ await removeNameFromDatabase(nameId);
+
+ // Заменяем название новым из базы данных
+ await replaceNameInRow(row);
+ }
+ });
+
+ // Отмечаем, что обработчик уже добавлен
+ button.dataset.handlerAttached = 'true';
+ }
+ });
+ }
+
+ // Функция для удаления названия из базы данных
+ async function removeNameFromDatabase(nameId) {
+ try {
+ const response = await fetch(`{% url 'products:api-delete-bouquet-name' 0 %}`.replace('/0/', `/${nameId}/`), {
+ method: 'DELETE',
+ headers: {
+ 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (response.ok) {
+ // Обновляем счётчик
+ updateBouquetNamesCount();
+ } else {
+ console.error('Failed to delete name from database');
+ }
+ } catch (error) {
+ console.error('Error deleting name:', error);
+ }
+ }
+
+ // Функция для замены названия в строке новым из базы данных
+ async function replaceNameInRow(row) {
+ try {
+ const response = await fetch("{% url 'products:api-random-bouquet-names' %}?count=1");
+ const data = await response.json();
+
+ if (data.names && data.names.length > 0) {
+ const newName = data.names[0];
+ const nameTextElement = row.querySelector('.name-text');
+ const buttonsElement = row.querySelector('.name-buttons');
+
+ nameTextElement.textContent = newName.name;
+ row.setAttribute('data-name-id', newName.id);
+
+ // Показываем кнопки
+ buttonsElement.style.display = 'flex';
+ } else {
+ // Если в базе больше нет названий
+ const nameTextElement = row.querySelector('.name-text');
+ const buttonsElement = row.querySelector('.name-buttons');
+
+ nameTextElement.textContent = '-';
+ row.setAttribute('data-name-id', '');
+
+ // Скрываем кнопки
+ buttonsElement.style.display = 'none';
+ }
+ } catch (error) {
+ console.error('Error replacing name:', error);
+ }
+ }
+
+ // Функция для обновления счётчика названий в базе
async function updateBouquetNamesCount() {
try {
- const response = await fetch("{% url 'products:api-random-bouquet-names' %}?count=999");
+ const response = await fetch("{% url 'products:api-get-bouquet-names-count' %}");
const data = await response.json();
const countEl = document.getElementById('bouquetNamesCount');
- if (countEl) countEl.textContent = data.names.length;
+ if (countEl) countEl.textContent = data.count;
} catch (error) {
console.error('Error updating count:', error);
}
}
- function updateNameSuggestions(names) {
- const container = document.querySelector('.name-suggestions');
- container.innerHTML = '';
-
- names.forEach((name, index) => {
- const row = document.createElement('div');
- row.className = 'd-flex justify-content-between align-items-center py-2' +
- (index < names.length - 1 ? ' border-bottom' : '');
- row.innerHTML = `
-
${name}
-
-
-
-
- `;
- container.appendChild(row);
- });
- attachNameButtonHandlers();
- }
-
- function attachNameButtonHandlers() {
- document.querySelectorAll('.btn-apply-name').forEach(button => {
- button.addEventListener('click', function() {
- const name = this.closest('.d-flex').querySelector('.text-muted').textContent;
- const nameInput = document.getElementById('id_name');
- if (nameInput) {
- nameInput.value = name;
- nameInput.style.borderColor = '#198754';
- nameInput.style.boxShadow = '0 0 0 0.25rem rgba(25, 135, 84, 0.15)';
- setTimeout(() => {
- nameInput.style.borderColor = '';
- nameInput.style.boxShadow = '';
- }, 2000);
- }
- });
- });
-
- document.querySelectorAll('.btn-remove-name').forEach(button => {
- button.addEventListener('click', function() {
- this.closest('.d-flex').remove();
- });
- });
- }
+ // Инициализация обработчиков кнопок
+ document.addEventListener('click', function(e) {
+ if (e.target.classList.contains('btn-take-name') || e.target.classList.contains('btn-delete-name')) {
+ attachNameRowHandlers();
+ }
+ });
// ========== ВАЛИДАЦИЯ ПЕРЕД ОТПРАВКОЙ ==========
const kitForm = document.querySelector('form[method="post"]');
diff --git a/myproject/products/urls.py b/myproject/products/urls.py
index b60f8ff..bca8fc7 100644
--- a/myproject/products/urls.py
+++ b/myproject/products/urls.py
@@ -58,6 +58,8 @@ urlpatterns = [
path('api/bulk-update-categories/', api_views.bulk_update_categories, name='api-bulk-update-categories'),
path('api/bouquet-names/random/', api_views.RandomBouquetNamesView.as_view(), name='api-random-bouquet-names'),
path('api/bouquet-names/generate/', api_views.GenerateBouquetNamesView.as_view(), name='api-generate-bouquet-names'),
+ path('api/bouquet-names/
/delete/', api_views.DeleteBouquetNameView.as_view(), name='api-delete-bouquet-name'),
+ path('api/bouquet-names/count/', api_views.GetBouquetNamesCountView.as_view(), name='api-get-bouquet-names-count'),
# Photo processing status API (for AJAX polling)
path('api/photos/status//', photo_status_api.photo_processing_status, name='api-photo-status'),
diff --git a/myproject/products/views/api_views.py b/myproject/products/views/api_views.py
index 472b080..036d6da 100644
--- a/myproject/products/views/api_views.py
+++ b/myproject/products/views/api_views.py
@@ -1816,8 +1816,12 @@ class RandomBouquetNamesView(View):
count = int(request.GET.get('count', 3))
# Ограничиваем максимум до 100
count = min(count, 100)
- names = list(BouquetName.objects.order_by('?')[:count].values_list('name', flat=True))
- return JsonResponse({'names': names})
+
+ # Получаем случайные названия с ID (любые, не только одобренные)
+ queryset = BouquetName.objects.order_by('?')[:count]
+ names_data = [{'id': obj.id, 'name': obj.name} for obj in queryset]
+
+ return JsonResponse({'names': names_data})
class GenerateBouquetNamesView(View):
@@ -1843,3 +1847,25 @@ class GenerateBouquetNamesView(View):
})
else:
return JsonResponse({'success': False, 'error': msg}, status=400)
+
+
+class DeleteBouquetNameView(View):
+ """Удаляет конкретное название из базы"""
+
+ def delete(self, request, pk):
+ try:
+ name_obj = BouquetName.objects.get(pk=pk)
+ name_obj.delete()
+ return JsonResponse({'success': True})
+ except BouquetName.DoesNotExist:
+ return JsonResponse({'success': False, 'error': 'Название не найдено'}, status=404)
+ except Exception as e:
+ return JsonResponse({'success': False, 'error': str(e)}, status=500)
+
+
+class GetBouquetNamesCountView(View):
+ """Возвращает количество названий в базе"""
+
+ def get(self, request):
+ count = BouquetName.objects.count()
+ return JsonResponse({'count': count})
diff --git a/test_bouquet_api.py b/test_bouquet_api.py
new file mode 100644
index 0000000..c34132e
--- /dev/null
+++ b/test_bouquet_api.py
@@ -0,0 +1,54 @@
+"""
+Простой тест для проверки API-эндпоинтов генератора названий букетов
+"""
+import os
+import sys
+import django
+from django.test import Client
+
+# Настройка Django
+sys.path.append(r'c:\Users\team_\Desktop\test_qwen')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
+django.setup()
+
+def test_bouquet_api_endpoints():
+ client = Client()
+
+ print("Тестируем API-эндпоинты для названий букетов...")
+
+ # Тестируем получение случайных названий
+ print("\n1. Тестируем получение случайных названий...")
+ response = client.get('/products/api/bouquet-names/random/?count=3')
+ print(f"Статус: {response.status_code}")
+ if response.status_code == 200:
+ data = response.json()
+ print(f"Получено названий: {len(data.get('names', []))}")
+ print(f"Примеры: {data.get('names', [])[:2]}")
+ else:
+ print(f"Ошибка: {response.content.decode()}")
+
+ # Тестируем получение количества названий
+ print("\n2. Тестируем получение количества названий...")
+ response = client.get('/products/api/bouquet-names/count/')
+ print(f"Статус: {response.status_code}")
+ if response.status_code == 200:
+ data = response.json()
+ print(f"Количество названий в базе: {data.get('count', 0)}")
+ else:
+ print(f"Ошибка: {response.content.decode()}")
+
+ # Попробуем сгенерировать названия (только если есть настройки для AI)
+ print("\n3. Попробуем сгенерировать названия...")
+ try:
+ response = client.post('/products/api/bouquet-names/generate/', {'count': 5})
+ print(f"Статус: {response.status_code}")
+ if response.status_code == 200:
+ data = response.json()
+ print(f"Результат генерации: {data}")
+ else:
+ print(f"Ошибка генерации: {response.content.decode()}")
+ except Exception as e:
+ print(f"Исключение при генерации: {e}")
+
+if __name__ == "__main__":
+ test_bouquet_api_endpoints()
\ No newline at end of file