Изменил наследования и отбрадение навигации

This commit is contained in:
NikDizell 2026-02-23 22:41:52 +03:00
parent 2cbc32d5b2
commit 8987720c94
8 changed files with 523 additions and 159 deletions

View File

@ -130,7 +130,6 @@ TEMPLATES = [
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'programmer.context_processors.menu_processor',
'programmer.context_processors.contact_info', 'programmer.context_processors.contact_info',
], ],
}, },
@ -149,7 +148,7 @@ DATABASES = {
'NAME': 'App', 'NAME': 'App',
'USER': 'postgres', 'USER': 'postgres',
'PASSWORD': 'NikDi94Zell', 'PASSWORD': 'NikDi94Zell',
'HOST': 'postgres', 'HOST': 'localhost',
'PORT': 5432, 'PORT': 5432,
} }
} }

View File

@ -1,4 +1,10 @@
{% extends 'programmer/base.html' %} {% extends 'programmer/base.html' %}
{% load static %}
{% load django_bootstrap5 %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
{% endblock %}
{% block content %} {% block content %}
<div class="page-header"> <div class="page-header">

View File

@ -1,4 +1,10 @@
{% extends 'programmer/base.html' %} {% extends 'programmer/base.html' %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
{% endblock %}
{% block content %} {% block content %}
<div class="page-header"> <div class="page-header">
<h1 class="page-title">Блог программиста 1С</h1> <h1 class="page-title">Блог программиста 1С</h1>

View File

@ -1,10 +1,6 @@
from .views import menu
from django.conf import settings from django.conf import settings
def menu_processor(request):
return {'menu': menu}
def contact_info(request): def contact_info(request):
return { return {
'CONTACT_EMAIL': getattr(settings, 'CONTACT_EMAIL', 'it@nserdyuk.ru'), 'CONTACT_EMAIL': getattr(settings, 'CONTACT_EMAIL', 'it@nserdyuk.ru'),

34
programmer/mixins.py Normal file
View File

@ -0,0 +1,34 @@
from typing import Any, Dict
from django.views.generic.base import ContextMixin
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):
"""
Миксин для добавления меню в контекст.
"""
menu = [
{'title': "Главная", 'url_name': 'home'},
{'title': "Проекты", 'url_name': 'solution'},
{'title': "Статьи", 'url_name': 'blog'},
{'title': "Отзывы", 'url_name': 'recall'},
{'title': "Обо мне", 'url_name': 'about'}
]
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context['menu'] = self.menu
return context

96
programmer/services.py Normal file
View File

@ -0,0 +1,96 @@
from typing import Optional
from django.http import HttpRequest
from .models import PageView, Visitor
from django.utils import timezone
from typing import Type
from django.db.models import Model, QuerySet
def get_client_ip(request: HttpRequest) -> str:
"""Получаем реальный IP клиента."""
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
def should_track_request(request: HttpRequest) -> bool:
"""Определяем, нужно ли отслеживать запрос."""
client_ip = get_client_ip(request)
path = request.path
# Игнорируемые пути
ignored_paths = [
'/static/', '/admin/', '/index.php', '/status.php',
'/cron', '/remote.php', '/ocs', '/apps/', '/custom_apps/',
]
# Игнорируемые IP (Docker сети)
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):
return False
if client_ip in docker_ips:
return False
return True
def track_page_view(request: HttpRequest) -> None:
"""Основная функция отслеживания просмотров."""
if not should_track_request(request):
return
try:
# Сохраняем просмотр страницы
PageView.objects.create(
url=request.path,
ip_address=get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', '')[:500],
referer=request.META.get('HTTP_REFERER', '')[:500],
)
# Обновляем статистику посетителя
ip = 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_published_queryset(model_class, order_by: str = '-time_create'):
"""
Возвращает QuerySet опубликованных записей для модели.
Args:
model_class: Класс модели Django
order_by: Поле для сортировки
Returns:
QuerySet или пустой список, если поле is_published отсутствует
"""
if hasattr(model_class, 'is_published'):
return model_class.objects.filter(is_published=True).order_by(order_by)
return model_class.objects.none()

View File

@ -5,16 +5,17 @@ from django.conf.urls.static import static
from .views import * from .views import *
from django.contrib.sitemaps.views import sitemap from django.contrib.sitemaps.views import sitemap
from .sitemaps import sitemaps from .sitemaps import sitemaps
from blog import views from blog.views import ArticleListView
from . import views
urlpatterns = [ urlpatterns = [
path('', index, name='home'), path('', views.HomePageView.as_view(), name='home'),
path('about/', about, name='about'), path('about/', views.AboutPageView.as_view(), name='about'),
path('solutions/', solution, name='solution'), path('solutions/', views.SolutionListView.as_view(), name='solution'),
# path('competence/', ability, name='ability'), # path('competence/', ability, name='ability'),
path('recall/', recall, name='recall'), path('recall/', views.RecallListView.as_view(), name='recall'),
path('blog/', views.ArticleListView.as_view(), name='blog'), path('blog/', ArticleListView.as_view(), name='blog'),
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'), path('admin/statistics/', statistics_view, name='statistics'),

View File

@ -1,28 +1,254 @@
from django.http import HttpResponse, HttpResponseNotFound from django.http import HttpResponse, HttpResponseNotFound, HttpRequest
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 Home, Solution, Recall, PageView, Visitor, CallbackRequest
from .forms import CallbackForm from .forms import CallbackForm
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
from .models import PageView, Visitor from .models import PageView, Visitor
from django.db.models import Count 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 from django.views.decorators.http import require_GET, require_POST
from django.views.generic import TemplateView, ListView
from .mixins import PageViewTrackingMixin, MenuContextMixin
from .services import get_published_queryset, track_page_view
from typing import Any, Dict, Type
menu = [ # menu = [
{'title': "Главная", 'url_name': 'home'}, # {'title': "Главная", 'url_name': 'home'},
{'title': "Проекты", 'url_name': 'solution'}, # {'title': "Проекты", 'url_name': 'solution'},
# {'title': "Компетенции", 'url_name': 'ability'}, # # {'title': "Компетенции", 'url_name': 'ability'},
{'title': "Отзывы", 'url_name': 'recall'}, # {'title': "Отзывы", 'url_name': 'recall'},
{'title': "Статьи", 'url_name': 'blog'}, # {'title': "Статьи", 'url_name': 'blog'},
{'title': "Обо мне", 'url_name': 'about'} # {'title': "Обо мне", 'url_name': 'about'}
] # ]
# === ДОБАВЬТЕ ЭТИ ФУНКЦИИ ЗДЕСЬ === class BasePageView(PageViewTrackingMixin, MenuContextMixin, TemplateView):
"""
Базовый класс для всех страниц сайта.
Включает отслеживание просмотров и контекст меню.
"""
extra_context = {}
class BaseListView(PageViewTrackingMixin, MenuContextMixin, ListView):
"""
Базовый класс для всех страниц со списками объектов.
Включает отслеживание просмотров и контекст меню.
"""
extra_context = {}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.extra_context)
return context
class HomePageView(BasePageView):
"""Главная страница с формой обратной связи."""
template_name = 'programmer/index.html'
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
# Получаем контент для главной страницы
posts = get_published_queryset(Home)
context.update({
'posts': posts,
'form': CallbackForm(),
'title': "Программист 1С Николай Сердюк - разработка и сопровождение",
'meta_description': (
"Профессиональный программист 1С с более чем 10-летним опытом. "
"Разработка, доработка, обновление и интеграция систем 1С. Сопровождение 1С."
),
'meta_keywords': (
"программист 1С, разработка 1С, обновление 1С, сопровождение 1С, "
"интеграция 1С, доработка 1С, 1С предприятие 8.3"
),
})
return context
class AboutPageView(BasePageView):
"""Страница 'О себе'."""
template_name = 'programmer/about.html'
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context.update({
'title': "Программист 1С Николай Сердюк - 10+ лет опыта | Услуги 1С",
'meta_description': (
"Николай Сердюк - сертифицированный программист 1С с 10+ лет опыта. "
"Специализация: обновление 1С, разработка под ключ, интеграция, миграция с 1С 7.7."
),
'meta_keywords': (
"программист 1С Николай Сердюк, обновление 1С, разработка 1С под ключ, "
"интеграция 1С, сертифицированный 1С, миграция 1С 7.7"
),
})
return context
class SolutionListView(BaseListView):
"""Список проектов с пагинацией."""
model = Solution
template_name = 'programmer/solution.html'
context_object_name = 'posts'
paginate_by = 1 # Количество проектов на странице
ordering = ['-time_create']
def get_queryset(self) -> QuerySet:
"""Возвращает только опубликованные проекты."""
return get_published_queryset(self.model, order_by='-time_create')
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context.update({
'title': "Проекты автоматизации 1С | Реализованные кейсы и решения",
'meta_description': (
"Реализованные проекты по автоматизации 1С: складской учет с ТСД, "
"интеграция с оборудованием, миграция с 1С 7.7. Примеры работ и кейсы."
),
'meta_keywords': (
"проекты 1С, автоматизация склада 1С, интеграция ТСД 1С, "
"миграция 1С 7.7, кейсы 1С, примеры работ 1С"
),
})
return context
class RecallListView(BaseListView):
"""Список отзывов с пагинацией."""
model = Recall
template_name = 'programmer/recall.html'
context_object_name = 'posts'
paginate_by = 5
ordering = ['-time_create']
def get_queryset(self) -> QuerySet:
"""Возвращает только опубликованные отзывы."""
return get_published_queryset(self.model, order_by='-time_create')
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context.update({
'title': "Отзывы клиентов о работе программиста 1С | Реальные кейсы",
'meta_description': (
"Реальные отзывы клиентов о работе программиста 1С Николая Сердюка. "
"Отзывы от ООО «РОВЕН-Регионы» и других компаний."
),
'meta_keywords': (
"отзывы программист 1С, рекомендации 1С, отзывы клиентов 1С, "
"реальные кейсы 1С, отзыв ООО РОВЕН"
),
})
return context
@require_POST
def callback_request(request: HttpRequest) -> HttpResponse:
"""
Обработка заявки на обратный звонок.
Принимает только POST запросы.
"""
form = CallbackForm(request.POST)
if form.is_valid():
# Сохраняем заявку
callback = form.save()
# TODO: В будущем можно добавить асинхронную отправку email через Celery
# from .tasks import send_callback_email
# send_callback_email.delay(callback.id)
messages.success(
request,
'✅ Ваша заявка успешно отправлена! Я свяжусь с вами в ближайшее время.'
)
else:
# Собираем все ошибки формы
for field, errors in form.errors.items():
field_label = form.fields[field].label if field in form.fields else field
for error in errors:
messages.error(
request,
f'❌ Ошибка в поле "{field_label}": {error}'
)
return redirect('home')
def show_post(request: HttpRequest, post_id: int) -> HttpResponse:
"""
Временная функция для отображения детальной страницы поста.
TODO: Реализовать полноценный DetailView для каждой модели.
"""
return HttpResponse(f"Отображение поста № {post_id}")
def pageNotFound(request: HttpRequest, exception: Exception) -> HttpResponseNotFound:
"""Обработчик 404 ошибки."""
return HttpResponseNotFound('<h1>Страница не найдена</h1>')
def is_staff_user(user) -> bool:
"""Проверка, является ли пользователь сотрудником (для декоратора)."""
return user.is_staff
@login_required
@user_passes_test(is_staff_user)
def statistics_view(request: HttpRequest) -> HttpResponse:
"""
Статистика сайта для администраторов.
TODO: Переделать на класс-based view с отдельным шаблоном.
"""
today = timezone.now().date()
week_ago = today - timedelta(days=7)
# Базовая статистика просмотров
stats = {
'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(),
'unique_visitors': Visitor.objects.filter(last_visit__date__gte=week_ago).count(),
}
# Популярные страницы за неделю
stats['popular_pages'] = (
PageView.objects
.filter(timestamp__date__gte=week_ago)
.values('url')
.annotate(views=Count('id'))
.order_by('-views')[:10]
)
# Последние посещения
stats['recent_views'] = PageView.objects.order_by('-timestamp')[:20]
# Статистика заявок
stats.update({
'total_callbacks': CallbackRequest.objects.count(),
'today_callbacks': CallbackRequest.objects.filter(time_create__date=today).count(),
'unread_callbacks': CallbackRequest.objects.filter(is_read=False).count(),
})
return render(request, 'admin/statistics.html', stats)
@require_GET
def robots_txt(request: HttpRequest) -> HttpResponse:
"""Отдает robots.txt."""
return render(request, 'robots.txt', content_type='text/plain')
def get_client_ip(request): def get_client_ip(request):
"""Получаем реальный IP клиента""" """Получаем реальный IP клиента"""
@ -102,159 +328,159 @@ def track_view(view_func):
return _wrapped_view return _wrapped_view
@track_view # @track_view
def index(request): # def index(request):
posts = Home.objects.filter(is_published=True) # posts = Home.objects.filter(is_published=True)
context = { # context = {
'posts': posts, # 'posts': posts,
'menu': menu, # 'menu': menu,
'title': "Программист 1С Николай Сердюк - разработка и сопровождение", # 'title': "Программист 1С Николай Сердюк - разработка и сопровождение",
'meta_description': "Профессиональный программист 1С с более чем 10-летним опытом. Разработка, доработка, обновление и интеграция систем 1С. Сопровождение 1С.", # 'meta_description': "Профессиональный программист 1С с более чем 10-летним опытом. Разработка, доработка, обновление и интеграция систем 1С. Сопровождение 1С.",
'meta_keywords': "программист 1С, разработка 1С, обновление 1С, сопровождение 1С, интеграция 1С, доработка 1С, 1С предприятие 8.3", # 'meta_keywords': "программист 1С, разработка 1С, обновление 1С, сопровождение 1С, интеграция 1С, доработка 1С, 1С предприятие 8.3",
'form': CallbackForm() # 'form': CallbackForm()
} # }
return render(request, 'programmer/index.html', context=context) # return render(request, 'programmer/index.html', context=context)
@track_view # @track_view
def about(request): # def about(request):
context = { # context = {
'menu': menu, # 'menu': menu,
'title': "Программист 1С Николай Сердюк - 10+ лет опыта | Услуги 1С", # 'title': "Программист 1С Николай Сердюк - 10+ лет опыта | Услуги 1С",
'meta_description': "Николай Сердюк - сертифицированный программист 1С с 10+ лет опыта. Специализация: обновление 1С, разработка под ключ, интеграция, миграция с 1С 7.7.", # 'meta_description': "Николай Сердюк - сертифицированный программист 1С с 10+ лет опыта. Специализация: обновление 1С, разработка под ключ, интеграция, миграция с 1С 7.7.",
'meta_keywords': "программист 1С Николай Сердюк, обновление 1С, разработка 1С под ключ, интеграция 1С, сертифицированный 1С, миграция 1С 7.7" # 'meta_keywords': "программист 1С Николай Сердюк, обновление 1С, разработка 1С под ключ, интеграция 1С, сертифицированный 1С, миграция 1С 7.7"
} # }
return render(request, 'programmer/about.html', context=context) # return render(request, 'programmer/about.html', context=context)
@track_view # @track_view
def solution(request): # def solution(request):
posts = Solution.objects.filter(is_published=True) # posts = Solution.objects.filter(is_published=True)
context = { # context = {
'posts': posts, # 'posts': posts,
'menu': menu, # 'menu': menu,
'meta_description': "Реализованные проекты по автоматизации 1С: складской учет с ТСД, интеграция с оборудованием, миграция с 1С 7.7. Примеры работ и кейсы.", # 'meta_description': "Реализованные проекты по автоматизации 1С: складской учет с ТСД, интеграция с оборудованием, миграция с 1С 7.7. Примеры работ и кейсы.",
'meta_keywords': "проекты 1С, автоматизация склада 1С, интеграция ТСД 1С, миграция 1С 7.7, кейсы 1С, примеры работ 1С", # 'meta_keywords': "проекты 1С, автоматизация склада 1С, интеграция ТСД 1С, миграция 1С 7.7, кейсы 1С, примеры работ 1С",
'title': "Проекты автоматизации 1С | Реализованные кейсы и решения", # 'title': "Проекты автоматизации 1С | Реализованные кейсы и решения",
} # }
return render(request, 'programmer/solution.html', context=context) # return render(request, 'programmer/solution.html', context=context)
@track_view # @track_view
def ability(request): # def ability(request):
posts = Competence.objects.filter(is_published=True) # posts = Competence.objects.filter(is_published=True)
context = { # context = {
'posts': posts, # 'posts': posts,
'menu': menu, # 'menu': menu,
'title': "Сертификаты и компетенции 1С | Программист 1С Николай Сердюк", # 'title': "Сертификаты и компетенции 1С | Программист 1С Николай Сердюк",
'meta_description': "Сертификаты 1С: Профессионал по платформе 8.3 и БП 3.0. Подтвержденная квалификация программиста 1С с сертификатами фирмы 1С.", # 'meta_description': "Сертификаты 1С: Профессионал по платформе 8.3 и БП 3.0. Подтвержденная квалификация программиста 1С с сертификатами фирмы 1С.",
'meta_keywords': "сертификаты 1С, 1С профессионал, компетенции 1С, квалификация программиста 1С, сертифицированный специалист 1С" # 'meta_keywords': "сертификаты 1С, 1С профессионал, компетенции 1С, квалификация программиста 1С, сертифицированный специалист 1С"
} # }
return render(request, 'programmer/competence.html', context=context) # return render(request, 'programmer/competence.html', context=context)
@track_view # @track_view
def recall(request): # def recall(request):
posts = Recall.objects.filter(is_published=True) # posts = Recall.objects.filter(is_published=True)
context = { # context = {
'posts': posts, # 'posts': posts,
'menu': menu, # 'menu': menu,
'title': "Отзывы клиентов о работе программиста 1С | Реальные кейсы", # 'title': "Отзывы клиентов о работе программиста 1С | Реальные кейсы",
'meta_description': "Реальные отзывы клиентов о работе программиста 1С Николая Сердюка. Отзывы от ООО «РОВЕН-Регионы» и других компаний.", # 'meta_description': "Реальные отзывы клиентов о работе программиста 1С Николая Сердюка. Отзывы от ООО «РОВЕН-Регионы» и других компаний.",
'meta_keywords': "отзывы программист 1С, рекомендации 1С, отзывы клиентов 1С, реальные кейсы 1С, отзыв ООО РОВЕН" # 'meta_keywords': "отзывы программист 1С, рекомендации 1С, отзывы клиентов 1С, реальные кейсы 1С, отзыв ООО РОВЕН"
} # }
return render(request, 'programmer/recall.html', context=context) # return render(request, 'programmer/recall.html', context=context)
def show_post(request, post_id): # def show_post(request, post_id):
return HttpResponse(f"Отображение № {post_id}") # return HttpResponse(f"Отображение № {post_id}")
def pageNotFound(request, exception): # def pageNotFound(request, exception):
return HttpResponseNotFound('<h1>Страница не найдена</h1>') # return HttpResponseNotFound('<h1>Страница не найдена</h1>')
def callback_request(request): # def callback_request(request):
if request.method == 'POST': # if request.method == 'POST':
form = CallbackForm(request.POST) # form = CallbackForm(request.POST)
if form.is_valid(): # if form.is_valid():
# Сохраняем заявку через форму # # Сохраняем заявку через форму
form.save() # form.save()
messages.success(request, '✅ Ваша заявка успешно отправлена! Я свяжусь с вами в ближайшее время.') # messages.success(request, '✅ Ваша заявка успешно отправлена! Я свяжусь с вами в ближайшее время.')
return redirect('home') # return redirect('home')
else: # else:
# Если форма невалидна, показываем ошибки # # Если форма невалидна, показываем ошибки
for field, errors in form.errors.items(): # for field, errors in form.errors.items():
for error in errors: # for error in errors:
messages.error(request, f'❌ Ошибка в поле {form.fields[field].label}: {error}') # messages.error(request, f'❌ Ошибка в поле {form.fields[field].label}: {error}')
return redirect('home') # return redirect('home')
#
# Если GET запрос, просто показываем главную страницу # # Если GET запрос, просто показываем главную страницу
return redirect('home') # return redirect('home')
def is_admin(user): # def is_admin(user):
return user.is_staff # return user.is_staff
def is_staff(user): # def is_staff(user):
return user.is_staff # return user.is_staff
@login_required # @login_required
@user_passes_test(is_staff) # @user_passes_test(is_staff)
def statistics_view(request): # def statistics_view(request):
today = timezone.now().date() # today = timezone.now().date()
week_ago = today - timedelta(days=7) # week_ago = today - timedelta(days=7)
#
# Статистика за сегодня # # Статистика за сегодня
today_views = PageView.objects.filter( # today_views = PageView.objects.filter(
timestamp__date=today # timestamp__date=today
).count() # ).count()
#
# Статистика за неделю # # Статистика за неделю
weekly_views = PageView.objects.filter( # weekly_views = PageView.objects.filter(
timestamp__date__gte=week_ago # timestamp__date__gte=week_ago
).count() # ).count()
#
# Всего просмотров # # Всего просмотров
total_views = PageView.objects.count() # total_views = PageView.objects.count()
#
# Популярные страницы за неделю # # Популярные страницы за неделю
popular_pages = PageView.objects.filter( # popular_pages = PageView.objects.filter(
timestamp__date__gte=week_ago # timestamp__date__gte=week_ago
).values('url').annotate( # ).values('url').annotate(
views=Count('id') # views=Count('id')
).order_by('-views')[:10] # ).order_by('-views')[:10]
#
# Уникальные посетители за неделю # # Уникальные посетители за неделю
unique_visitors = Visitor.objects.filter( # unique_visitors = Visitor.objects.filter(
last_visit__date__gte=week_ago # last_visit__date__gte=week_ago
).count() # ).count()
#
# Последние посещения # # Последние посещения
recent_views = PageView.objects.select_related().order_by('-timestamp')[:20] # recent_views = PageView.objects.select_related().order_by('-timestamp')[:20]
#
today = timezone.now().date() # today = timezone.now().date()
total_callbacks = CallbackRequest.objects.count() # total_callbacks = CallbackRequest.objects.count()
today_callbacks = CallbackRequest.objects.filter(time_create__date=today).count() # today_callbacks = CallbackRequest.objects.filter(time_create__date=today).count()
unread_callbacks = CallbackRequest.objects.filter(is_read=False).count() # unread_callbacks = CallbackRequest.objects.filter(is_read=False).count()
#
context = { # context = {
'today_views': today_views, # 'today_views': today_views,
'weekly_views': weekly_views, # 'weekly_views': weekly_views,
'total_views': total_views, # 'total_views': total_views,
'unique_visitors': unique_visitors, # 'unique_visitors': unique_visitors,
'popular_pages': popular_pages, # 'popular_pages': popular_pages,
'recent_views': recent_views, # 'recent_views': recent_views,
'total_callbacks': total_callbacks, # 'total_callbacks': total_callbacks,
'today_callbacks': today_callbacks, # 'today_callbacks': today_callbacks,
'unread_callbacks': unread_callbacks, # 'unread_callbacks': unread_callbacks,
} # }
#
return render(request, 'admin/statistics.html', context) # return render(request, 'admin/statistics.html', context)
@require_GET # @require_GET
def robots_txt(request): # def robots_txt(request):
return render(request, 'robots.txt', content_type='text/plain') # return render(request, 'robots.txt', content_type='text/plain')