Files
2020-dq/dicquo/web/views.py

228 lines
11 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.
# -*- coding: utf-8 -*-
__author__ = "Sergei Erjemin"
__copyright__ = "Copyright 2020-2026, Sergei Erjemin"
__credits__ = ["Sergei Erjemin", ]
__license__ = "GPL"
__version__ = "0.3.0"
__maintainer__ = "Sergei Erjemin"
__email__ = "erjemin@gmail.com"
__status__ = "in progress"
from django.core.exceptions import ObjectDoesNotExist
from django.views.generic import DetailView, TemplateView
import time
import pytils
from web.models import TbDictumAndQuotes, TbImages, TbAuthor
# Create your views here.
class CommonContextMixin:
"""
Общий миксин для представлений:
- Логика "одной цитаты" (получение контекста цитаты)
- Общий контекст (куки, тайминги)
"""
def dispatch(self, request, *args, **kwargs):
# Засекаем время в самом начале обработки запроса
self.t_start = time.process_time()
return super().dispatch(request, *args, **kwargs)
def get_filtered_queryset(self):
"""
Возвращает (queryset, tag_slug) на основе GET-параметров запроса.
Если тега нет или он не найден, возвращает (None, None).
"""
tag_slug = self.request.GET.get('tag')
if not tag_slug:
return None, None
dq_qs = TbDictumAndQuotes.objects.all()
# 1. Пробуем найти цитаты, где АВТОР имеет этот тег
author_tag_qs = dq_qs.filter(kAuthor__tags__slug__in=[tag_slug])
if author_tag_qs.exists():
return author_tag_qs, tag_slug
# 2. Если авторов нет, ищем цитаты с этим тегом
quote_tag_qs = dq_qs.filter(tags__slug__in=[tag_slug])
if quote_tag_qs.exists():
return quote_tag_qs, tag_slug
return None, None
def get_dictum_context(self, request, dq, queryset=None):
"""
Получение контекста для цитаты dq. Если queryset передан, используется для логики "следующей цитаты"
и фильтрации по тегу.
"""
context = {}
# Если queryset не передан, используем все объекты
if queryset is None:
queryset = TbDictumAndQuotes.objects.all()
# --- 1. ЛОГИКА ИСТОРИИ СЕССИИ (Предотвращение петель) ---
seen_ids = request.session.get('seen_ids', [])
# Если мы переключили контекст (например, выбрали другой тег), имеет смысл сбросить историю?
# Или можно оставить, так как уникальность ID глобальна.
# Проблема: если seen_ids забит цитатами, а мы выбрали тег, где всего 2 цитаты,
# и они обе случайно оказались в seen_ids (потому что мы их видели раньше без тега),
# то exclude исключит всё.
# Решение: принудительно добавить текущую цитату, если её нет
if dq.id not in seen_ids:
seen_ids.append(dq.id)
if len(seen_ids) > 100:
seen_ids.pop(0)
request.session['seen_ids'] = seen_ids
context.update({'DQ': dq})
# --- 3. АВТОР И ТЕГИ ---
try:
au = TbAuthor.objects.get(id=dq.kAuthor_id)
context.update({'AUTHOR': au})
tags = au.tags.names()
except ObjectDoesNotExist:
tags = dq.tags.names()
tag_and_slug = []
for i in tags:
tag_and_slug.append({"name": i, "slug": pytils.translit.slugify(i.lower())[:120]})
context.update({'TAGS': sorted(tag_and_slug, key=lambda x: x["name"])})
# --- 4. ВЫБОР КАРТИНКИ ---
if dq.kImages_id is None:
if len(tags) != 0:
tagged_image = TbImages.objects.filter(tags__name__in=tags).order_by('?').first()
if tagged_image:
context.update({'IMAGE': tagged_image.imFile})
else:
context.update({'IMAGE': dq.kImages.imFile})
# --- 5. СЧЕТЧИК ---
dq.iViewCounter += 1
dq.save(update_fields=['iViewCounter'])
# --- 6. ВЫБОР СЛЕДУЮЩЕЙ ЦИТАТЫ ---
# Сначала пробуем найти следующую цитату, которую мы еще не видели
dq_next = queryset.exclude(id__in=seen_ids).order_by('?').first()
# Если таких нет (мы посмотрели все цитаты в этом контексте/теге)
if dq_next is None:
# СБРОС ИСТОРИИ!
# Мы посмотрели всё, что было по этому фильтру. Начинаем круг заново.
# Но удалять ВСЮ историю сессии опасно (вдруг мы вернемся в общий список).
# Лучше локально для выбора следующей цитаты игнорировать историю,
# но возможно стоит очистить сессию, чтобы цикл начался чисто.
# Вариант: Очистить seen_ids, чтобы в следующий раз (на некст странице) список был пуст?
# Или просто выбрать любую КРОМЕ текущей?
dq_next = queryset.exclude(id=dq.id).order_by('?').first()
# Если мы действительно прошли весь цикл по тегу, логично сбросить seen_ids,
# чтобы пользователь мог заново проходить этот список случайно, а не "застревать" на последних.
# Однако, очистка seen_ids здесь повлияет на глобальную сессию.
# Если тег "red" (2 цитаты), мы их посмотрели. seen_ids=[1,2].
# queryset=[1,2]. exclude -> []. dq_next=None.
# Fallback: exclude(current) -> [1] (если cur=2). dq_next=1.
# User goes to 1. seen_ids=[1,2] (set logic handles dupes/order? No, list appends).
# seen_ids=[1,2,1].
# Next request (dq=1). queryset=[1,2]. exclude([1,2,1]) -> []. dq_next=2.
# It loops 1-2-1-2.
# Чтобы разорвать этот малый круг и сделать его снова "случайным" (если там >2 элементов, но меньше 100),
# нужно очистить seen_ids, если мы уткнулись в конец списка.
# Но удалять нужно только те ID, которые принадлежат этому queryset? Сложно.
# Проще очистить всё, так как пользователь явно "наелся" текущим контекстом и пошел по второму кругу.
request.session['seen_ids'] = []
if dq_next:
context.update({"NEXT": dq_next.id})
context.update({"NEXT_TXT": pytils.translit.slugify(dq_next.szContent.lower()[:120])})
# Если мы в режиме фильтрации (tag), передаем текущий тег в контекст
if request.GET.get('tag'):
context.update({"CURRENT_TAG": request.GET.get('tag')})
return context
def finalize_context(self, context):
"""
Добавляет общие данные: проверки куки и время выполнения.
"""
if self.request.COOKIES.get('cookie_accept'):
context['cookie_accept'] = 1
# Считаем время от self.t_start, заданного в dispatch
total_time = 0.0
if hasattr(self, 't_start'):
total_time = float(time.process_time() - self.t_start)
context['ticks'] = total_time * 1000
return context
class DictumDetailView(CommonContextMixin, DetailView):
model = TbDictumAndQuotes
template_name = "index.html"
pk_url_kwarg = 'dq_id'
context_object_name = 'DQ'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Определяем контекст фильтрации (если есть тег в URL)
active_qs, _ = self.get_filtered_queryset()
# Используем миксин логики цитаты с учетом фильтра
extras = self.get_dictum_context(self.request, self.object, queryset=active_qs)
context.update(extras)
# Финализируем контекст (куки, тайминги)
return self.finalize_context(context)
class IndexView(CommonContextMixin, TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
active_qs, _ = self.get_filtered_queryset()
dq = None
seen_ids = self.request.session.get('seen_ids', [])
if active_qs is not None:
# Если мы в режиме фильтрации, тоже стараемся не показывать то, что уже видели
dq = active_qs.exclude(id__in=seen_ids).order_by('?').first()
# Если после фильтрации ничего не осталось (мы просмотрели все цитаты тега)
if dq is None:
# Сбрасываем историю и берем любую
self.request.session['seen_ids'] = []
dq = active_qs.order_by('?').first()
if dq is None:
# Если тег не задан, или по тегу ничего не нашлось совсем
# Сбрасываем active_qs на "все", так как специфический контекст пуст
active_qs = TbDictumAndQuotes.objects.all()
# Случайная цитата (с учетом истории, чтобы главная страница тоже не зацикливалась)
dq = active_qs.exclude(id__in=seen_ids).order_by('?').first()
if dq is None:
self.request.session['seen_ids'] = []
dq = active_qs.order_by('?').first()
if dq:
# Используем миксин, ОБЯЗАТЕЛЬНО передаем active_qs
extras = self.get_dictum_context(self.request, dq, queryset=active_qs)
context.update(extras)
# Финализируем контекст (куки, тайминги)
return self.finalize_context(context)