From 48a96f495ecfb90364ef237a21fc401bd10b9222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 26 Dec 2025 16:25:18 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20requests=20detail=20view=20-=20stage=20?= =?UTF-8?q?4:=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлена функция удаления комментариев в views_front.py - Добавлена обработка удаления в URL routing - Расширен JavaScript модуль для работы с добавлением/удалением комментариев - Добавлена обработка формы добавления комментариев с JSON - Улучшена обработка ошибок при добавлении комментария - Добавлена инициализация emoji picker в модали - Обновлен счетчик комментариев при добавлении/удалении - Обновлены шаблоны для работы с данными из API - Добавлена поддержка перезагрузки содержимого модали после каждого действия --- backend/requests_app/urls_front.py | 7 +- backend/requests_app/views_front.py | 22 +++ backend/static/js/modules/requestDetail.js | 141 ++++++++++++++++-- .../requests_app/request_detail.html | 12 +- 4 files changed, 162 insertions(+), 20 deletions(-) diff --git a/backend/requests_app/urls_front.py b/backend/requests_app/urls_front.py index 48ab43d0..7379e512 100644 --- a/backend/requests_app/urls_front.py +++ b/backend/requests_app/urls_front.py @@ -5,6 +5,7 @@ from .views_front import ( RequestDetailView, request_comments, request_comment_add, + request_comment_delete, ) app_name = "requests" @@ -18,4 +19,8 @@ urlpatterns = [ request_comment_add, name="request_comment_add", ), -] + path( + "comments//delete//", + request_comment_delete, + name="request_comment_delete", ), +] \ No newline at end of file diff --git a/backend/requests_app/views_front.py b/backend/requests_app/views_front.py index e5b41494..35722567 100644 --- a/backend/requests_app/views_front.py +++ b/backend/requests_app/views_front.py @@ -396,3 +396,25 @@ def request_comment_add(request: HttpRequest, pk: int) -> JsonResponse: if not ok and st != 201: return JsonResponse(data or {"detail": f"HTTP {st}"}, status=st) return JsonResponse(data, status=201) + +def request_comment_delete(request: HttpRequest, pk: int, comment_id: int) -> JsonResponse: + """Прокси: удаление комментария. + + DELETE /api/v1/requests/{pk}/comments/{comment_id}/ + + Требует авторизации и проверяет права (только автор может удалить свой комментарий). + """ + if request.method != "POST": + return JsonResponse({"detail": "Method not allowed"}, status=405) + if not request.user.is_authenticated: + return JsonResponse({"detail": "Authentication required"}, status=401) + + api = get_api_client(request) + ok, data, st = _api_unpack( + api.delete(f"v1/requests/{pk}/comments/{comment_id}/") + ) + + if not ok: + return JsonResponse(data or {"detail": f"HTTP {st}"}, status=st) + + return JsonResponse({"detail": "Comment deleted"}, status=204) \ No newline at end of file diff --git a/backend/static/js/modules/requestDetail.js b/backend/static/js/modules/requestDetail.js index ad101341..b35413ee 100644 --- a/backend/static/js/modules/requestDetail.js +++ b/backend/static/js/modules/requestDetail.js @@ -111,6 +111,20 @@ export class RequestDetailModal { if (cancelBtn) { cancelBtn.addEventListener('click', (e) => this.handleCancel(e)); } + + // Обработчик для кнопок удаления комментариев + const deleteCommentForms = this.container?.querySelectorAll('[action*="request_comment_delete"]'); + if (deleteCommentForms) { + deleteCommentForms.forEach(form => { + form.addEventListener('submit', (e) => this.handleDeleteComment(e)); + }); + } + + // Инициализация emoji picker если доступна + this.initEmojiPicker(); + + // Обновить счетчик комментариев + this.updateCommentsCount(); } /** @@ -128,11 +142,98 @@ export class RequestDetailModal { return; } + // Показать состояние загрузки + const submitBtn = form.querySelector('button[type="submit"]'); + const originalBtnText = submitBtn?.textContent || 'Отправить'; + if (submitBtn) { + submitBtn.disabled = true; + submitBtn.innerHTML = 'Отправка...'; + } + try { - const formData = new FormData(form); const response = await fetch(form.action, { method: 'POST', - body: formData, + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': form.querySelector('[name="csrfmiddlewaretoken"]')?.value || '' + }, + body: JSON.stringify({ text: text }), + credentials: 'same-origin' + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.detail || `HTTP ${response.status}`); + } + + // Очистить форму + textarea.value = ''; + + // Показать успешное сообщение + if (submitBtn) { + const originalClass = submitBtn.className; + submitBtn.className = submitBtn.className.replace('btn-primary', 'btn-success'); + submitBtn.innerHTML = 'Комментарий добавлен!'; + + // Вернуть кнопку в исходное состояние + setTimeout(() => { + submitBtn.className = originalClass; + submitBtn.innerHTML = originalBtnText; + submitBtn.disabled = false; + }, 2000); + } + + // Перезагрузить содержимое модали + if (this.currentRequestId) { + // Небольшая задержка для лучшего UX + setTimeout(() => { + this.loadData(this.currentRequestId); + this.updateCommentsCount(); + }, 500); + } + } catch (error) { + console.error('Error submitting comment:', error); + + // Восстановить кнопку при ошибке + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.innerHTML = originalBtnText; + } + + // Показать ошибку в UI + const alertDiv = form.querySelector('.alert'); + if (alertDiv) { + alertDiv.remove(); + } + + const errorAlert = document.createElement('div'); + errorAlert.className = 'alert alert-danger alert-dismissible fade show mb-2'; + errorAlert.innerHTML = ` + Ошибка: ${error.message} + + `; + form.parentNode.insertBefore(errorAlert, form); + } + } + + /** + * Обработчик удаления комментария + */ + async handleDeleteComment(e) { + e.preventDefault(); + + if (!confirm('Вы уверены, что хотите удалить этот комментарий?')) { + return; + } + + const form = e.target; + + try { + const response = await fetch(form.action, { + method: 'POST', + headers: { + 'X-CSRFToken': form.querySelector('[name="csrfmiddlewaretoken"]')?.value || '' + }, credentials: 'same-origin' }); @@ -140,28 +241,42 @@ export class RequestDetailModal { throw new Error(`HTTP ${response.status}`); } - // Очистить форму - textarea.value = ''; - // Перезагрузить содержимое модали if (this.currentRequestId) { await this.loadData(this.currentRequestId); + this.updateCommentsCount(); } } catch (error) { - console.error('Error submitting comment:', error); - alert('Ошибка при добавлении комментария: ' + error.message); + console.error('Error deleting comment:', error); + alert('Ошибка при удалении комментария: ' + error.message); } } /** - * Обработчик отозвания заявления + * Инициализировать emoji picker если доступен */ - handleCancel(e) { - e.preventDefault(); + initEmojiPicker() { + const emojiPicker = this.container?.querySelector('emoji-picker'); + const textarea = this.container?.querySelector('textarea[name="text"]'); - if (confirm('Вы уверены, что хотите отозвать это заявление?')) { - // Перейти по ссылке или отправить форму - window.location.href = e.target.href; + if (emojiPicker && textarea) { + emojiPicker.addEventListener('emoji-click', (e) => { + textarea.value += e.detail.emoji.native; + textarea.focus(); + }); + } + } + + /** + * Обновить счетчик комментариев + */ + updateCommentsCount() { + const commentsContainer = this.container?.querySelector('.d-flex.flex-column.gap-2.mb-3'); + const countSpan = this.container?.querySelector('.comments-count'); + + if (commentsContainer && countSpan) { + const commentCount = commentsContainer.querySelectorAll('.bg-body-tertiary.border').length; + countSpan.textContent = commentCount; } } diff --git a/backend/templates/requests_app/request_detail.html b/backend/templates/requests_app/request_detail.html index 189b0186..ab0edaa7 100644 --- a/backend/templates/requests_app/request_detail.html +++ b/backend/templates/requests_app/request_detail.html @@ -110,25 +110,25 @@
Комментарии
-
Всего: {{ request_obj.comments.count }}
+
Всего: {{ comments|length }}
- {% if request_obj.comments.all %} + {% if comments %}
- {% for c in request_obj.comments.all %} + {% for c in comments %}
- {{ c.author.get_full_name|default:c.author.username }} + {{ c.author.full_name|default:c.author_name }} · - +
{# удалить — только автору комментария (права дополнительно проверяются во вьюхе) #} {% if c.author_id == request.user.id %} -
+ {% csrf_token %}