diff --git a/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc b/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc index dd9dd5e..d2b1f95 100644 Binary files a/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc and b/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc differ diff --git a/OneCprogsite/OneCprogsite/settings.py b/OneCprogsite/OneCprogsite/settings.py index fe1cc84..2b2053b 100644 --- a/OneCprogsite/OneCprogsite/settings.py +++ b/OneCprogsite/OneCprogsite/settings.py @@ -50,6 +50,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'programmer.middleware.PageViewMiddleware', ] ROOT_URLCONF = 'OneCprogsite.urls' diff --git a/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc index a9cb1cc..abc1326 100644 Binary files a/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/__pycache__/middleware.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/middleware.cpython-310.pyc new file mode 100644 index 0000000..9fc794e Binary files /dev/null and b/OneCprogsite/programmer/__pycache__/middleware.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc index d55c907..cbbb6fa 100644 Binary files a/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/__pycache__/urls.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/urls.cpython-310.pyc index 6382875..a20925c 100644 Binary files a/OneCprogsite/programmer/__pycache__/urls.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/urls.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc index e43f2d6..8ae32e4 100644 Binary files a/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/admin.py b/OneCprogsite/programmer/admin.py index 3c0e738..bb5f8d1 100644 --- a/OneCprogsite/programmer/admin.py +++ b/OneCprogsite/programmer/admin.py @@ -43,6 +43,17 @@ class CallbackAdmin(admin.ModelAdmin): search_fields = ('name', 'phone', 'email') readonly_fields = ('time_create',) + +@admin.register(PageView) +class PageViewAdmin(admin.ModelAdmin): + list_display = ['url', 'timestamp', 'ip_address'] + list_filter = ['timestamp', 'url'] + search_fields = ['url', 'ip_address'] + date_hierarchy = 'timestamp' + + def get_queryset(self, request): + return super().get_queryset(request).order_by('-timestamp') + admin.site.register(Competence, ProgrammerAdmin) admin.site.register(Recall, RecallAdmin) admin.site.register(Solution, SolutionAdmin) diff --git a/OneCprogsite/programmer/middleware.py b/OneCprogsite/programmer/middleware.py new file mode 100644 index 0000000..494461e --- /dev/null +++ b/OneCprogsite/programmer/middleware.py @@ -0,0 +1,54 @@ +from .models import PageView, Visitor +from django.utils import timezone +from django.db import transaction + + +class PageViewMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # Игнорируем статические файлы и админку + if not request.path.startswith('/static/') and not request.path.startswith('/admin/'): + self.track_page_view(request) + + response = self.get_response(request) + 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 \ No newline at end of file diff --git a/OneCprogsite/programmer/migrations/0011_visitor_pageview.py b/OneCprogsite/programmer/migrations/0011_visitor_pageview.py new file mode 100644 index 0000000..cbe2d9d --- /dev/null +++ b/OneCprogsite/programmer/migrations/0011_visitor_pageview.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.7 on 2025-11-12 11:43 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('programmer', '0010_alter_callbackrequest_email_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Visitor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ip_address', models.GenericIPAddressField()), + ('first_visit', models.DateTimeField(default=django.utils.timezone.now)), + ('last_visit', models.DateTimeField(default=django.utils.timezone.now)), + ('visit_count', models.IntegerField(default=1)), + ], + options={ + 'indexes': [models.Index(fields=['ip_address'], name='programmer__ip_addr_2c6dca_idx')], + }, + ), + migrations.CreateModel( + name='PageView', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.CharField(max_length=500)), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now)), + ('ip_address', models.GenericIPAddressField()), + ('user_agent', models.TextField(blank=True)), + ('referer', models.CharField(blank=True, max_length=500)), + ], + options={ + 'indexes': [models.Index(fields=['url', 'timestamp'], name='programmer__url_9a41b2_idx'), models.Index(fields=['timestamp'], name='programmer__timesta_070072_idx')], + }, + ), + ] diff --git a/OneCprogsite/programmer/migrations/__pycache__/0011_visitor_pageview.cpython-310.pyc b/OneCprogsite/programmer/migrations/__pycache__/0011_visitor_pageview.cpython-310.pyc new file mode 100644 index 0000000..b085443 Binary files /dev/null and b/OneCprogsite/programmer/migrations/__pycache__/0011_visitor_pageview.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/models.py b/OneCprogsite/programmer/models.py index caa1d47..5cd7369 100644 --- a/OneCprogsite/programmer/models.py +++ b/OneCprogsite/programmer/models.py @@ -1,5 +1,6 @@ from django.db import models from django.urls import reverse +from django.utils import timezone class Recall(models.Model): @@ -98,3 +99,29 @@ class CallbackRequest(models.Model): verbose_name = 'Заявка на звонок' verbose_name_plural = 'Заявки на звонок' ordering = ['-time_create'] + + +class PageView(models.Model): + url = models.CharField(max_length=500) + timestamp = models.DateTimeField(default=timezone.now) + ip_address = models.GenericIPAddressField() + user_agent = models.TextField(blank=True) + referer = models.CharField(max_length=500, blank=True) + + class Meta: + indexes = [ + models.Index(fields=['url', 'timestamp']), + models.Index(fields=['timestamp']), + ] + + +class Visitor(models.Model): + ip_address = models.GenericIPAddressField() + first_visit = models.DateTimeField(default=timezone.now) + last_visit = models.DateTimeField(default=timezone.now) + visit_count = models.IntegerField(default=1) + + class Meta: + indexes = [ + models.Index(fields=['ip_address']), + ] diff --git a/OneCprogsite/programmer/templates/admin/base.html b/OneCprogsite/programmer/templates/admin/base.html new file mode 100644 index 0000000..c8f00f5 --- /dev/null +++ b/OneCprogsite/programmer/templates/admin/base.html @@ -0,0 +1,79 @@ + + + + + + {% block title %}Админ-панель - Статистика{% endblock %} + {% load django_bootstrap5 %} + {% bootstrap_css %} + + + +
+
+
+

{% block page_title %}Админ-панель{% endblock %}

+
+ На сайт + Django Admin + Выйти +
+
+
+
+ +
+ {% bootstrap_messages %} + {% block content %} + {% endblock %} +
+ + {% bootstrap_javascript %} + + \ No newline at end of file diff --git a/OneCprogsite/programmer/templates/admin/base_site.html b/OneCprogsite/programmer/templates/admin/base_site.html new file mode 100644 index 0000000..5bc87fe --- /dev/null +++ b/OneCprogsite/programmer/templates/admin/base_site.html @@ -0,0 +1,15 @@ +{% extends "admin/base.html" %} + +{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} + +{% block branding %} +

{{ site_header|default:_('Django administration') }}

+{% endblock %} + +{% block nav-global %} +{% endblock %} + +{% block userlinks %} + 📊 Статистика / + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/OneCprogsite/programmer/templates/admin/statistics.html b/OneCprogsite/programmer/templates/admin/statistics.html new file mode 100644 index 0000000..ca01039 --- /dev/null +++ b/OneCprogsite/programmer/templates/admin/statistics.html @@ -0,0 +1,100 @@ +{% extends 'admin/base.html' %} + +{% block title %}Статистика посещений{% endblock %} +{% block page_title %}Статистика посещений{% endblock %} + +{% block content %} +
+
+
+
+

📊 Просмотров сегодня

+

{{ today_views }}

+
+
+

📈 Просмотров за неделю

+

{{ weekly_views }}

+
+
+

👥 Уникальных посетителей

+

{{ unique_visitors }}

+
+
+

🕒 Всего просмотров

+

{{ total_views }}

+
+
+
+
+ +
+
+
+
+

🔥 Популярные страницы (за неделю)

+
+
+ + + + + + + + + {% for page in popular_pages %} + + + + + {% empty %} + + + + {% endfor %} + +
СтраницаПросмотров
+ {{ page.url }} + {% if page.url == '/' %} + Главная + {% endif %} + {{ page.views }}
Нет данных за выбранный период
+
+
+
+
+ +
+
+
+
+

📋 Последние посещения

+
+
+ + + + + + + + + + {% for view in recent_views %} + + + + + + {% empty %} + + + + {% endfor %} + +
ВремяСтраницаIP-адрес
{{ view.timestamp|date:"d.m.Y H:i" }}{{ view.url }}{{ view.ip_address }}
Нет данных
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/OneCprogsite/programmer/urls.py b/OneCprogsite/programmer/urls.py index 2bec85a..51e8c5a 100644 --- a/OneCprogsite/programmer/urls.py +++ b/OneCprogsite/programmer/urls.py @@ -12,6 +12,7 @@ urlpatterns = [ path('recall/', recall, name='recall'), path('post//', show_post, name='post'), path('callback/', callback_request, name='callback'), + path('admin/statistics/', statistics_view, name='statistics'), ] if settings.DEBUG: diff --git a/OneCprogsite/programmer/views.py b/OneCprogsite/programmer/views.py index b1c9d35..45725c4 100644 --- a/OneCprogsite/programmer/views.py +++ b/OneCprogsite/programmer/views.py @@ -1,10 +1,14 @@ from django.http import HttpResponse, HttpResponseNotFound -from django.shortcuts import render from .models import * from django.shortcuts import render, redirect from django.contrib import messages from .models import CallbackRequest # Импортируем из models, а не forms from .forms import CallbackForm +from django.utils import timezone +from datetime import timedelta +from .models import PageView, Visitor +from django.db.models import Count +from django.contrib.auth.decorators import login_required, user_passes_test menu = [ @@ -89,4 +93,58 @@ def callback_request(request): return redirect('home') # Если GET запрос, просто показываем главную страницу - return redirect('home') \ No newline at end of file + return redirect('home') + + +def is_admin(user): + return user.is_staff + + +def is_staff(user): + return user.is_staff + + +@login_required +@user_passes_test(is_staff) +def statistics_view(request): + today = timezone.now().date() + week_ago = today - timedelta(days=7) + + # Статистика за сегодня + today_views = PageView.objects.filter( + timestamp__date=today + ).count() + + # Статистика за неделю + weekly_views = PageView.objects.filter( + timestamp__date__gte=week_ago + ).count() + + # Всего просмотров + total_views = PageView.objects.count() + + # Популярные страницы за неделю + popular_pages = PageView.objects.filter( + timestamp__date__gte=week_ago + ).values('url').annotate( + views=Count('id') + ).order_by('-views')[:10] + + # Уникальные посетители за неделю + unique_visitors = Visitor.objects.filter( + last_visit__date__gte=week_ago + ).count() + + # Последние посещения + recent_views = PageView.objects.select_related().order_by('-timestamp')[:20] + + context = { + 'today_views': today_views, + 'weekly_views': weekly_views, + 'total_views': total_views, + 'unique_visitors': unique_visitors, + 'popular_pages': popular_pages, + 'recent_views': recent_views, + } + + return render(request, 'admin/statistics.html', context)