Merge pull request 'Оптимизировал просмотры страниц' (#22) from fix_test into main
Some checks failed
Auto-update README / update-readme (push) Has been cancelled

Reviewed-on: #22
This commit is contained in:
Николай Сердюк 2026-06-19 22:10:39 +03:00
commit d54d77dd88
7 changed files with 19 additions and 69 deletions

View File

@ -45,6 +45,9 @@ X_FRAME_OPTIONS = 'ALLOW-FROM https://metrika.yandex.ru'
# ОБЯЗАТЕЛЬНО укажите ваши домены # ОБЯЗАТЕЛЬНО укажите ваши домены
ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',') ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')
# Исключения для отслеживания
EXCLUDED_IPS = os.getenv('EXCLUDED_IPS', 'localhost,127.0.0.1').split(',')
# Важно для работы за прокси # Важно для работы за прокси
if not DEBUG: if not DEBUG:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

View File

@ -1,4 +1,3 @@
# blog/signals.py
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from .models import Article from .models import Article

View File

@ -96,7 +96,6 @@ class ArticleDetailView(MenuContextMixin, BreadcrumbMixin, DetailView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""Обработка формы комментария.""" """Обработка формы комментария."""
self.object = self.get_object() self.object = self.get_object()
print("POST data:", request.POST)
if not request.user.is_authenticated: if not request.user.is_authenticated:
messages.error(request, "❌ Только авторизованные пользователи могут оставлять комментарии.") messages.error(request, "❌ Только авторизованные пользователи могут оставлять комментарии.")
@ -106,7 +105,6 @@ class ArticleDetailView(MenuContextMixin, BreadcrumbMixin, DetailView):
if form.is_valid(): if form.is_valid():
parent_id = form.cleaned_data.get('parent_id') parent_id = form.cleaned_data.get('parent_id')
print(f"parent_id from cleaned_data: {parent_id}")
parent = None parent = None
if parent_id: if parent_id:

View File

@ -1,6 +1,8 @@
from .models import PageView, Visitor from .services import track_page_view
from django.utils import timezone import logging
from django.db import transaction
logger = logging.getLogger(__name__)
class CSPMiddleware: class CSPMiddleware:
@ -20,45 +22,7 @@ class PageViewMiddleware:
def __call__(self, request): def __call__(self, request):
# Игнорируем статические файлы и админку # Игнорируем статические файлы и админку
if not request.path.startswith('/static/') and not request.path.startswith('/admin/'): if not request.path.startswith('/static/') and not request.path.startswith('/admin/'):
self.track_page_view(request) track_page_view(request)
response = self.get_response(request) response = self.get_response(request)
return response return response
def track_page_view(self, request):
try:
with transaction.atomic():
# Сохраняем просмотр страницы
PageView.objects.create(
url=request.path,
ip_address=self.get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
referer=request.META.get('HTTP_REFERER', '')
)
# Обновляем статистику посетителя
ip = self.get_client_ip(request)
visitor, created = Visitor.objects.get_or_create(
ip_address=ip,
defaults={
'first_visit': timezone.now(),
'last_visit': timezone.now()
}
)
if not created:
visitor.last_visit = timezone.now()
visitor.visit_count += 1
visitor.save()
except Exception as e:
# Логируем ошибку, но не прерываем выполнение
print(f"Error tracking page view: {e}")
def get_client_ip(self, request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip

View File

@ -3,17 +3,6 @@ from django.views.generic.base import ContextMixin
from .services import track_page_view from .services import track_page_view
class PageViewTrackingMixin(ContextMixin):
"""
Миксин для отслеживания просмотров страниц.
Добавляет трекинг при вызове view.
"""
def dispatch(self, request, *args, **kwargs):
# Отслеживаем просмотр перед обработкой запроса
track_page_view(request)
return super().dispatch(request, *args, **kwargs)
class MenuContextMixin(ContextMixin): class MenuContextMixin(ContextMixin):
""" """
Миксин для добавления меню в контекст. Миксин для добавления меню в контекст.

View File

@ -1,7 +1,7 @@
import logging import logging
from typing import Optional from typing import Optional
from django.http import HttpRequest from django.http import HttpRequest
from django.conf import settings
from .models import PageView, Visitor from .models import PageView, Visitor
from django.utils import timezone from django.utils import timezone
@ -28,14 +28,11 @@ def should_track_request(request: HttpRequest) -> bool:
'/cron', '/remote.php', '/ocs', '/apps/', '/custom_apps/', '/cron', '/remote.php', '/ocs', '/apps/', '/custom_apps/',
] ]
docker_ips = [
'192.168.64.1', '192.168.65.1',
'172.17.0.1', '172.18.0.1', '172.19.0.1',
]
if any(path.startswith(p) for p in ignored_paths): if any(path.startswith(p) for p in ignored_paths):
return False return False
if client_ip in docker_ips:
excluded_ips = getattr(settings, 'EXCLUDED_IPS', [])
if client_ip in excluded_ips:
return False return False
return True return True

View File

@ -16,7 +16,7 @@ from django.db.models import Count, QuerySet
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.views.decorators.http import require_GET, require_POST from django.views.decorators.http import require_GET, require_POST
from django.views.generic import TemplateView, ListView, CreateView, UpdateView, DetailView from django.views.generic import TemplateView, ListView, CreateView, UpdateView, DetailView
from .mixins import PageViewTrackingMixin, MenuContextMixin, BreadcrumbMixin from .mixins import MenuContextMixin, BreadcrumbMixin
from .services import get_published_queryset, track_page_view from .services import get_published_queryset, track_page_view
from typing import Any, Dict, Type from typing import Any, Dict, Type
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -26,7 +26,7 @@ from products.models import Order
import os import os
class BasePageView(PageViewTrackingMixin, MenuContextMixin, TemplateView): class BasePageView(MenuContextMixin, TemplateView):
""" """
Базовый класс для всех страниц сайта. Базовый класс для всех страниц сайта.
Включает отслеживание просмотров и контекст меню. Включает отслеживание просмотров и контекст меню.
@ -34,7 +34,7 @@ class BasePageView(PageViewTrackingMixin, MenuContextMixin, TemplateView):
extra_context = {} extra_context = {}
class BaseListView(PageViewTrackingMixin, MenuContextMixin, ListView): class BaseListView(MenuContextMixin, ListView):
""" """
Базовый класс для всех страниц со списками объектов. Базовый класс для всех страниц со списками объектов.
Включает отслеживание просмотров и контекст меню. Включает отслеживание просмотров и контекст меню.
@ -194,14 +194,14 @@ class RecallListView(BaseListView, BreadcrumbMixin):
return context return context
class ProfileView(LoginRequiredMixin, PageViewTrackingMixin, MenuContextMixin, BreadcrumbMixin, TemplateView): class ProfileView(LoginRequiredMixin, MenuContextMixin, BreadcrumbMixin, TemplateView):
template_name = 'programmer/profile.html' template_name = 'programmer/profile.html'
def get_breadcrumbs(self): def get_breadcrumbs(self):
return [{'title': 'Профиль', 'url_name': None}] return [{'title': 'Профиль', 'url_name': None}]
class RegisterView(PageViewTrackingMixin, MenuContextMixin, SuccessMessageMixin, BreadcrumbMixin, CreateView): class RegisterView(MenuContextMixin, SuccessMessageMixin, BreadcrumbMixin, CreateView):
"""Регистрация нового пользователя""" """Регистрация нового пользователя"""
template_name = 'programmer/register.html' template_name = 'programmer/register.html'
form_class = RegistrationForm form_class = RegistrationForm
@ -240,7 +240,7 @@ class RegisterView(PageViewTrackingMixin, MenuContextMixin, SuccessMessageMixin,
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class ProfileEditView(LoginRequiredMixin, PageViewTrackingMixin, MenuContextMixin, BreadcrumbMixin, UpdateView): class ProfileEditView(LoginRequiredMixin, MenuContextMixin, BreadcrumbMixin, UpdateView):
model = Profile model = Profile
form_class = ProfileForm form_class = ProfileForm
template_name = 'programmer/profile_edit.html' template_name = 'programmer/profile_edit.html'