удалить лишнего из новостей

This commit is contained in:
lizerk 2026-03-10 13:07:05 +03:00
parent 7e410ebff8
commit 5dce32c303
5 changed files with 0 additions and 336 deletions

View File

@ -1,25 +0,0 @@
from django import forms
from .models import Post, Comment
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['type', 'department', 'title', 'body', 'image', 'attachment']
widgets = {
'type': forms.Select(attrs={'class': 'form-select'}),
'department': forms.Select(attrs={'class': 'form-select'}),
'title': forms.TextInput(attrs={'class': 'form-control'}),
'body': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
'image': forms.ClearableFileInput(attrs={'class': 'form-control'}),
'attachment': forms.ClearableFileInput(attrs={'class': 'form-control'}),
}
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
widgets = {
'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}

View File

@ -1,42 +0,0 @@
from django import forms
TYPE_COMPANY = "company"
TYPE_DEPARTMENT = "department"
class LoginForm(forms.Form):
email = forms.EmailField(label="Email")
password = forms.CharField(label="Пароль", widget=forms.PasswordInput)
class PostCreateForm(forms.Form):
type = forms.ChoiceField(
choices=[(TYPE_COMPANY, "Главная (компания)"), (TYPE_DEPARTMENT, "Отдел")]
)
department = forms.IntegerField(
required=False, min_value=1, label="Отдел (для новостей отдела)"
)
title = forms.CharField(max_length=200, label="Заголовок")
body = forms.CharField(widget=forms.Textarea, label="Текст")
image = forms.ImageField(required=False, label="Изображение")
attachment = forms.FileField(required=False, label="Вложение")
class CommentForm(forms.Form):
post = forms.IntegerField(widget=forms.HiddenInput)
text = forms.CharField(
required=False, widget=forms.Textarea(attrs={"rows": 2}), label="Комментарий"
)
image = forms.ImageField(required=False, label="Изображение")
attachment = forms.FileField(required=False, label="Файл")
def clean(self):
cleaned = super().clean()
has_any = bool(
(cleaned.get("text") or "").strip()
or cleaned.get("image")
or cleaned.get("attachment")
)
if not has_any:
raise forms.ValidationError("Нужно указать текст, изображение или файл.")
return cleaned

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,14 +0,0 @@
from django.urls import path
from . import views_front
app_name = "feed"
urlpatterns = [
path("", views_front.feed_list, name="feed_list"),
path("departments/<int:pk>/", views_front.department_feed, name="department_feed"),
path("employees/<int:pk>/", views_front.employee_feed, name="employee_feed"),
path("post/<int:pk>/", views_front.post_detail, name="post_detail"),
path("post/<int:pk>/pin/", views_front.pin_post, name="pin_post"),
path("post/<int:pk>/like/", views_front.toggle_like, name="toggle_like"),
]

View File

@ -1,252 +0,0 @@
# 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)