Улучшил работу с заявками на звонок
This commit is contained in:
parent
9a0d64fa00
commit
c3228a8baa
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,4 +1,10 @@
|
||||
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 *
|
||||
|
||||
|
||||
@ -36,13 +42,104 @@ class HomeAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(CallbackRequest)
|
||||
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_editable = ('is_processed',)
|
||||
list_filter = ('time_create', 'is_processed')
|
||||
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']
|
||||
|
||||
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):
|
||||
|
||||
@ -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='Прочитано'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -94,6 +94,7 @@ class CallbackRequest(models.Model):
|
||||
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='Прочитано')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.phone}"
|
||||
|
||||
@ -10,6 +10,17 @@
|
||||
{% endblock %}
|
||||
|
||||
{% 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 }}
|
||||
{% endblock %}
|
||||
45
OneCprogsite/programmer/templates/admin/callback_stats.html
Normal file
45
OneCprogsite/programmer/templates/admin/callback_stats.html
Normal 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 %}
|
||||
@ -1,9 +1,29 @@
|
||||
{% 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">
|
||||
@ -23,10 +43,21 @@
|
||||
<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">
|
||||
|
||||
Binary file not shown.
@ -1,14 +1,14 @@
|
||||
from django import template
|
||||
from programmer.models import *
|
||||
|
||||
from ..models import CallbackRequest
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag(name='competence')
|
||||
def get_competence():
|
||||
return Competence.objects.all()
|
||||
@register.simple_tag
|
||||
def get_unread_callbacks():
|
||||
return CallbackRequest.objects.filter(is_read=False).count()
|
||||
|
||||
|
||||
@register.simple_tag(name='recall')
|
||||
def get_recall():
|
||||
return Recall.objects.all()
|
||||
@register.simple_tag
|
||||
def get_today_callbacks():
|
||||
from django.utils import timezone
|
||||
today = timezone.now().date()
|
||||
return CallbackRequest.objects.filter(time_create__date=today).count()
|
||||
@ -228,6 +228,11 @@ def statistics_view(request):
|
||||
# Последние посещения
|
||||
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,
|
||||
@ -235,6 +240,9 @@ def statistics_view(request):
|
||||
'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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user