From c3228a8baae88d18f6bd7c39c5a6e7b417ed9201 Mon Sep 17 00:00:00 2001 From: NikDizell Date: Fri, 14 Nov 2025 12:45:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83=20=D1=81=20=D0=B7=D0=B0?= =?UTF-8?q?=D1=8F=D0=B2=D0=BA=D0=B0=D0=BC=D0=B8=20=D0=BD=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B2=D0=BE=D0=BD=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/admin.cpython-310.pyc | Bin 2062 -> 5134 bytes .../__pycache__/models.cpython-310.pyc | Bin 6021 -> 6069 bytes .../__pycache__/views.cpython-310.pyc | Bin 6958 -> 7141 bytes OneCprogsite/programmer/admin.py | 103 +++++++++++++++++- .../0012_callbackrequest_is_read.py | 18 +++ ...12_callbackrequest_is_read.cpython-310.pyc | Bin 0 -> 668 bytes OneCprogsite/programmer/models.py | 1 + .../programmer/templates/admin/base_site.html | 13 ++- .../templates/admin/callback_stats.html | 45 ++++++++ .../templates/admin/statistics.html | 31 ++++++ .../programmer_tags.cpython-310.pyc | Bin 611 -> 751 bytes .../templatetags/programmer_tags.py | 18 +-- OneCprogsite/programmer/views.py | 8 ++ 13 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 OneCprogsite/programmer/migrations/0012_callbackrequest_is_read.py create mode 100644 OneCprogsite/programmer/migrations/__pycache__/0012_callbackrequest_is_read.cpython-310.pyc create mode 100644 OneCprogsite/programmer/templates/admin/callback_stats.html diff --git a/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc index abc132657cf67f3f90a9e58a1d8a78297ff6746d..e52d92c8596be2940ec0928d6722fd2ab3cd9e0e 100644 GIT binary patch literal 5134 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 bY?U4QInER-pDkzT=VYc>DO+MCR%ZVL^khuQ literal 2062 zcmb7FOK%)S5bmDGZtr7u?ZvzTh)ChJftUjlLI|NCBZUjbN)U(9N~7^~c|A_ggYH?O zjkq~d{)WxX|Aup4k&trEjT2QpyY>PxfnH5@J-WN9tGd3eqNvYs{BCygnPBWU5;jMO z!d?8#uSg`5T(N@3oHOZ3UwPlNdrSs0e9UAB{KP-?;{Y`s*+orvr3q0ZWDhmHm8OH5 zz8s)tu+re#GXcGGj+UW}&=R$lQy%x7ZQyJpIY!&~(l$gc!kr@s&OOdn(ip-GX$v|V z(l&HSl3Nehbo(jZicPr$E69e?>=6L+jP&OA_4_bAPmJq*xA*@J}GR0F9+fS0Bk_$!rFiCz~q-tdu z;^|Eyb5pByX(yXfqVn>gv5|pPXUEA=4oafvmVnHg^pN6q;!&;|&=*YKApb=ExcBwv zvx8-GT$QUS!TX;;*39HdTFxt+Oy0okyAt+l%#0I=X;UEErFx`U_Dn|n4yB3QuN zpWx1NZCNz!!L$$VLzXJFO8Hd|HKoc1CQD0~Fipqnh{%}87Lt9vP3+~RTJ89MKSf;@(v`JOy@Q2v7@}G6%<(Ql;r;u~+54s{zy+vp($4>vm0P%lGSfOF=~{>89kl*?+r&b4v2}!v_OOD7Y4&hcMH|rT zb!}4O1Vx&www^<38C_*y49Kac-=$#QLo((11DZUyuI2qJvwpu_LyuyA+S3!9^-Dx{ zh`1!aLfmUaUMKP@55tHt{*cHWB$hMlrA{xd)4X9#)c9KCt_kNS5mPoDZV zG>aOv9nPUazyZB9&{$Dv2|77kPCe{BP)D|_0j&lx%=VHbQ>igYve9TCqq|SZF9k8- zk8b?sPP?0@UsJcMYK7zYGDxkO@BV UCxU)L>~PWB;iKM&kNAZD34rItp#T5? diff --git a/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc index f6d816b274cd3530c58be7387d8307a9439de357..e776b3897c68ef9cb08f6741cd726e45e15fb351 100644 GIT binary patch delta 217 zcmZqG->T1>&&$ij00fdh#4=ZH$W+Fv$t|L98QV4&iLo*A3JLM? M@ql#jF^Vt(09AN9SpWb4 delta 183 zcmdn0->T1>&&$ij00f3}L^4A+@=oJpteDKmDKq&1pQ}uYK#E`sLlnO|LyAy}a0^3< za5GbsK#E8(gQnIewag zlM@A#n2OXUf8ds#{92HcTL`R%kCBg2U^Amo79(TPnF>JPGzi| byhZdaW9#NcVr-1OfjHKlixb4kVyj_H;q7Gvi*P|iQutE%p(5Nh%rz`EtTpU4Y&9G;oHblE+$jQc zIBR)I_-hza1Z#MjnS&WLg(hDTYgS^sC0LS~n;M^7l$uzQ8XunmB;7O_Z?R_<#{-2@ zG&v`SiH8~A;xEZBNz92)PRz+kN=(i!1_`7jRw4-ql;(jnz(ppn7MElcn0!<`$(s}C zsUkKYp~+jM1f*`Uf;HdbC{Inzj!#U_FDeJAr~na_Afg&XRDp;Z5K#*v>Oe&OO>NboTgbx%&0RAs!ixmQw_ Sk*yPCT=(P+Qq{~Re2f5vJ#KdZ delta 224 zcmaEAzRrv~L4RZ}k4Qma14O17dC#L5Yl>$YJ%0NUph^Pb+6(FK&GP|TMquXRtNlPInW+p}$Wc$U# vS=2naPqLZ!Gf-VI6OiCzDr%i9C#A|5wb?~VmXWO)WI*fWbm?kl9X>_?7y&Y^ 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 0000000000000000000000000000000000000000..342d1873a3bb17ba53027c9a6eb81bee0f39f732 GIT binary patch literal 668 zcmZ8fy>1gh5Z<4&?`$~&A;ph?BhBu9Go$%-zFobu(`RHSzlQmD&e#tR?pi?LfZRT$ z1sL#xl|1I9hy`ay3V5O2 z`PSS}+Ys<9T@ZS%*Y!1dP5W0📊 Статистика / + {% 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 732e5fc42ea84ac9b028884bb8abbfdcc50cfcfc..220100487ff3280779a7c859f3120dc312fef166 100644 GIT binary patch literal 751 zcmY*X&5qMB5cW8~E!jmB#5>(25hU z{tELZwaK+C#5B|)o^y#>HL|qE z#UJa|O6{UjZF>8I=@Ev#`TgPL)8+Zrep3CN%g?oau~~*~Zqb z(HY)yL9Z@cNO7;9rZROxCZ(i&I2BnXitRx{O@GKeB%VHUph*GGHRBmfKvVPP(~*mZ zr!_Hkai@gD3WkoosaLwtTbF8C)rNXDHZ^`0NrHN