diff --git a/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc index abc1326..e52d92c 100644 Binary files a/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc index f6d816b..e776b38 100644 Binary files a/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc index 8ad2d2d..d08997c 100644 Binary files a/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/views.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/admin.py b/OneCprogsite/programmer/admin.py index bb5f8d1..18a9935 100644 --- a/OneCprogsite/programmer/admin.py +++ b/OneCprogsite/programmer/admin.py @@ -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('🆕 НОВАЯ') + 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): diff --git a/OneCprogsite/programmer/migrations/0012_callbackrequest_is_read.py b/OneCprogsite/programmer/migrations/0012_callbackrequest_is_read.py new file mode 100644 index 0000000..080dfee --- /dev/null +++ b/OneCprogsite/programmer/migrations/0012_callbackrequest_is_read.py @@ -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='Прочитано'), + ), + ] diff --git a/OneCprogsite/programmer/migrations/__pycache__/0012_callbackrequest_is_read.cpython-310.pyc b/OneCprogsite/programmer/migrations/__pycache__/0012_callbackrequest_is_read.cpython-310.pyc new file mode 100644 index 0000000..342d187 Binary files /dev/null and b/OneCprogsite/programmer/migrations/__pycache__/0012_callbackrequest_is_read.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/models.py b/OneCprogsite/programmer/models.py index 065b6aa..4b08fa7 100644 --- a/OneCprogsite/programmer/models.py +++ b/OneCprogsite/programmer/models.py @@ -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}" diff --git a/OneCprogsite/programmer/templates/admin/base_site.html b/OneCprogsite/programmer/templates/admin/base_site.html index 5bc87fe..58bd451 100644 --- a/OneCprogsite/programmer/templates/admin/base_site.html +++ b/OneCprogsite/programmer/templates/admin/base_site.html @@ -10,6 +10,17 @@ {% endblock %} {% block userlinks %} - 📊 Статистика / + {% load programmer_tags %} + + + {% get_unread_callbacks as unread_callbacks %} + {% if unread_callbacks %} + + 🚨 {{ unread_callbacks }} новых заявок + / + {% endif %} + + 📊 Статистика заявок / + 📈 Посещения / {{ block.super }} {% endblock %} \ No newline at end of file diff --git a/OneCprogsite/programmer/templates/admin/callback_stats.html b/OneCprogsite/programmer/templates/admin/callback_stats.html new file mode 100644 index 0000000..916c167 --- /dev/null +++ b/OneCprogsite/programmer/templates/admin/callback_stats.html @@ -0,0 +1,45 @@ +{% extends "admin/base_site.html" %} + +{% block title %}Статистика заявок{% endblock %} + +{% block content %} +
{{ stats.total }}
+{{ stats.today }}
+{{ stats.week }}
+{{ stats.unread }}
+{{ stats.unprocessed }}
++ У вас {{ unread_callbacks }} непрочитанных заявок на обратный звонок + {% if today_callbacks > 0 %} + ({{ today_callbacks }} из них сегодня) + {% endif %} +
+ + 📋 Перейти к заявкам + + +{{ total_views }}
{{ today_callbacks }}
+{% get_unread_callbacks %}/{{ total_callbacks }}
+ (непрочитанные/всего) +