Изменил наследования и отбрадение навигации
This commit is contained in:
parent
2cbc32d5b2
commit
8987720c94
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
34
programmer/mixins.py
Normal 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
96
programmer/services.py
Normal 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()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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'),
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user