From 17d45d1c058b4c7d8a6a669ca458a0223abd427a Mon Sep 17 00:00:00 2001 From: erjemin Date: Sun, 5 Jul 2026 21:29:35 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BC=D0=B5=D0=B4=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20'=3F'-=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=20=D0=B2=20=D0=B1=D0=B0=D0=B7=D0=B5=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=D0=B8=20=D0=BD=D0=B0=20=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=20=D0=BF=D0=BE=20ip=20(=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=20=D0=BD=D0=B5=20=D1=82=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=BE=D0=B7=D0=B8=D1=82=D1=8C=20=D0=B2=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5)=20+=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dicquo/web/views.py | 45 +++++++++++++++++++++++++++++++---------- docker-compose.prod.yml | 8 ++++---- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/dicquo/web/views.py b/dicquo/web/views.py index 6a6f683..4f28efe 100644 --- a/dicquo/web/views.py +++ b/dicquo/web/views.py @@ -12,6 +12,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.views.generic import DetailView, TemplateView import time import pytils +import random from web.models import TbDictumAndQuotes, TbImages, TbAuthor @@ -98,9 +99,11 @@ class CommonContextMixin: # --- 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}) + # Используем Python random вместо order_by('?') для производительности + tagged_images = list(TbImages.objects.filter(tags__name__in=tags).values_list('id', 'imFile')) + if tagged_images: + tagged_image = tagged_images[random.randint(0, len(tagged_images) - 1)] + context.update({'IMAGE': tagged_image[1]}) else: context.update({'IMAGE': dq.kImages.imFile}) @@ -110,7 +113,13 @@ class CommonContextMixin: # --- 6. ВЫБОР СЛЕДУЮЩЕЙ ЦИТАТЫ --- # Сначала пробуем найти следующую цитату, которую мы еще не видели - dq_next = queryset.exclude(id__in=seen_ids).order_by('?').first() + # Используем Python random вместо order_by('?') для производительности + unseen_ids = queryset.exclude(id__in=seen_ids).values_list('id', flat=True) + if unseen_ids: + dq_next_id = list(unseen_ids)[random.randint(0, len(unseen_ids) - 1)] + dq_next = queryset.filter(id=dq_next_id).first() + else: + dq_next = None # Если таких нет (мы посмотрели все цитаты в этом контексте/теге) if dq_next is None: @@ -123,7 +132,12 @@ class CommonContextMixin: # Вариант: Очистить seen_ids, чтобы в следующий раз (на некст странице) список был пуст? # Или просто выбрать любую КРОМЕ текущей? - dq_next = queryset.exclude(id=dq.id).order_by('?').first() + other_ids = list(queryset.exclude(id=dq.id).values_list('id', flat=True)) + if other_ids: + dq_next_id = other_ids[random.randint(0, len(other_ids) - 1)] + dq_next = queryset.filter(id=dq_next_id).first() + else: + dq_next = None # Если мы действительно прошли весь цикл по тегу, логично сбросить seen_ids, # чтобы пользователь мог заново проходить этот список случайно, а не "застревать" на последних. @@ -200,13 +214,19 @@ class IndexView(CommonContextMixin, TemplateView): if active_qs is not None: # Если мы в режиме фильтрации, тоже стараемся не показывать то, что уже видели - dq = active_qs.exclude(id__in=seen_ids).order_by('?').first() + unseen_ids = list(active_qs.exclude(id__in=seen_ids).values_list('id', flat=True)) + if unseen_ids: + dq = active_qs.filter(id=unseen_ids[random.randint(0, len(unseen_ids) - 1)]).first() + else: + dq = None # Если после фильтрации ничего не осталось (мы просмотрели все цитаты тега) if dq is None: # Сбрасываем историю и берем любую self.request.session['seen_ids'] = [] - dq = active_qs.order_by('?').first() + all_ids = list(active_qs.values_list('id', flat=True)) + if all_ids: + dq = active_qs.filter(id=all_ids[random.randint(0, len(all_ids) - 1)]).first() if dq is None: # Если тег не задан, или по тегу ничего не нашлось совсем @@ -214,11 +234,14 @@ class IndexView(CommonContextMixin, TemplateView): active_qs = TbDictumAndQuotes.objects.all() # Случайная цитата (с учетом истории, чтобы главная страница тоже не зацикливалась) - dq = active_qs.exclude(id__in=seen_ids).order_by('?').first() - - if dq is None: + unseen_ids = list(active_qs.exclude(id__in=seen_ids).values_list('id', flat=True)) + if unseen_ids: + dq = active_qs.filter(id=unseen_ids[random.randint(0, len(unseen_ids) - 1)]).first() + else: self.request.session['seen_ids'] = [] - dq = active_qs.order_by('?').first() + all_ids = list(active_qs.values_list('id', flat=True)) + if all_ids: + dq = active_qs.filter(id=all_ids[random.randint(0, len(all_ids) - 1)]).first() if dq: # Используем миксин, ОБЯЗАТЕЛЬНО передаем active_qs diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8fd24e2..087e266 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -48,7 +48,7 @@ services: mkdir -p /app/public/media/errors && cp /home/app/web/dicquo/templates/static_404.html /app/public/media/errors/404.html && cp /home/app/web/dicquo/templates/static_500.html /app/public/media/errors/500.html && - gunicorn --workers 3 --bind 0.0.0.0:8000 dicquo.wsgi:application" + gunicorn --workers 2 --timeout 15 --bind 0.0.0.0:8000 dicquo.wsgi:application" # 4. Проброс портов (Внешний Nginx -> localhost:8010) ports: @@ -91,9 +91,9 @@ services: healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()"] interval: 30s # Проверка каждые 30 секунд - timeout: 3s # Таймаут ответа - 3 секунды - start_period: 10s # Даем 10 секунд на стартап перед первой проверкой - retries: 3 # Unhealthy после 3 неудачных попыток + timeout: 5s # Таймаут ответа - 5 секунд + start_period: 60s # Даем 60 секунд на стартап (миграции + collectstatic + gunicorn запуск) + retries: 5 # Unhealthy после 5 неудачных попыток # 9. Логирование (Ротация) logging: