/
Anubis55
/
Web-Polytech
Обзор
Документация
Войти
/
Anubis55
/
Web-Polytech
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
comics/views.py
514 строк
18 KB
Anubis55
Мелкие улучшение в коде
26 июн 2026, 02:21
26 июн 2026, 02:21
8e887ce
Код
Авторство
О чём код?
from typing import Optional, List, Dict, Any, Union, Set from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.shortcuts import render, get_object_or_404, redirect from django.views.generic import UpdateView, DeleteView from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.models import User from django.contrib import messages from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.files.storage import FileSystemStorage from django.db.models import Avg, Count, Q from django.urls import reverse, reverse_lazy from django.utils import timezone from django.views.decorators.http import require_POST from django.conf import settings from django.db import IntegrityError from .models import Comment, Genre, Comic, Chapter, Rating, Favorite, Appeal, AppealFile from .forms import CustomUserCreationForm, CommentForm, ContactForm, UploadFileForm from .filters import ComicFilter import os def index(request: HttpRequest) -> HttpResponse: chapters = Chapter.objects.select_related('comic').order_by('-published_at') comic_ids = [] seen = set() for ch in chapters: if ch.comic_id not in seen: comic_ids.append(ch.comic_id) seen.add(ch.comic_id) if len(comic_ids) == 6: break comics_with_rating = Comic.objects.filter(id__in=comic_ids).annotate( avg_rating=Avg('ratings__score') ) comics_dict = {} for comic in comics_with_rating: comics_dict[comic.id] = comic latest_chapters = [] added_comic_ids = set() for ch in chapters: if ch.comic_id in comics_dict and ch.comic_id not in added_comic_ids: ch.comic = comics_dict[ch.comic_id] latest_chapters.append(ch) added_comic_ids.add(ch.comic_id) if len(latest_chapters) == 6: break continue_reading = None if request.user.is_authenticated: random_comics = Comic.objects.annotate( avg_rating=Avg('ratings__score'), total_chapters=Count('chapters') ).order_by('?')[:5] continue_reading = [ { 'comic': comic, 'read_chapters': 0, 'total_chapters': comic.total_chapters, } for comic in random_comics ] popular_manga = Comic.objects.annotate( avg_rating=Avg('ratings__score') ).order_by('-views')[:5] top_users = User.objects.annotate( comment_count=Count('comments') ).order_by('-comment_count')[:10] context = { 'latest_chapters': latest_chapters, 'popular_manga': popular_manga, 'top_users': top_users, 'continue_reading': continue_reading, } return render(request, 'comics/index.html', context) def manga_list(request: HttpRequest) -> HttpResponse: """ Страница каталога комиксов с фильтрацией, поиском и пагинацией. Args: request: HTTP-запрос. Returns: HttpResponse: Отрендеренная страница manga_list.html. """ comics = Comic.objects.all().annotate(total_chapters=Count('chapters')).order_by('-id') filter = ComicFilter(request.GET, queryset=comics) comics = filter.qs paginator = Paginator(comics, 12) page = request.GET.get('page') try: mangas = paginator.page(page) except PageNotAnInteger: mangas = paginator.page(1) except EmptyPage: mangas = paginator.page(paginator.num_pages) genres = Genre.objects.all() return render(request, 'comics/manga_list.html', { 'mangas': mangas, 'genres': genres, 'filter': filter, 'current_status': request.GET.get('status'), 'current_genre': request.GET.get('genre'), 'current_search': request.GET.get('search'), }) def manga_detail(request: HttpRequest, slug: str) -> HttpResponse: manga = get_object_or_404( Comic.objects.annotate( avg_rating=Avg('ratings__score'), total_chapters=Count('chapters'), total_comments=Count('comments'), favorites_count=Count('favorites__user', distinct=True) ).prefetch_related('authors', 'genres'), slug=slug ) chapters = manga.chapters.order_by('-number') comments = manga.comments.select_related('user__profile').filter(is_approved=True) is_bookmarked = False user_rating = None if request.user.is_authenticated: is_bookmarked = Favorite.objects.filter(user=request.user, comic=manga).exists() user_rating = Rating.objects.filter(user=request.user, comic=manga).first() genre_ids = manga.genres.values_list('id', flat=True) similar_comics = Comic.objects.filter(genres__in=genre_ids) \ .exclude(pk=manga.pk) \ .annotate( genre_match=Count('genres', filter=Q(genres__in=genre_ids)), avg_rating=Avg('ratings__score') ) \ .order_by('-genre_match', '-views')[:3] if request.method == 'POST': if 'rating' in request.POST: score = request.POST.get('score') if score and score.isdigit(): score = int(score) if 1 <= score <= 5: Rating.objects.update_or_create( user=request.user, comic=manga, defaults={'score': score} ) messages.success(request, 'Оценка сохранена!') else: messages.error(request, 'Неверная оценка.') return redirect('comics:manga_detail', slug=manga.slug) else: form = CommentForm(request.POST) if form.is_valid(): comment = form.save(commit=False) comment.user = request.user comment.comic = manga comment.save() return redirect('comics:manga_detail', slug=manga.slug) else: form = CommentForm() active_tab = request.GET.get('tab', 'info') return render(request, 'comics/manga_detail.html', { 'manga': manga, 'chapters': chapters, 'comments': comments, 'form': form, 'similar_comics': similar_comics, 'is_bookmarked': is_bookmarked, 'active_tab': active_tab, 'user_rating': user_rating, }) def chapter_detail(request: HttpRequest, pk: int) -> HttpResponse: """ Страница просмотра одной главы с файлом или изображением. Args: request: HTTP-запрос. pk: ID главы. Returns: HttpResponse: Отрендеренная страница chapter_detail.html. """ chapter = get_object_or_404(Chapter, pk=pk) file_ext = '' image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'jfif'] if chapter.file: file_ext = chapter.file.name.split('.')[-1].lower() return render(request, 'comics/chapter_detail.html', { 'chapter': chapter, 'file_ext': file_ext, 'image_extensions': image_extensions, }) def register(request: HttpRequest) -> HttpResponse: """ Страница регистрации нового пользователя. Args: request: HTTP-запрос. Returns: HttpResponse: Отрендеренная страница register.html. """ if request.method == 'POST': form = CustomUserCreationForm(request.POST) if form.is_valid(): form.save() return redirect('comics:login') else: form = CustomUserCreationForm() return render(request, 'registration/register.html', {'form': form}) @login_required def profile(request: HttpRequest) -> HttpResponse: """ Страница профиля пользователя со статистикой посещений. Args: request: HTTP-запрос. Returns: HttpResponse: Отрендеренная страница profile.html. """ last_visit_date = request.session.get('last_visit_date') visit_days_count = request.session.get('visit_days_count', 0) today = timezone.now().date().isoformat() if last_visit_date != today: visit_days_count += 1 request.session['last_visit_date'] = today request.session['visit_days_count'] = visit_days_count return render(request, 'comics/profile.html', { 'visit_days_count': visit_days_count, }) class CommentUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): """ Представление для обновления комментария (только автор). """ model = Comment form_class = CommentForm template_name = 'comics/comment_form.html' def get_success_url(self) -> str: """ Возвращает URL для перенаправления после успешного обновления. Returns: str: URL страницы комикса. """ return reverse_lazy('comics:manga_detail', args=[self.object.comic.slug]) def test_func(self) -> bool: """ Проверяет, является ли текущий пользователь автором комментария. Returns: bool: True, если пользователь — автор, иначе False. """ comment = self.get_object() return self.request.user == comment.user class CommentDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): """ Представление для удаления комментария (автором). """ model = Comment template_name = 'comics/comment_confirm_delete.html' def get_success_url(self) -> str: """ Возвращает URL для перенаправления после успешного удаления. Returns: str: URL страницы комикса. """ return reverse_lazy('comics:manga_detail', args=[self.object.comic.slug]) def test_func(self) -> bool: """ Проверяет, является ли текущий пользователь автором комментария. Returns: bool: True, если пользователь — автор, иначе False. """ comment = self.get_object() return self.request.user == comment.user def stats(request: HttpRequest) -> HttpResponse: """ Страница статистики с количеством комиксов, глав, пользователей и комментариев. Args: request: HTTP-запрос. Returns: HttpResponse: Отрендеренная страница stats.html. """ comics_titles = Comic.objects.values_list('title', flat=True) status_counts = Comic.objects.values('status').annotate(count=Count('id')) total_comics = Comic.objects.count() total_chapters = Chapter.objects.count() total_users = User.objects.count() total_comments = Comment.objects.count() avg_rating_all = Rating.objects.aggregate(avg=Avg('score'))['avg'] or 0 has_ongoing = Comic.objects.filter(status='ongoing').exists() ongoing_count = Comic.objects.filter(status='ongoing').count() completed_count = Comic.objects.filter(status='completed').count() context = { 'total_comics': total_comics, 'total_chapters': total_chapters, 'total_users': total_users, 'total_comments': total_comments, 'avg_rating_all': round(avg_rating_all, 1), 'has_ongoing': has_ongoing, 'ongoing_count': ongoing_count, 'completed_count': completed_count, 'status_counts': status_counts, 'comics_titles': comics_titles[:20], } return render(request, 'comics/stats.html', context) def contact(request: HttpRequest) -> HttpResponse: """ Страница обратной связи с отправкой сообщения и файлов. Args: request: HTTP-запрос. Returns: HttpResponse: Отрендеренная страница contact.html. """ if request.method == 'POST': form = ContactForm(request.POST, request.FILES) if form.is_valid(): appeal = Appeal.objects.create( name=form.cleaned_data['name'], email=form.cleaned_data['email'], message=form.cleaned_data['message'] ) saved_names = [] for i in range(1, 6): f = form.cleaned_data.get(f'file{i}') if f: fs = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'appeals')) filename = fs.save(f.name, f) saved_names.append(filename) AppealFile.objects.create(appeal=appeal, file=f'appeals/{filename}') if saved_names: files_info = f'Загружены файлы: {", ".join(saved_names)}' else: files_info = 'Файлы не загружены' success_msg = f'{appeal.name} ({appeal.email}), ваше сообщение: "{appeal.message}". {files_info}' request.session['contact_success'] = success_msg return redirect('comics:contact') else: form = ContactForm() success_msg = request.session.pop('contact_success', None) return render(request, 'comics/contact.html', {'form': form, 'success_msg': success_msg}) def test_upload(request: HttpRequest) -> HttpResponse: """ Тестовая страница для загрузки файлов. Args: request: HTTP-запрос. Returns: HttpResponse: Отрендеренная страница upload.html. """ if request.method == 'POST': form = UploadFileForm(request.POST, request.FILES) if form.is_valid(): uploaded_file = form.cleaned_data['file'] fs = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'uploads')) filename = fs.save(uploaded_file.name, uploaded_file) return HttpResponse(f'Файл "{filename}" успешно загружен!') else: form = UploadFileForm() return render(request, 'comics/upload.html', {'form': form}) @login_required def bookmarks(request: HttpRequest) -> HttpResponse: """ Страница закладок пользователя с фильтрацией по статусу. Args: request: HTTP-запрос. Returns: HttpResponse: Отрендеренная страница bookmarks.html. """ favorites = Favorite.objects.filter(user=request.user).select_related('comic') status_filter = request.GET.get('status') if status_filter in ['reading', 'completed']: favorites = favorites.filter(status=status_filter) current_status = status_filter or 'all' show_add_form = request.GET.get('show_add') == '1' search_query = request.GET.get('search', '') search_results = [] bookmarked_comic_ids = set(Favorite.objects.filter(user=request.user).values_list('comic_id', flat=True)) if show_add_form and search_query: search_results = Comic.objects.filter(title__icontains=search_query)[:10] context = { 'favorites': favorites, 'current_status': current_status, 'show_add_form': show_add_form, 'search_query': search_query, 'search_results': search_results, 'bookmarked_comic_ids': bookmarked_comic_ids, } return render(request, 'comics/bookmarks.html', context) @login_required @require_POST def bookmark_add(request: HttpRequest) -> HttpResponseRedirect: """ Добавляет комикс в закладки. Args: request: HTTP-запрос. Returns: HttpResponseRedirect: Перенаправление на страницу закладок или предыдущую страницу. """ comic_id = request.POST.get('comic_id') next_url = request.POST.get('next', reverse('comics:bookmarks')) if comic_id: comic = get_object_or_404(Comic, pk=comic_id) Favorite.objects.get_or_create( user=request.user, comic=comic, defaults={'status': 'reading'} ) return redirect(next_url) @login_required @require_POST def bookmark_delete(request: HttpRequest, pk: int) -> HttpResponseRedirect: """ Удаляет комикс из закладок. Args: request: HTTP-запрос. pk: ID закладки. Returns: HttpResponseRedirect: Перенаправление на страницу закладок. """ favorite = get_object_or_404(Favorite, pk=pk, user=request.user) favorite.delete() return redirect('comics:bookmarks') @login_required @require_POST def bookmark_update_status(request: HttpRequest, pk: int) -> HttpResponseRedirect: """ Обновляет статус чтения в закладке. Args: request: HTTP-запрос. pk: ID закладки. Returns: HttpResponseRedirect: Перенаправление на страницу закладок. """ favorite = get_object_or_404(Favorite, pk=pk, user=request.user) new_status = request.POST.get('status') if new_status in ['reading', 'completed']: favorite.status = new_status favorite.save() return redirect('comics:bookmarks') def test_sentry(request): raise Exception("Тестовая ошибка Sentry")