Улучшил работу с заявками на звонок

This commit is contained in:
NikDizell 2025-11-14 12:45:13 +03:00
parent 9a0d64fa00
commit c3228a8baa
13 changed files with 224 additions and 13 deletions

View File

@ -1,4 +1,10 @@
from django.contrib import admin 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 * from .models import *
@ -36,13 +42,104 @@ class HomeAdmin(admin.ModelAdmin):
@admin.register(CallbackRequest) @admin.register(CallbackRequest)
class CallbackAdmin(admin.ModelAdmin): class CallbackAdmin(admin.ModelAdmin):
list_display = ('name', 'phone', 'email', 'time_create', 'is_processed') list_display = ('name', 'phone', 'email', 'time_create', 'is_processed', 'is_read', 'new_badge')
list_display_links = ('name', 'phone') list_display_links = ('name', 'phone')
list_editable = ('is_processed',) list_editable = ('is_processed', 'is_read')
list_filter = ('time_create', 'is_processed') list_filter = ('time_create', 'is_processed', 'is_read')
search_fields = ('name', 'phone', 'email') search_fields = ('name', 'phone', 'email')
readonly_fields = ('time_create',) readonly_fields = ('time_create',)
actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed']
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) @admin.register(PageView)
class PageViewAdmin(admin.ModelAdmin): class PageViewAdmin(admin.ModelAdmin):

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2025-11-14 09:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('programmer', '0011_visitor_pageview'),
]
operations = [
migrations.AddField(
model_name='callbackrequest',
name='is_read',
field=models.BooleanField(default=False, verbose_name='Прочитано'),
),
]

View File

@ -94,6 +94,7 @@ class CallbackRequest(models.Model):
question = models.TextField(blank=True, verbose_name='Ваш вопрос') # Сделать необязательным question = models.TextField(blank=True, verbose_name='Ваш вопрос') # Сделать необязательным
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
is_processed = models.BooleanField(default=False, verbose_name='Обработано') is_processed = models.BooleanField(default=False, verbose_name='Обработано')
is_read = models.BooleanField(default=False, verbose_name='Прочитано')
def __str__(self): def __str__(self):
return f"{self.name} - {self.phone}" return f"{self.name} - {self.phone}"

View File

@ -10,6 +10,17 @@
{% endblock %} {% endblock %}
{% block userlinks %} {% block userlinks %}
<a href="{% url 'statistics' %}">📊 Статистика</a> / {% 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 }} {{ block.super }}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,45 @@
{% 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>
{% endblock %}

View File

@ -1,9 +1,29 @@
{% extends 'admin/base.html' %} {% extends 'admin/base.html' %}
{% load programmer_tags %}
{% block title %}Статистика посещений{% endblock %} {% block title %}Статистика посещений{% endblock %}
{% block page_title %}Статистика посещений{% endblock %} {% block page_title %}Статистика посещений{% endblock %}
{% block content %} {% 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="row mb-4">
<div class="col-12"> <div class="col-12">
<div class="stats-grid"> <div class="stats-grid">
@ -23,10 +43,21 @@
<h3>🕒 Всего просмотров</h3> <h3>🕒 Всего просмотров</h3>
<p class="stat-number">{{ total_views }}</p> <p class="stat-number">{{ total_views }}</p>
</div> </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>
</div> </div>
<!-- Остальной код статистики остается без изменений -->
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="card">

View File

@ -1,14 +1,14 @@
from django import template from django import template
from programmer.models import * from ..models import CallbackRequest
register = template.Library() register = template.Library()
@register.simple_tag(name='competence') @register.simple_tag
def get_competence(): def get_unread_callbacks():
return Competence.objects.all() return CallbackRequest.objects.filter(is_read=False).count()
@register.simple_tag
@register.simple_tag(name='recall') def get_today_callbacks():
def get_recall(): from django.utils import timezone
return Recall.objects.all() today = timezone.now().date()
return CallbackRequest.objects.filter(time_create__date=today).count()

View File

@ -228,6 +228,11 @@ def statistics_view(request):
# Последние посещения # Последние посещения
recent_views = PageView.objects.select_related().order_by('-timestamp')[:20] 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 = { context = {
'today_views': today_views, 'today_views': today_views,
'weekly_views': weekly_views, 'weekly_views': weekly_views,
@ -235,6 +240,9 @@ def statistics_view(request):
'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,
'today_callbacks': today_callbacks,
'unread_callbacks': unread_callbacks,
} }
return render(request, 'admin/statistics.html', context) return render(request, 'admin/statistics.html', context)