from programmer.mixins import MenuContextMixin, BreadcrumbMixin from django.views.generic import ListView, DetailView, CreateView from django.urls import reverse_lazy from django.contrib import messages from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from .models import Article, Category from .services import ( get_published_articles, get_article_by_slug, increment_article_views, add_comment_to_article ) from .forms import CommentForm from django.http import Http404 from django.shortcuts import redirect from django.contrib.admin.views.decorators import staff_member_required class ArticleListView(MenuContextMixin, BreadcrumbMixin, ListView): """Список статей""" model = Article template_name = 'blog/article_list.html' context_object_name = 'articles' paginate_by = 10 def get_breadcrumbs(self): if 'category_slug' in self.kwargs: category = Category.objects.get(slug=self.kwargs['category_slug']) return [ {'title': 'Статьи', 'url_name': 'blog:article_list'}, {'title': category.name, 'url_name': None}, ] return [{'title': 'Статьи', 'url_name': None}] def get_queryset(self): category_slug = self.kwargs.get('category_slug') return get_published_articles(category_slug) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['categories'] = Category.objects.all() context.update({ 'title': "Статии | Инструкции", 'meta_description': ( "Инструкции по работе в 1С и бизнес-решения" "инструкции, статьи помощь 1С." ), 'meta_keywords': ( "проекты 1С, автоматизация склада 1С, интеграция ТСД 1С, " "миграция 1С 7.7, кейсы 1С, примеры работ 1С" ), }) if 'category_slug' in self.kwargs: context['current_category'] = Category.objects.filter( slug=self.kwargs['category_slug'] ).first() return context def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) class ArticleDetailView(MenuContextMixin, BreadcrumbMixin, DetailView): """Детальная страница статьи с формой комментария.""" model = Article template_name = 'blog/article_detail.html' context_object_name = 'article' def get_object(self, queryset=None): """Получаем статью по slug, только опубликованные.""" if not hasattr(self, '_article_cache'): article = get_article_by_slug(self.kwargs['slug']) if article is None: raise Http404("Статья не найдена") self._article_cache = article return self._article_cache def get_breadcrumbs(self): article = self.object # already set by the time breadcrumbs are called return [ {'title': 'Статьи', 'url_name': 'blog:article_list'}, { 'title': article.category.name, 'url_name': 'blog:category_detail', 'category_slug': article.category.slug, }, {'title': article.title, 'url_name': None}, ] def get(self, request, *args, **kwargs): """Устанавливаем self.object один раз, увеличиваем счётчик просмотров.""" self.object = self.get_object() increment_article_views(self.object) context = self.get_context_data(object=self.object) return self.render_to_response(context) def post(self, request, *args, **kwargs): """Обработка формы комментария.""" self.object = self.get_object() if not request.user.is_authenticated: messages.error(request, "❌ Только авторизованные пользователи могут оставлять комментарии.") return redirect(request.path) form = CommentForm(request.POST, request=request) if form.is_valid(): add_comment_to_article( article=self.object, data=form.cleaned_data ) messages.success(request, "✅ Ваш комментарий отправлен на модерацию.") return redirect(self.object.get_absolute_url()) # Форма невалидна — показываем страницу снова с ошибками context = self.get_context_data(object=self.object, form=form) return self.render_to_response(context) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Используем form из kwargs если передана (например при ошибке валидации) if 'form' not in kwargs: context['form'] = CommentForm(request=self.request) context.update({ 'moderated_comments': self.object.comments.filter(is_moderated=True), 'user_is_authenticated': self.request.user.is_authenticated, 'title': self.object.get_seo_title(), 'meta_description': self.object.get_seo_description(), 'meta_keywords': ', '.join( tag.name for tag in self.object.tags.all() ), }) return context @method_decorator(staff_member_required, name='dispatch') class ArticleDraftPreviewView(MenuContextMixin, BreadcrumbMixin, DetailView): model = Article template_name = 'blog/article_detail.html' # используем тот же шаблон context_object_name = 'article' def get_queryset(self): # Для предпросмотра показываем даже неопубликованные статьи return Article.objects.all() # без фильтрации по is_published