Убрал комментарии слияния

This commit is contained in:
NikDizell 2025-12-08 15:31:10 +03:00
parent decb9471bd
commit 1219966c84
56 changed files with 6 additions and 2403 deletions

7
.gitignore vendored
View File

@ -21,4 +21,9 @@ ENV/
# OS
.DS_Store
Thumbs.db
Thumbs.db
# Project specific
*.log
*.pot
*.py[co]

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
from django.contrib import admin
from django.utils import timezone
from django.utils.html import format_html
@ -168,174 +167,3 @@ admin.site.register(Competence, ProgrammerAdmin)
admin.site.register(Recall, RecallAdmin)
admin.site.register(Solution, SolutionAdmin)
admin.site.register(Home, HomeAdmin)
=======
from django.contrib import admin
from django.utils import timezone
from django.utils.html import format_html
from django.urls import path
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.contrib import messages
from .models import *
class ProgrammerAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class RecallAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'scan', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class SolutionAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'description', 'implementation')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class HomeAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
@admin.register(CallbackRequest)
class CallbackAdmin(admin.ModelAdmin):
list_display = ('name', 'phone', 'email', 'time_create', 'is_processed', 'is_read', 'new_badge')
list_display_links = ('name', 'phone')
list_editable = ('is_processed', 'is_read')
list_filter = ('time_create', 'is_processed', 'is_read')
search_fields = ('name', 'phone', 'email')
readonly_fields = ('time_create',)
actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed']
actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed', 'resend_notification']
def resend_notification(self, request, queryset):
from .utils.email_notifications import send_callback_notification
count = 0
for callback in queryset:
success = send_callback_notification(callback)
if success:
count += 1
self.message_user(request, f'Уведомления отправлены для {count} заявок')
resend_notification.short_description = "Переотправить email уведомления"
def new_badge(self, obj):
if not obj.is_read:
return format_html('<span style="color: red; font-weight: bold;">🆕 НОВАЯ</span>')
return ""
new_badge.short_description = 'Статус'
def get_queryset(self, request):
# Показываем количество непрочитанных в заголовке
unread_count = CallbackRequest.objects.filter(is_read=False).count()
if unread_count > 0:
self.message_user(
request,
f'У вас {unread_count} непрочитанных заявок!',
messages.WARNING
)
return super().get_queryset(request)
def mark_as_read(self, request, queryset):
updated = queryset.update(is_read=True)
self.message_user(request, f'{updated} заявок отмечены как прочитанные')
mark_as_read.short_description = "Отметить как прочитанные"
def mark_as_unread(self, request, queryset):
updated = queryset.update(is_read=False)
self.message_user(request, f'{updated} заявок отмечены как непрочитанные')
mark_as_unread.short_description = "Отметить как непрочитанные"
def mark_as_processed(self, request, queryset):
updated = queryset.update(is_processed=True)
self.message_user(request, f'{updated} заявок отмечены как обработанные')
mark_as_processed.short_description = "Отметить как обработанные"
# Добавляем кастомное представление для статистики
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('callback-stats/', self.admin_site.admin_view(self.callback_stats), name='callback_stats'),
]
return custom_urls + urls
def callback_stats(self, request):
today = timezone.now().date()
week_ago = today - timezone.timedelta(days=7)
stats = {
'total': CallbackRequest.objects.count(),
'today': CallbackRequest.objects.filter(time_create__date=today).count(),
'week': CallbackRequest.objects.filter(time_create__date__gte=week_ago).count(),
'unread': CallbackRequest.objects.filter(is_read=False).count(),
'unprocessed': CallbackRequest.objects.filter(is_processed=False).count(),
}
context = {
**self.admin_site.each_context(request),
'title': 'Статистика заявок',
'stats': stats,
}
return render(request, 'admin/callback_stats.html', context)
class ProgrammerAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'photo', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class RecallAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'scan', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class SolutionAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'description', 'implementation')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
class HomeAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'time_create', 'is_published')
list_display_links = ('id', 'title')
search_fields = ('title', 'content')
list_editable = ('is_published',)
list_filter = ('time_create', 'is_published')
@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)
admin.site.register(Home, HomeAdmin)
>>>>>>> master

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
from django.apps import AppConfig
@ -6,12 +5,3 @@ class ProgrammerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'programmer'
verbose_name = 'Программисты'
=======
from django.apps import AppConfig
class ProgrammerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'programmer'
verbose_name = 'Программисты'
>>>>>>> master

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
from .views import menu
from django.conf import settings
@ -10,17 +9,4 @@ def contact_info(request):
return {
'CONTACT_EMAIL': getattr(settings, 'CONTACT_EMAIL', 'it@nserdyuk.ru'),
'CONTACT_PHONE': getattr(settings, 'CONTACT_PHONE', '+7 (960) 469-40-88'),
=======
from .views import menu
from django.conf import settings
def menu_processor(request):
return {'menu': menu}
def contact_info(request):
return {
'CONTACT_EMAIL': getattr(settings, 'CONTACT_EMAIL', 'it@nserdyuk.ru'),
'CONTACT_PHONE': getattr(settings, 'CONTACT_PHONE', '+7 (960) 469-40-88'),
>>>>>>> master
}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
from django import forms
from .models import CallbackRequest
@ -31,38 +30,4 @@ class CallbackForm(forms.ModelForm):
'phone': 'Телефон',
'email': 'Электронная почта',
'question': 'Ваш вопрос'
=======
from django import forms
from .models import CallbackRequest
class CallbackForm(forms.ModelForm):
class Meta:
model = CallbackRequest
fields = ['name', 'phone', 'email', 'question']
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-input',
'placeholder': 'Ваше имя'
}),
'phone': forms.TextInput(attrs={
'class': 'form-input',
'placeholder': '+7 (___) ___-__-__'
}),
'email': forms.EmailInput(attrs={
'class': 'form-input',
'placeholder': 'your@email.com'
}),
'question': forms.Textarea(attrs={
'class': 'form-textarea',
'placeholder': 'Опишите ваш вопрос или задачу...',
'rows': 4
}),
}
labels = {
'name': 'Имя',
'phone': 'Телефон',
'email': 'Электронная почта',
'question': 'Ваш вопрос'
>>>>>>> master
}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
from .models import PageView, Visitor
from django.utils import timezone
from django.db import transaction
@ -52,59 +51,4 @@ class PageViewMiddleware:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
=======
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')
>>>>>>> master
return ip

View File

@ -1,159 +1,3 @@
<<<<<<< HEAD
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from .utils.email_notifications import send_callback_notification
class Recall(models.Model):
title = models.CharField(max_length=255, verbose_name='Организация')
content = models.TextField(blank=True, verbose_name='Отзыв')
scan = models.ImageField(upload_to="scan/%Y/%m/%d/", verbose_name='Фото')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Отзыв'
verbose_name_plural = 'Отзывы'
ordering = ['time_create', 'title']
class Competence(models.Model):
title = models.CharField(max_length=255, verbose_name='Программист')
content = models.TextField(blank=True, verbose_name='Компетенция')
photo = models.ImageField(upload_to="photos/%Y/%m/%d/", verbose_name='Фото')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Компетенция'
verbose_name_plural = 'Компетенции'
ordering = ['time_create', 'title']
class Solution(models.Model):
title = models.CharField(max_length=255, verbose_name='Наименование')
description = models.TextField(blank=True, verbose_name='Описание')
implementation = models.TextField(blank=True, verbose_name='Реализация')
closing = models.TextField(blank=True, verbose_name='Заключение')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Проекты'
verbose_name_plural = 'Проекты'
ordering = ['time_create', 'title']
class Home(models.Model):
title = models.CharField(max_length=255, verbose_name='Наименование')
content = models.TextField(blank=True, verbose_name='Статья')
home_image = models.ImageField(upload_to="home_image/%Y/%m/%d/", verbose_name='Фото')
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', kwargs={'post_id': self.pk})
class Meta:
verbose_name = 'Главная страница'
verbose_name_plural = 'Главная страница'
ordering = ['time_create', 'title']
class CallbackRequest(models.Model):
name = models.CharField(max_length=100, verbose_name='Имя')
phone = models.CharField(max_length=20, verbose_name='Телефон')
email = models.EmailField(blank=True, null=True, verbose_name='Электронная почта') # Сделать необязательным
question = models.TextField(blank=True, verbose_name='Ваш вопрос') # Сделать необязательным
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
is_processed = models.BooleanField(default=False, verbose_name='Обработано')
is_read = models.BooleanField(default=False, verbose_name='Прочитано')
notification_sent = models.BooleanField(default=False, verbose_name='Уведомление отправлено')
def __str__(self):
return f"{self.name} - {self.phone}"
class Meta:
verbose_name = 'Заявка на звонок'
verbose_name_plural = 'Заявки на звонок'
ordering = ['-time_create']
# Сигнал для отправки уведомления при создании заявки
@receiver(post_save, sender=CallbackRequest)
def send_callback_email_notification(sender, instance, created, **kwargs):
if created and not instance.notification_sent:
# Отправляем email уведомление
success = send_callback_notification(instance)
if success:
instance.notification_sent = True
# Сохраняем без повторного вызова сигнала
sender.objects.filter(pk=instance.pk).update(notification_sent=True)
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']),
]
@receiver([post_save, post_delete], sender=Home)
@receiver([post_save, post_delete], sender=Solution)
@receiver([post_save, post_delete], sender=Competence)
@receiver([post_save, post_delete], sender=Recall)
def clear_sitemap_cache(sender, **kwargs):
"""Очищаем кэш sitemap при изменении контента"""
cache.delete('sitemap_cache')
=======
from django.db import models
from django.urls import reverse
from django.utils import timezone
@ -334,4 +178,3 @@ class Visitor(models.Model):
def clear_sitemap_cache(sender, **kwargs):
"""Очищаем кэш sitemap при изменении контента"""
cache.delete('sitemap_cache')
>>>>>>> master

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from .models import Home, Solution, Competence, Recall
@ -62,69 +61,4 @@ sitemaps = {
'solutions': SolutionSitemap,
'competence': CompetenceSitemap,
'recall': RecallSitemap,
=======
from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from .models import Home, Solution, Competence, Recall
class StaticViewSitemap(Sitemap):
priority = 1.0
changefreq = 'monthly'
def items(self):
return ['home', 'about', 'solution', 'ability', 'recall']
def location(self, item):
return reverse(item)
class HomeSitemap(Sitemap):
changefreq = 'weekly'
priority = 1.0
def items(self):
return Home.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
# УБИРАЕМ метод location - используем default
# Django автоматически сгенерирует правильные URL
class SolutionSitemap(Sitemap):
changefreq = 'weekly'
priority = 0.9
def items(self):
return Solution.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
class CompetenceSitemap(Sitemap):
changefreq = 'monthly'
priority = 0.8
def items(self):
return Competence.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
class RecallSitemap(Sitemap):
changefreq = 'monthly'
priority = 0.7
def items(self):
return Recall.objects.filter(is_published=True)
def lastmod(self, obj):
return obj.time_update
# Упрощаем sitemaps - убираем HomeSitemap если он дублирует главную
sitemaps = {
'static': StaticViewSitemap,
'solutions': SolutionSitemap,
'competence': CompetenceSitemap,
'recall': RecallSitemap,
>>>>>>> master
}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
<!DOCTYPE html>
<html lang="ru">
<head>
@ -77,84 +76,4 @@
{% bootstrap_javascript %}
</body>
=======
<!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>
>>>>>>> master
</html>

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
{% extends "admin/base.html" %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
@ -24,31 +23,4 @@
<a href="{% url 'callback_stats' %}">📊 Статистика заявок</a> /
<a href="{% url 'statistics' %}">📈 Посещения</a> /
{{ block.super }}
=======
{% 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 %}
{% load programmer_tags %}
<!-- Уведомление о новых заявках -->
{% get_unread_callbacks as unread_callbacks %}
{% if unread_callbacks %}
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" style="color: #dc3545; font-weight: bold;">
🚨 {{ unread_callbacks }} новых заявок
</a> /
{% endif %}
<a href="{% url 'callback_stats' %}">📊 Статистика заявок</a> /
<a href="{% url 'statistics' %}">📈 Посещения</a> /
{{ block.super }}
>>>>>>> master
{% endblock %}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
{% extends "admin/base_site.html" %}
{% block title %}Статистика заявок{% endblock %}
@ -43,50 +42,4 @@
</a>
</div>
</div>
=======
{% extends "admin/base_site.html" %}
{% block title %}Статистика заявок{% endblock %}
{% block content %}
<div class="module">
<h1>📊 Статистика заявок на обратный звонок</h1>
<div class="stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin: 2rem 0;">
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>📋 Всего заявок</h3>
<p style="font-size: 2rem; font-weight: bold; color: #007bff; margin: 0;">{{ stats.total }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>📅 Сегодня</h3>
<p style="font-size: 2rem; font-weight: bold; color: #28a745; margin: 0;">{{ stats.today }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>📈 За неделю</h3>
<p style="font-size: 2rem; font-weight: bold; color: #17a2b8; margin: 0;">{{ stats.week }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>🆕 Непрочитанные</h3>
<p style="font-size: 2rem; font-weight: bold; color: #dc3545; margin: 0;">{{ stats.unread }}</p>
</div>
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
<h3>В обработке</h3>
<p style="font-size: 2rem; font-weight: bold; color: #ffc107; margin: 0;">{{ stats.unprocessed }}</p>
</div>
</div>
<div style="margin-top: 2rem;">
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" class="button" style="background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
📋 Перейти к списку заявок
</a>
<a href="{% url 'admin:index' %}" class="button" style="background: #6c757d; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin-left: 10px;">
🏠 На главную админки
</a>
</div>
</div>
>>>>>>> master
{% endblock %}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
{% extends 'admin/base.html' %}
{% load programmer_tags %}
@ -129,136 +128,4 @@
</div>
</div>
</div>
=======
{% extends 'admin/base.html' %}
{% load programmer_tags %}
{% block title %}Статистика посещений{% endblock %}
{% block page_title %}Статистика посещений{% endblock %}
{% block content %}
<!-- Уведомления о заявках -->
{% get_unread_callbacks as unread_callbacks %}
{% get_today_callbacks as today_callbacks %}
{% if unread_callbacks > 0 %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<h4>🚨 Внимание!</h4>
<p>
У вас <strong>{{ unread_callbacks }}</strong> непрочитанных заявок на обратный звонок
{% if today_callbacks > 0 %}
({{ today_callbacks }} из них сегодня)
{% endif %}
</p>
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" class="btn btn-warning btn-sm">
📋 Перейти к заявкам
</a>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<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 class="stat-card">
<h3>📞 Заявок сегодня</h3>
<p class="stat-number">{{ today_callbacks }}</p>
</div>
<div class="stat-card">
<h3>📋 Всего заявок</h3>
<p class="stat-number">{% get_unread_callbacks %}/{{ total_callbacks }}</p>
<small>(непрочитанные/всего)</small>
</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>
>>>>>>> master
{% endblock %}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
<!-- templates/emails/callback_notification.html -->
<!DOCTYPE html>
<html>
@ -55,62 +54,4 @@
</div>
</div>
</body>
=======
<!-- templates/emails/callback_notification.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #FF6B00 0%, #0055A5 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
.info-box { background: white; padding: 15px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #007bff; }
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚨 Новая заявка на сайте</h1>
<p>Требуется ваше внимание!</p>
</div>
<div class="content">
<div class="alert">
<strong>⚠️ Срочно!</strong> Пользователь оставил заявку на обратный звонок.
</div>
<div class="info-box">
<h3>📋 Информация о заявке:</h3>
<p><strong>👤 Имя:</strong> {{ callback.name }}</p>
<p><strong>📞 Телефон:</strong> {{ callback.phone }}</p>
{% if callback.email %}
<p><strong>📧 Email:</strong> {{ callback.email }}</p>
{% endif %}
{% if callback.question %}
<p><strong>❓ Вопрос:</strong><br>{{ callback.question }}</p>
{% endif %}
<p><strong>🕒 Время отправки:</strong> {{ callback.time_create|date:"d.m.Y H:i" }}</p>
</div>
<div style="text-align: center; margin: 20px 0;">
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
📋 Перейти к заявкам в админке
</a>
</div>
<p><em>Не забудьте отметить заявку как обработанную после связи с клиентом!</em></p>
</div>
<div class="footer">
<p>Это автоматическое уведомление от системы сайта.</p>
<p>Если вы получили это письмо по ошибке, пожалуйста, свяжитесь с администратором.</p>
</div>
</div>
</body>
>>>>>>> master
</html>

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
<!-- templates/emails/daily_summary.html -->
<!DOCTYPE html>
<html>
@ -58,65 +57,4 @@
</div>
</div>
</body>
=======
<!-- templates/emails/daily_summary.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; }
.stat-card { background: white; padding: 15px; border-radius: 8px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.stat-number { font-size: 2rem; font-weight: bold; margin: 0; }
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📊 Ежедневная сводка</h1>
<p>Статистика заявок за {{ yesterday|date:"d.m.Y" }}</p>
</div>
<div class="content">
<div class="stats-grid">
<div class="stat-card">
<h3>📅 Вчерашние заявки</h3>
<p class="stat-number" style="color: #28a745;">{{ yesterday_callbacks }}</p>
</div>
<div class="stat-card">
<h3>⏳ Ожидают обработки</h3>
<p class="stat-number" style="color: #ffc107;">{{ unprocessed_callbacks }}</p>
</div>
</div>
{% if unprocessed_callbacks > 0 %}
<div class="alert">
<strong>⚠️ Внимание!</strong> У вас есть {{ unprocessed_callbacks }} необработанных заявок.
</div>
{% endif %}
<div style="text-align: center; margin: 20px 0;">
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
📋 Управление заявками
</a>
</div>
<p><em>Не забудьте обработать все pending заявки!</em></p>
</div>
<div class="footer">
<p>Ежедневная автоматическая сводка от системы сайта.</p>
<p>Дата формирования: {{ today|date:"d.m.Y H:i" }}</p>
</div>
</div>
</body>
>>>>>>> master
</html>

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
@ -152,159 +151,4 @@
}
</script>
=======
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% block content %}
<div class="page-header">
<h1 class="page-title">{{title}}</h1>
<p class="page-subtitle">Профессиональный программист 1С с более чем 10-летним опытом</p>
</div>
<div class="content-card">
<div class="about-header">
<h2>Николай Сердюк</h2>
<p class="subtitle">Разработчик 1С</p>
</div>
<div class="about-section">
<h3>🚀 Опыт работы</h3>
<p class="card-subtitle">Более 10 лет успешной работы в разработке и сопровождении систем на платформе 1С</p>
<div class="experience-item mt-3">
<h4>Основные направления:</h4>
<div class="skills-grid">
<div class="skill-category">
<h4>💻 Разработка</h4>
<ul>
<li>Разработка и доработка конфигураций 1С</li>
<li>Создание внешних обработок и отчетов</li>
<li>Кастомизация под бизнес-процессы</li>
</ul>
</div>
<div class="skill-category">
<h4>🔗 Интеграция</h4>
<ul>
<li>Интеграция 1С с веб-сервисами</li>
<li>Связь с сайтами и мобильными приложениями</li>
<li>API и веб-сервисы</li>
</ul>
</div>
<div class="skill-category">
<h4>⚡ Оптимизация</h4>
<ul>
<li>Оптимизация бизнес-процессов</li>
<li>Ускорение работы баз данных</li>
<li>Автоматизация рутинных операций</li>
</ul>
</div>
</div>
</div>
</div>
<div class="about-section">
<h3>🛠 Технологии и навыки</h3>
<div class="skills-grid">
<div class="skill-category">
<h4>🎯 1С Разработка</h4>
<ul>
<li>1С:Предприятие 8.3</li>
<li>Управление торговлей</li>
<li>Бухгалтерия предприятия</li>
<li>Зарплата и управление персоналом</li>
<li>Внешние обработки и отчеты</li>
</ul>
</div>
<div class="skill-category">
<h4>🔧 Дополнительные технологии</h4>
<ul>
<li>SQL и оптимизация запросов</li>
<li>Веб-сервисы и API</li>
<li>XML, JSON, REST</li>
<li>Системное администрирование</li>
</ul>
</div>
</div>
</div>
<div class="about-section">
<h3>📈 Проекты и достижения</h3>
<p class="card-subtitle">Успешно реализовал более 50 проектов различной сложности</p>
<div class="skills-grid mt-3">
<div class="skill-category">
<h4>🏆 Ключевые проекты</h4>
<ul>
<li>Автоматизация учетных систем для предприятий</li>
<li>Интеграция 1С с сайтами и мобильными приложениями</li>
<li>Разработка кастомизированных отчетов и дашбордов</li>
<li>Оптимизация производительности баз данных</li>
</ul>
</div>
</div>
</div>
<div class="about-section">
<h3>📞 Контакты</h3>
<div class="contacts">
<div class="skills-grid">
<div class="skill-category">
<h4>📧 Электронная почта</h4>
<p><strong>{{ CONTACT_EMAIL }}</strong></p>
</div>
<div class="skill-category">
<h4>📱 Телефон</h4>
<p><strong>{{ CONTACT_PHONE }}</strong></p>
</div>
<div class="skill-category">
<h4>💬 Telegram</h4>
<p><strong><a href="https://t.me/odinesina_prog" target="_blank" class="btn btn-primary" style="display: inline-flex; padding: 0.5rem 1rem;">@odinesina_prog</a></strong></p>
</div>
</div>
</div>
</div>
<div class="text-center mt-4">
<div class="card-actions">
<a href="{% url 'solution' %}" class="btn btn-primary">📂 Посмотреть мои проекты</a>
<a href="{% url 'recall' %}" class="btn btn-secondary">⭐ Отзывы клиентов</a>
</div>
</div>
</div>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Service",
"serviceType": "1С программирование",
"provider": {
"@type": "Person",
"name": "Николай Сердюк"
},
"areaServed": "Россия",
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Услуги программиста 1С",
"itemListElement": [
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Разработка конфигураций 1С"
}
},
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Интеграция 1С с веб-сервисами"
}
}
]
}
}
</script>
>>>>>>> master
{% endblock %}

View File

@ -1,559 +1,3 @@
<<<<<<< HEAD
{% load static %}
{% load programmer_tags %}
{% load django_bootstrap5 %}
<!DOCTYPE html>
<html lang="ru">
<head>
<title>{{title}} - Программист 1С</title>
<!-- Основные мета-теги -->
<meta name="description" content="{% block meta_description %}Профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция и оптимизация систем 1С.{% endblock %}">
<meta name="keywords" content="{% block meta_keywords %}программист 1С, разработка 1С, интеграция 1С, оптимизация 1С, 1С предприятие{% endblock %}">
<meta name="author" content="Николай Сердюк">
<!-- Open Graph для соцсетей -->
<meta property="og:title" content="{{title}} - Программист 1С">
<meta property="og:description" content="Профессиональный программист 1С с более чем 10-летним опытом">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{% static 'programmer/images/og-image.jpg' %}">
<meta property="og:site_name" content="Программист 1С - Николай Сердюк">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{title}} - Программист 1С">
<meta name="twitter:description" content="Профессиональный программист 1С с более чем 10-летним опытом">
<meta name="twitter:image" content="{% static 'programmer/images/og-image.jpg' %}">
<!-- Дополнительные SEO-теги -->
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
<link rel="canonical" href="{{ request.build_absolute_uri }}">
{% bootstrap_css %}
<!-- Основной CSS файл (темная тема по умолчанию) -->
<link type="text/css" href="{% static 'programmer/css/styles_w.css' %}" rel="stylesheet" id="theme-css" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="shortcut icon" href="{% static 'programmer/images/main.ico' %}" type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
/* Временные стили для тумблера и мобильного меню */
.theme-switcher {
display: flex;
align-items: center;
}
.theme-toggle-checkbox {
display: none;
}
.theme-toggle-label {
position: relative;
display: flex;
align-items: center;
width: 60px;
height: 30px;
background: #222738;
border: 2px solid #2D3447;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
padding: 2px;
}
.theme-toggle-slider {
position: absolute;
width: 24px;
height: 24px;
background: #FF6B00;
border-radius: 50%;
transition: all 0.3s ease;
left: 2px;
z-index: 2;
}
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-toggle-slider {
transform: translateX(30px);
background: #0055A5;
}
.theme-icon {
position: absolute;
font-size: 12px;
transition: all 0.3s ease;
z-index: 1;
}
.theme-icon.sun {
left: 8px;
opacity: 0;
}
.theme-icon.moon {
right: 8px;
opacity: 1;
}
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-icon.sun {
opacity: 1;
}
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-icon.moon {
opacity: 0;
}
.nav-actions {
display: flex;
align-items: center;
gap: 1rem;
}
/* Мобильное меню */
.mobile-menu-btn {
display: none;
background: none;
border: none;
color: var(--text-primary);
font-size: 1.5rem;
cursor: pointer;
padding: 0.5rem;
border-radius: 4px;
transition: all 0.3s ease;
}
.mobile-menu-btn:hover {
background: var(--border-light);
}
.mobile-menu-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.mobile-menu {
position: fixed;
top: 0;
right: -100%;
width: 300px;
height: 100%;
background: var(--bg-card);
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
transition: right 0.3s ease;
z-index: 1000;
overflow-y: auto;
padding: 2rem 1.5rem;
}
.mobile-menu.active {
right: 0;
}
.mobile-menu-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-light);
}
.mobile-menu-close {
background: none;
border: none;
font-size: 1.5rem;
color: var(--text-primary);
cursor: pointer;
padding: 0.5rem;
border-radius: 4px;
transition: all 0.3s ease;
}
.mobile-menu-close:hover {
background: var(--border-light);
}
.mobile-nav-menu {
list-style: none;
margin-bottom: 2rem;
}
.mobile-nav-item {
margin-bottom: 0.5rem;
}
.mobile-nav-link {
display: block;
padding: 1rem;
color: var(--text-primary);
text-decoration: none;
border-radius: 8px;
transition: all 0.3s ease;
font-weight: 500;
}
.mobile-nav-link:hover,
.mobile-nav-link.active {
background: var(--primary);
color: white;
}
.mobile-nav-actions {
display: flex;
flex-direction: column;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-light);
}
/* ===== MOBILE RESPONSIVE STYLES ===== */
@media (max-width: 768px) {
.hero-title {
font-size: 2.5rem;
}
.hero-subtitle {
font-size: 1.125rem;
padding: 0 1rem;
}
.page-title {
font-size: 2.25rem;
}
.page-subtitle {
font-size: 1.125rem;
padding: 0 1rem;
}
.grid-2 {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.modern-card {
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.card-title {
font-size: 1.375rem;
}
.content-card {
padding: 1.5rem;
}
.about-section {
padding: 1.5rem;
}
.skills-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.competence-item {
flex-direction: column;
padding: 1.5rem;
gap: 1.5rem;
}
.competence-scan-container {
width: 100%;
max-width: 280px;
margin: 0 auto;
}
.recall-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.recall-meta {
flex-direction: column;
gap: 0.5rem;
}
.footer-content {
grid-template-columns: 1fr;
gap: 2rem;
text-align: center;
}
.footer-contacts p {
justify-content: center;
}
.card-actions {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
.modal-content {
margin: 5% auto;
max-width: 95%;
}
.modal-header {
padding: 1.5rem 1.5rem 1rem;
}
.modal-body {
padding: 1.5rem;
}
.page-content {
padding: 1.5rem;
}
.breadcrumbs {
font-size: 0.8rem;
}
}
@media (max-width: 480px) {
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
}
.page-title {
font-size: 1.875rem;
}
.modern-card {
padding: 1.25rem;
}
.content-card {
padding: 1.25rem;
}
.about-section {
padding: 1.25rem;
}
.card-title {
font-size: 1.25rem;
}
.competence-scan-container {
max-width: 100%;
}
}
</style>
{% block extra_css %}
<!-- Дополнительные CSS файлы для конкретных страниц -->
{% endblock %}
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-X3W9YSQHRM"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-X3W9YSQHRM');
</script>
<!-- Яндекс.Метрика -->
<script type="text/javascript">
(function(m,e,t,r,i,k,a){
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=105278924', 'ym');
ym(105278924, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", accurateTrackBounce:true, trackLinks:true});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/105278924" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Николай Сердюк",
"jobTitle": "Программист 1С",
"description": "Профессиональный программист 1С с более чем 10-летним опытом",
"url": "https://nikdizell.ru",
"email": "{{ CONTACT_EMAIL }}",
"telephone": "{{ CONTACT_PHONE }}",
"knowsAbout": [
"1С:Предприятие 8.3",
"Управление торговлей",
"Бухгалтерия предприятия",
"Зарплата и управление персоналом",
"SQL",
"Веб-сервисы",
"API интеграция"
],
"hasOccupation": {
"@type": "Occupation",
"name": "Программист 1С",
"description": "Разработка и сопровождение систем на платформе 1С",
"occupationLocation": "Россия"
}
}
</script>
</head>
<body>
<!-- Header -->
{% block mainmenu %}
<header class="header">
<div class="container">
<nav class="nav">
<a href="{% url 'home' %}" class="logo">
<img src="{% static 'programmer/images/main.ico' %}" alt="Logo" class="logo-img">
<span class="logo-text">Программист 1С</span>
</a>
<!-- Десктопное меню -->
<ul class="nav-menu">
{% for m in menu %}
<li class="nav-item">
<a href="{% url m.url_name %}" class="nav-link {% if request.resolver_match.url_name == m.url_name %}active{% endif %}">
{{m.title}}
</a>
</li>
{% endfor %}
</ul>
<div class="nav-actions">
<a href="https://t.me/odinesina_prog" target="_blank" class="telegram-btn">
<span class="telegram-icon">
<img src="{% static 'programmer/images/share_tg.png' %}" alt="Telegram" width="20" height="20">
</span>
</a>
<!-- Theme Toggle Switch -->
<div class="theme-switcher">
<input type="checkbox" id="theme-toggle" class="theme-toggle-checkbox" checked>
<label for="theme-toggle" class="theme-toggle-label">
<span class="theme-toggle-slider"></span>
<span class="theme-icon sun">☀️</span>
<span class="theme-icon moon">🌙</span>
</label>
</div>
<!-- Кнопка мобильного меню -->
<button class="mobile-menu-btn" id="mobileMenuBtn">
</button>
</div>
</nav>
</div>
</header>
<!-- Мобильное меню -->
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
<div class="mobile-menu" id="mobileMenu">
<div class="mobile-menu-header">
<h3>Меню</h3>
<button class="mobile-menu-close" id="mobileMenuClose">
</button>
</div>
<ul class="mobile-nav-menu">
{% for m in menu %}
<li class="mobile-nav-item">
<a href="{% url m.url_name %}" class="mobile-nav-link {% if request.resolver_match.url_name == m.url_name %}active{% endif %}">
{{m.title}}
</a>
</li>
{% endfor %}
</ul>
<div class="mobile-nav-actions">
<a href="https://t.me/odinesina_prog" target="_blank" class="btn btn-primary" style="width: 100%; text-align: center;">
<span class="telegram-icon">
<img src="{% static 'programmer/images/share_tg.png' %}" alt="Telegram" width="20" height="20">
</span>
</a>
<div class="theme-switcher" style="justify-content: center;">
<input type="checkbox" id="mobile-theme-toggle" class="theme-toggle-checkbox" checked>
<label for="mobile-theme-toggle" class="theme-toggle-label">
<span class="theme-toggle-slider"></span>
<span class="theme-icon sun">☀️</span>
<span class="theme-icon moon">🌙</span>
</label>
</div>
</div>
</div>
{% endblock mainmenu %}
<!-- Main Content -->
<main class="main">
<div class="container">
<section class="content">
<!-- Breadcrumbs -->
{% block breadcrumbs %}
<nav class="breadcrumbs">
<a href="{% url 'home' %}" class="breadcrumb-link">Главная</a>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">{{title}}</span>
</nav>
{% endblock %}
<!-- Messages -->
{% bootstrap_messages %}
<!-- Page Content -->
<div class="page-content">
{% block content %}
{% endblock %}
</div>
</section>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-info">
<h3>Николай Сердюк</h3>
<p>Программист 1С</p>
</div>
<div class="footer-contacts">
<p>📧 {{ CONTACT_EMAIL }}</p>
<p>📱 {{ CONTACT_PHONE }}</p>
</div>
<div class="footer-copyright">
<p>&copy; 2025 ИП Сердюк Николай Александрович. Все права защищены.</p>
</div>
</div>
</div>
</footer>
{% bootstrap_javascript %}
<script src="{% static 'programmer/js/theme-switcher.js' %}"></script>
<script src="{% static 'programmer/js/mobile-menu.js' %}"></script>
{% block extra_js %}
<!-- Дополнительные JS файлы для конкретных страниц -->
{% endblock %}
</body>
=======
{% load static %}
{% load programmer_tags %}
{% load django_bootstrap5 %}
@ -1131,5 +575,4 @@
}
</script>
</body>
>>>>>>> master
</html>

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
@ -6,13 +5,4 @@
=======
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% block content %}
>>>>>>> master
{% endblock %}

View File

@ -1,76 +1,3 @@
<<<<<<< HEAD
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/competence.css' %}">
{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Компетенции</h1>
<p class="page-subtitle">Профессиональные навыки и опыт</p>
</div>
<div class="competence-grid">
{% for p in posts %}
<div class="modern-card fade-in">
<div class="competence-item">
{% if p.photo %}
<div class="competence-scan-wrapper">
<div class="competence-scan-container">
<img src="{{ p.photo.url }}"
alt="{{ p.title }}"
class="competence-scan"
onclick="openCompetenceModal('{{ p.photo.url }}', '{{ p.title }}')">
<div class="scan-hint">
<span class="scan-zoom-icon">🔍</span>
<span class="scan-text">Нажмите для увеличения</span>
</div>
</div>
</div>
{% endif %}
<div class="competence-content">
<h2 class="competence-title">{{ p.title }}</h2>
<div class="competence-description">
{{ p.content|linebreaks }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if not posts %}
<div class="modern-card text-center fade-in">
<h3>📚 Информация о компетенциях</h3>
<p class="card-subtitle">Раздел находится в разработке</p>
<div class="card-actions justify-center">
<a href="{% url 'solution' %}" class="btn btn-primary">Посмотреть проекты</a>
<a href="{% url 'about' %}" class="btn btn-secondary">Обо мне</a>
</div>
</div>
{% endif %}
<!-- Модальное окно для увеличенного просмотра -->
<div id="competenceModal" class="modal competence-modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="competenceModalTitle">Компетенция</h3>
<button class="modal-close" onclick="closeCompetenceModal()">&times;</button>
</div>
<div class="modal-body">
<img class="modal-image" id="competenceModalImage" alt="Увеличенное изображение компетенции">
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'programmer/js/competence.js' %}"></script>
=======
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
@ -142,5 +69,4 @@
{% block extra_js %}
<script src="{% static 'programmer/js/competence.js' %}"></script>
>>>>>>> master
{% endblock %}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
/* TEAM */
Developer: Николай Сердюк
Site: https://nikdizell.ru
@ -11,18 +10,4 @@ Bootstrap
/* SITE */
Last update: 2025
Language: Russian
=======
/* TEAM */
Developer: Николай Сердюк
Site: https://nikdizell.ru
Email: {{ CONTACT_EMAIL }}
/* THANKS */
Django Framework
Bootstrap
/* SITE */
Last update: 2025
Language: Russian
>>>>>>> master
Doctype: HTML5

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
@ -101,108 +100,4 @@ document.addEventListener('keydown', function(event) {
}
});
</script>
=======
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% block content %}
<div class="hero-section fade-in">
<h1 class="hero-title">🚀 Добро пожаловать!</h1>
<p class="hero-subtitle">Я профессиональный программист 1С с опытом создания эффективных бизнес-решений</p>
</div>
<div class="grid grid-2">
{% autoescape off %}
{% for p in posts %}
<div class="modern-card fade-in {% cycle '' 'secondary' %}">
<div class="card-header">
<h2 class="card-title">{{p.title}}</h2>
</div>
<div class="card-content">
{{p.content}}
</div>
<div class="card-actions">
<button onclick="openModal()" class="btn btn-primary">🎯 Получить консультацию</button>
</div>
</div>
{% endfor %}
{% endautoescape %}
</div>
<!-- Модальное окно формы -->
<div id="callbackModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>📞 Заявка на консультацию</h3>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'callback' %}" id="callbackForm">
{% csrf_token %}
<div class="form-group">
<label for="id_name">Имя *</label>
{{ form.name }}
</div>
<div class="form-group">
<label for="id_phone">Телефон *</label>
{{ form.phone }}
</div>
<div class="form-group">
<label for="id_email">Электронная почта</label>
{{ form.email }}
</div>
<div class="form-group">
<label for="id_question">Ваш вопрос</label>
{{ form.question }}
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" style="width: 100%;">
📨 Отправить заявку
</button>
</div>
</form>
</div>
</div>
</div>
{% if not posts %}
<div class="modern-card text-center fade-in">
<h3>🚀 Контент скоро появится</h3>
<p class="card-subtitle">Мы готовим для вас интересные материалы и кейсы</p>
<div class="card-actions justify-center">
<button onclick="openModal()" class="btn btn-primary">🎯 Получить консультацию</button>
</div>
</div>
{% endif %}
<script>
function openModal() {
document.getElementById('callbackModal').style.display = 'block';
}
function closeModal() {
document.getElementById('callbackModal').style.display = 'none';
}
// Закрытие модального окна при клике вне его
window.onclick = function(event) {
const modal = document.getElementById('callbackModal');
if (event.target === modal) {
closeModal();
}
}
// Закрытие по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
</script>
>>>>>>> master
{% endblock %}

View File

@ -1,153 +1,3 @@
<<<<<<< HEAD
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Отзывы</h1>
<p class="page-subtitle">Мнения клиентов и партнеров</p>
</div>
<div class="recall-grid">
{% for p in posts %}
<div class="modern-card fade-in">
<div class="recall-item">
<div class="recall-header">
<div class="recall-info">
<h2 class="recall-title">{{ p.title }}</h2>
{% if p.time_create %}
<!-- <div class="recall-meta">-->
<!-- <span class="recall-date">{{ p.time_create|date:"d.m.Y" }}</span>-->
<!-- </div>-->
{% endif %}
</div>
</div>
<div class="recall-content">
{% if p.scan %}
<div class="recall-scan-wrapper">
<div class="recall-scan-container">
<img src="{{ p.scan.url }}"
alt="Отзыв от {{ p.title }}"
class="recall-scan"
onclick="openModal('{{ p.scan.url }}', '{{ p.title }}')">
<div class="scan-hint">
<span class="scan-zoom-icon">🔍</span>
<span class="scan-text">Нажмите для увеличения</span>
</div>
</div>
</div>
{% endif %}
<div class="recall-text">
{{ p.content|linebreaks }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if not posts %}
<div class="modern-card text-center fade-in">
<h3>💬 Отзывы клиентов</h3>
<p class="card-subtitle">Здесь будут отображаться отзывы от довольных клиентов</p>
<div class="card-actions justify-center">
<a href="{% url 'solution' %}" class="btn btn-primary">Посмотреть проекты</a>
<a href="{% url 'about' %}" class="btn btn-secondary">Связаться со мной</a>
</div>
</div>
{% endif %}
<!-- Модальное окно для увеличенного просмотра -->
<div id="imageModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">Отзыв</h3>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<img class="modal-image" id="modalImage">
</div>
</div>
</div>
<script>
function openModal(imageUrl, title) {
console.log('Opening modal with:', imageUrl);
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
const modalTitle = document.getElementById('modalTitle');
if (modal && modalImg) {
modal.style.display = "block";
modalImg.src = imageUrl;
if (title && modalTitle) {
modalTitle.textContent = title;
}
// Добавляем класс для анимации
setTimeout(() => {
modal.classList.add('active');
}, 10);
}
}
function closeModal() {
const modal = document.getElementById('imageModal');
if (modal) {
modal.classList.remove('active');
setTimeout(() => {
modal.style.display = "none";
}, 300);
}
}
// Закрытие модального окна при клике вне изображения
document.addEventListener('click', function(event) {
const modal = document.getElementById('imageModal');
if (event.target === modal) {
closeModal();
}
});
// Закрытие по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
// Адаптация размера изображения в модальном окне
window.addEventListener('resize', function() {
const modalImg = document.getElementById('modalImage');
if (modalImg && modalImg.src) {
adjustModalImageSize();
}
});
function adjustModalImageSize() {
const modalImg = document.getElementById('modalImage');
const modalContent = document.querySelector('.modal-content');
if (modalImg && modalContent) {
const maxWidth = window.innerWidth * 0.9;
const maxHeight = window.innerHeight * 0.8;
modalImg.style.maxWidth = `${maxWidth}px`;
modalImg.style.maxHeight = `${maxHeight}px`;
}
}
{% endblock %}
{% block extra_js %}
<script src="{% static 'programmer/js/recall.js' %}"></script>
=======
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
@ -321,5 +171,4 @@ function adjustModalImageSize() {
{% block extra_js %}
<script src="{% static 'programmer/js/recall.js' %}"></script>
>>>>>>> master
{% endblock %}

View File

@ -1,22 +1,3 @@
<<<<<<< HEAD
User-agent: *
Allow: /
# Основной сайт
Sitemap: {{ request.scheme }}://{{ request.get_host }}/sitemap.xml
# Запрещаем служебные разделы
Disallow: /admin/
Disallow: /static/admin/
Disallow: /callback/
Disallow: /api/
# Разрешаем индексацию статических файлов
Allow: /static/
Allow: /media/
# Указываем главное зеркало
=======
User-agent: *
Allow: /
@ -35,5 +16,4 @@ Allow: /static/
Allow: /media/
# Указываем главное зеркало
>>>>>>> master
Host: {{ request.scheme }}://{{ request.get_host }}

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
@ -10,17 +9,4 @@
{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
</url>
{% endfor %}
=======
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% for url in urlset %}
<url>
<loc>{{ url.location }}</loc>
{% if url.lastmod %}<lastmod>{{ url.lastmod|date:"Y-m-d" }}</lastmod>{% endif %}
{% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
</url>
{% endfor %}
>>>>>>> master
</urlset>

View File

@ -1,74 +1,3 @@
<<<<<<< HEAD
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/solution-accordion.css' %}">
{% endblock %}
{% block content %}
<h1>{{title}}</h1>
<div class="improved-list">
{% autoescape off %}
{% for p in posts %}
<li class="fade-in">
<div class="content-card">
<h2>{{p.title}}</h2>
<div class="solution-accordion">
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>📋 Описание задачи</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.description}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>🔧 Описание решения</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.implementation}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>✅ Результат</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.closing}}
</div>
</div>
</div>
<div class="article-panel">
<p class="first">Опубликовано: {{p.time_create|date:"d.m.Y"}}</p>
</div>
</div>
</li>
{% endfor %}
{% endautoescape %}
</div>
{% if not posts %}
<div class="content-card text-center">
<h3>Примеры решений скоро появятся</h3>
<p>Мы готовим для вас интересные кейсы и решения</p>
</div>
{% endif %}
<!-- Подключаем внешний скрипт -->
<script src="{% static 'programmer/js/solution-accordion.js' %}"></script>
=======
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
@ -164,5 +93,4 @@
<!-- Подключаем внешний скрипт -->
<script src="{% static 'programmer/js/solution-accordion.js' %}"></script>
>>>>>>> master
{% endblock %}

View File

@ -1,19 +1,3 @@
<<<<<<< HEAD
from django import template
from programmer.models import *
register = template.Library()
@register.simple_tag(name='competence')
def get_competence():
return Competence.objects.all()
@register.simple_tag(name='recall')
def get_recall():
return Recall.objects.all()
=======
from django import template
from ..models import CallbackRequest
@ -28,4 +12,3 @@ def get_today_callbacks():
from django.utils import timezone
today = timezone.now().date()
return CallbackRequest.objects.filter(time_create__date=today).count()
>>>>>>> master

View File

@ -1,9 +1,3 @@
<<<<<<< HEAD
from django.test import TestCase
# Create your tests here.
=======
from django.test import TestCase
# Create your tests here.
>>>>>>> master

View File

@ -1,4 +1,3 @@
<<<<<<< HEAD
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
@ -25,32 +24,4 @@ urlpatterns = [
if settings.DEBUG:
=======
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from .views import *
from django.contrib.sitemaps.views import sitemap
from .sitemaps import sitemaps
urlpatterns = [
path('', index, name='home'),
path('about/', about, name='about'),
path('solutions/', solution, name='solution'),
path('competence/', ability, name='ability'),
path('recall/', recall, name='recall'),
path('post/<int:post_id>/', show_post, name='post'),
path('callback/', callback_request, name='callback'),
path('admin/statistics/', statistics_view, name='statistics'),
# Sitemap
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
path('robots.txt', robots_txt, name='robots'),
]
if settings.DEBUG:
>>>>>>> master
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -1,258 +1,3 @@
<<<<<<< HEAD
from django.http import HttpResponse, HttpResponseNotFound
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
from django.views.decorators.http import require_GET
menu = [
{'title': "Главная", 'url_name': 'home'},
{'title': "Проекты", 'url_name': 'solution'},
{'title': "Компетенции", 'url_name': 'ability'},
{'title': "Отзывы", 'url_name': 'recall'},
{'title': "Обо мне", 'url_name': 'about'}
]
# === ДОБАВЬТЕ ЭТИ ФУНКЦИИ ЗДЕСЬ ===
def get_client_ip(request):
"""Получаем реальный 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):
"""Определяем, нужно ли отслеживать запрос"""
client_ip = get_client_ip(request)
path = request.path
# Игнорируемые пути (Nextcloud специфичные)
nextcloud_paths = [
'/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 path.startswith('/static/') or path.startswith('/admin/'):
return False
# Не отслеживаем Nextcloud и Docker запросы
if any(path.startswith(p) for p in nextcloud_paths):
return False
if client_ip in docker_ips:
return False
return True
def track_page_view(request):
"""Основная функция отслеживания просмотров"""
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],
)
except Exception as e:
print(f"Error tracking page: {e}")
def track_view(view_func):
"""Декоратор для отслеживания просмотров страниц"""
from functools import wraps
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
# Отслеживаем просмотр перед выполнением view
track_page_view(request)
return view_func(request, *args, **kwargs)
return _wrapped_view
@track_view
def index(request):
posts = Home.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'title': "Главная страница",
'meta_description': "Профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция и оптимизация систем 1С. Закажите консультацию.",
'meta_keywords': "программист 1С, разработка 1С, интеграция 1С, оптимизация 1С, 1С предприятие 8.3",
'form': CallbackForm()
}
return render(request, 'programmer/index.html', context=context)
@track_view
def about(request):
context = {
'menu': menu,
'title': "Обо мне - Программист 1С",
'meta_description': "Николай Сердюк - профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция, оптимизация бизнес-процессов.",
'meta_keywords': "программист 1С Николай Сердюк, опыт работы 1С, компетенции 1С, проекты 1С"
}
return render(request, 'programmer/about.html', context=context)
@track_view
def solution(request):
posts = Solution.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'title': "Проекты"
}
return render(request, 'programmer/solution.html', context=context)
@track_view
def ability(request):
posts = Competence.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'title': "Компетенции"
}
return render(request, 'programmer/competence.html', context=context)
@track_view
def recall(request):
posts = Recall.objects.filter(is_published=True)
context = {
'posts': posts,
'menu': menu,
'title': "Отзывы"
}
return render(request, 'programmer/recall.html', context=context)
def show_post(request, post_id):
return HttpResponse(f"Отображение № {post_id}")
def pageNotFound(request, exception):
return HttpResponseNotFound('<h1>Страница не найдена</h1>')
def callback_request(request):
if request.method == 'POST':
form = CallbackForm(request.POST)
if form.is_valid():
# Сохраняем заявку через форму
form.save()
messages.success(request, '✅ Ваша заявка успешно отправлена! Я свяжусь с вами в ближайшее время.')
return redirect('home')
else:
# Если форма невалидна, показываем ошибки
for field, errors in form.errors.items():
for error in errors:
messages.error(request, f'❌ Ошибка в поле {form.fields[field].label}: {error}')
return redirect('home')
# Если GET запрос, просто показываем главную страницу
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]
today = timezone.now().date()
total_callbacks = CallbackRequest.objects.count()
today_callbacks = CallbackRequest.objects.filter(time_create__date=today).count()
unread_callbacks = CallbackRequest.objects.filter(is_read=False).count()
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,
'total_callbacks': total_callbacks,
'today_callbacks': today_callbacks,
'unread_callbacks': unread_callbacks,
}
return render(request, 'admin/statistics.html', context)
@require_GET
def robots_txt(request):
return render(request, 'robots.txt', content_type='text/plain')
=======
from django.http import HttpResponse, HttpResponseNotFound
from .models import *
from django.shortcuts import render, redirect
@ -512,4 +257,3 @@ def statistics_view(request):
@require_GET
def robots_txt(request):
return render(request, 'robots.txt', content_type='text/plain')
>>>>>>> master