Corp/backend/feed/views_front.py
Игорь Надеин 66cbbef243 feat: переделана лента новых сотрудников на AJAX загрузку
- Создан новый JS модуль newEmployeesScroller.js
- Новые сотрудники теперь загружаются через API с клиента
- Аватары используют глобальные URL (как в модале)
- Убран серверный рендеринг из feed_list view
- Шаблон использует пустой контейнер для JS инициализации
- Добавлен полный collapse блок с последним комментарием
- Добавлена форма быстрого комментария с emoji picker
- Исправлена проблема с localhost URL в аватарах
2025-12-05 10:56:23 +03:00

253 lines
7.9 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.

# backend/feed/views.py
from __future__ import annotations
from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
from api.client import ApiResponse, get_api_client
from .constants import TYPE_COMPANY, TYPE_DEPARTMENT
from .forms import CommentForm
# ---------- Константы путей API ----------
API_POSTS = "v1/posts/"
API_COMMENTS = "v1/comments/"
API_DEPARTMENTS = "v1/departments/"
API_EMPLOYEES = "v1/employees/"
# ---------- Вспомогательное: доступ к ApiClient ----------
def _api(request):
return get_api_client(request)
def _paged_or_list(resp: ApiResponse):
"""Извлекает results из пагинированного ответа или возвращает сырой список."""
data = resp.json or {}
return data.get("results", data)
def _extract_page_data(resp: ApiResponse):
"""Извлекает данные пагинации из ответа API.
Returns:
tuple: (items, count, next_url, prev_url)
"""
data = resp.json or {}
if isinstance(data, dict) and "results" in data:
return (
data["results"],
data.get("count", 0),
data.get("next"),
data.get("previous"),
)
return data if isinstance(data, list) else [], 0, None, None
# ---------- Хелперы API для ленты ----------
def _api_list_posts(request, **params) -> list[dict]:
resp = _api(request).get(API_POSTS, params=params)
if not resp.ok:
messages.error(request, f"Не удалось получить публикации: {resp.status}")
return []
return _paged_or_list(resp)
def _api_get_post(request, post_id: int) -> dict | None:
resp = _api(request).get(f"{API_POSTS}{post_id}/")
if not resp.ok:
messages.error(request, f"Публикация недоступна: {resp.status}")
return None
return resp.json or None
def _api_get_comments_for_post(request, post_id: int) -> list[dict]:
resp = _api(request).get(
API_COMMENTS, params={"post": post_id, "ordering": "created_at"}
)
if not resp.ok:
messages.error(request, f"Не удалось получить комментарии: {resp.status}")
return []
return _paged_or_list(resp)
def _api_get_department(request, dept_id: int) -> dict | None:
resp = _api(request).get(f"{API_DEPARTMENTS}{dept_id}/")
return resp.json if resp.ok else None
def _api_get_employee(request, employee_id: int) -> dict | None:
resp = _api(request).get(f"{API_EMPLOYEES}{employee_id}/")
return resp.json if resp.ok else None
def _api_list_department_members(request, dept_id: int) -> list[dict]:
# Предпочтительно: /v1/employees/?department=<id>&is_active=true&ordering=last_name,first_name
resp = _api(request).get(
API_EMPLOYEES,
params={
"department": dept_id,
"is_active": "true",
"ordering": "last_name,first_name",
},
)
return _paged_or_list(resp) if resp.ok else []
def _api_list_new_employees(request, limit: int = 10) -> list[dict]:
"""Получить новых сотрудников через API"""
since = (timezone.now() - timedelta(days=14)).isoformat()
resp = _api(request).get(
API_EMPLOYEES,
params={
"active": "true",
"created_at__gte": since,
"ordering": "-created_at",
"page_size": limit,
},
)
return _paged_or_list(resp) if resp.ok else []
# ---------- Навигационные хелперы ----------
def _post_back_url(post: dict, user) -> str:
t = post.get("type")
if t == TYPE_COMPANY:
return reverse("feed:feed_list")
if t == TYPE_DEPARTMENT and post.get("department_id"):
return reverse("employees:department_detail", args=[post["department_id"]])
# страница пользователя — все его публикации
author_id = post.get("author_id") or (
user.pk if getattr(user, "is_authenticated", False) else None
)
return reverse("feed:employee_feed", args=[author_id or 0])
# ---------- Ленты ----------
def feed_list(request):
"""
Лента компании - посты и новые сотрудники загружаются через API на клиенте.
"""
return render(
request,
"feed/feed_list.html",
)
@login_required
def department_feed(request, pk):
"""Лента отдела"""
department = _api_get_department(request, pk)
if not department:
messages.error(request, "Отдел недоступен.")
return redirect("feed:feed_list")
posts = _api_list_posts(request, type=TYPE_DEPARTMENT, department=pk)
emp_links = _api_list_department_members(request, pk)
return render(
request,
"feed/feed_list.html",
{
"department": department,
"posts": posts,
"new_employees": [],
"emp_links": emp_links,
},
)
@login_required
def employee_feed(request, pk):
"""Лента сотрудника"""
employee = _api_get_employee(request, pk)
if not employee:
messages.error(request, "Сотрудник недоступен.")
return redirect("feed:feed_list")
posts = _api_list_posts(request, author=pk)
return render(
request, "feed/feed_list.html", {"employee": employee, "posts": posts}
)
# ---------- Детальная + комментарии ----------
@login_required
def post_detail(request, pk):
"""Рендерит детальную страницу поста с комментариями. CRUD через API в JavaScript."""
post = _api_get_post(request, pk)
if not post:
return redirect("feed:feed_list")
comments = _api_get_comments_for_post(request, pk)
form = CommentForm(initial={"post": pk})
return render(
request,
"feed/post_detail.html",
{"post": post, "comments": comments, "comment_form": form},
)
@login_required
def pin_post(request, pk):
post = _api_get_post(request, pk)
if not post:
return redirect("feed:feed_list")
action = "unpin" if post.get("pinned") else "pin"
resp = _api(request).post(f"{API_POSTS}{pk}/{action}/")
if resp.ok:
messages.success(
request, "Новость закреплена!" if action == "pin" else "Новость откреплена!"
)
else:
messages.error(request, f"Операция недоступна: {resp.status}")
return redirect(_post_back_url(post, request.user))
# ---------- Лайки ----------
@login_required
def toggle_like(request, pk):
if request.method != "POST":
return redirect("feed:post_detail", pk=pk)
op = (request.POST.get("op") or "like").lower()
if op not in {"like", "unlike"}:
op = "like"
endpoint = "like" if op == "like" else "unlike"
resp = _api(request).post(f"{API_POSTS}{pk}/{endpoint}/")
if not resp.ok:
messages.error(request, f"Не удалось {op} публикацию: {resp.status}")
wants_json = (
"application/json" in request.headers.get("Accept", "")
or request.headers.get("X-Requested-With") == "XMLHttpRequest"
)
if wants_json:
data = resp.json or {}
return JsonResponse(
{
"ok": bool(resp.ok),
"post_id": pk,
"liked": data.get("liked"),
"likes": data.get("likes_count"),
}
)
next_url = (
request.POST.get("next")
or request.META.get("HTTP_REFERER")
or reverse("feed:post_detail", args=[pk])
)
return HttpResponseRedirect(next_url)