Добавил статистику на сайт

This commit is contained in:
NikDizell 2025-11-12 14:48:46 +03:00
parent cde6762f9e
commit 1360bc18e1
17 changed files with 389 additions and 2 deletions

View File

@ -50,6 +50,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'programmer.middleware.PageViewMiddleware',
] ]
ROOT_URLCONF = 'OneCprogsite.urls' ROOT_URLCONF = 'OneCprogsite.urls'

View File

@ -43,6 +43,17 @@ class CallbackAdmin(admin.ModelAdmin):
search_fields = ('name', 'phone', 'email') search_fields = ('name', 'phone', 'email')
readonly_fields = ('time_create',) 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(Competence, ProgrammerAdmin)
admin.site.register(Recall, RecallAdmin) admin.site.register(Recall, RecallAdmin)
admin.site.register(Solution, SolutionAdmin) admin.site.register(Solution, SolutionAdmin)

View File

@ -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

View File

@ -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')],
},
),
]

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
class Recall(models.Model): class Recall(models.Model):
@ -98,3 +99,29 @@ class CallbackRequest(models.Model):
verbose_name = 'Заявка на звонок' verbose_name = 'Заявка на звонок'
verbose_name_plural = 'Заявки на звонок' verbose_name_plural = 'Заявки на звонок'
ordering = ['-time_create'] 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']),
]

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Админ-панель - Статистика{% endblock %}</title>
{% load django_bootstrap5 %}
{% bootstrap_css %}
<style>
body {
background: #f8f9fa;
font-family: 'Inter', sans-serif;
}
.admin-header {
background: #343a40;
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #007bff;
margin: 0;
}
table {
width: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
th {
background: #f8f9fa;
font-weight: 600;
}
</style>
</head>
<body>
<div class="admin-header">
<div class="container">
<div class="d-flex justify-content-between align-items-center">
<h1>{% block page_title %}Админ-панель{% endblock %}</h1>
<div>
<a href="{% url 'home' %}" class="btn btn-outline-light btn-sm">На сайт</a>
<a href="{% url 'admin:index' %}" class="btn btn-outline-light btn-sm">Django Admin</a>
<a href="{% url 'admin:logout' %}" class="btn btn-outline-light btn-sm">Выйти</a>
</div>
</div>
</div>
</div>
<div class="container">
{% bootstrap_messages %}
{% block content %}
{% endblock %}
</div>
{% bootstrap_javascript %}
</body>
</html>

View File

@ -0,0 +1,15 @@
{% extends "admin/base.html" %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
{% endblock %}
{% block nav-global %}
{% endblock %}
{% block userlinks %}
<a href="{% url 'statistics' %}">📊 Статистика</a> /
{{ block.super }}
{% endblock %}

View File

@ -0,0 +1,100 @@
{% extends 'admin/base.html' %}
{% block title %}Статистика посещений{% endblock %}
{% block page_title %}Статистика посещений{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col-12">
<div class="stats-grid">
<div class="stat-card">
<h3>📊 Просмотров сегодня</h3>
<p class="stat-number">{{ today_views }}</p>
</div>
<div class="stat-card">
<h3>📈 Просмотров за неделю</h3>
<p class="stat-number">{{ weekly_views }}</p>
</div>
<div class="stat-card">
<h3>👥 Уникальных посетителей</h3>
<p class="stat-number">{{ unique_visitors }}</p>
</div>
<div class="stat-card">
<h3>🕒 Всего просмотров</h3>
<p class="stat-number">{{ total_views }}</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3>🔥 Популярные страницы (за неделю)</h3>
</div>
<div class="card-body p-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Страница</th>
<th>Просмотров</th>
</tr>
</thead>
<tbody>
{% for page in popular_pages %}
<tr>
<td>
<code>{{ page.url }}</code>
{% if page.url == '/' %}
<span class="badge bg-primary">Главная</span>
{% endif %}
</td>
<td><strong>{{ page.views }}</strong></td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center text-muted">Нет данных за выбранный период</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3>📋 Последние посещения</h3>
</div>
<div class="card-body p-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Время</th>
<th>Страница</th>
<th>IP-адрес</th>
</tr>
</thead>
<tbody>
{% for view in recent_views %}
<tr>
<td>{{ view.timestamp|date:"d.m.Y H:i" }}</td>
<td><code>{{ view.url }}</code></td>
<td><small>{{ view.ip_address }}</small></td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center text-muted">Нет данных</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -12,6 +12,7 @@ urlpatterns = [
path('recall/', recall, name='recall'), path('recall/', recall, name='recall'),
path('post/<int:post_id>/', show_post, name='post'), path('post/<int:post_id>/', show_post, name='post'),
path('callback/', callback_request, name='callback'), path('callback/', callback_request, name='callback'),
path('admin/statistics/', statistics_view, name='statistics'),
] ]
if settings.DEBUG: if settings.DEBUG:

View File

@ -1,10 +1,14 @@
from django.http import HttpResponse, HttpResponseNotFound from django.http import HttpResponse, HttpResponseNotFound
from django.shortcuts import render
from .models import * from .models import *
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from .models import CallbackRequest # Импортируем из models, а не forms from .models import CallbackRequest # Импортируем из models, а не forms
from .forms import CallbackForm 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 = [ menu = [
@ -89,4 +93,58 @@ def callback_request(request):
return redirect('home') return redirect('home')
# Если GET запрос, просто показываем главную страницу # Если GET запрос, просто показываем главную страницу
return redirect('home') 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)