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 }}

+
+
+ +
+ + 📋 Перейти к списку заявок + + + 🏠 На главную админки + +
+
+{% endblock %} \ No newline at end of file diff --git a/OneCprogsite/programmer/templates/admin/statistics.html b/OneCprogsite/programmer/templates/admin/statistics.html index ca01039..4a1650d 100644 --- a/OneCprogsite/programmer/templates/admin/statistics.html +++ b/OneCprogsite/programmer/templates/admin/statistics.html @@ -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 %} + +{% endif %} +
@@ -23,10 +43,21 @@

🕒 Всего просмотров

{{ total_views }}

+ +
+

📞 Заявок сегодня

+

{{ today_callbacks }}

+
+
+

📋 Всего заявок

+

{% get_unread_callbacks %}/{{ total_callbacks }}

+ (непрочитанные/всего) +
+
diff --git a/OneCprogsite/programmer/templatetags/__pycache__/programmer_tags.cpython-310.pyc b/OneCprogsite/programmer/templatetags/__pycache__/programmer_tags.cpython-310.pyc index 732e5fc..2201004 100644 Binary files a/OneCprogsite/programmer/templatetags/__pycache__/programmer_tags.cpython-310.pyc and b/OneCprogsite/programmer/templatetags/__pycache__/programmer_tags.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/templatetags/programmer_tags.py b/OneCprogsite/programmer/templatetags/programmer_tags.py index c81f3c7..5ca4387 100644 --- a/OneCprogsite/programmer/templatetags/programmer_tags.py +++ b/OneCprogsite/programmer/templatetags/programmer_tags.py @@ -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() \ No newline at end of file diff --git a/OneCprogsite/programmer/views.py b/OneCprogsite/programmer/views.py index c41256e..e51d82d 100644 --- a/OneCprogsite/programmer/views.py +++ b/OneCprogsite/programmer/views.py @@ -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)