dashboard: кастомизируемый дашборд из виджетов + шаблоны по умолчанию
Дашборд теперь собирается из виджетов (приветствие, метрики, мои задачи, новости, быстрый доступ). Сотрудник настраивает состав и порядок под себя (вкл/выкл + перемещение ↑↓), а для тех, кто не настраивал, действует набор по умолчанию по типу локации (Офис/Аптека/Склад), задаётся в админке. - core/dashboard.py: реестр виджетов + сборка раскладки - core/models/dashboard.py: DashboardLayout (персональная), DashboardDefaults (синглтон по типу локации) - core/views.py: dashboard_view через реестр + dashboard_customize_view - templates/pages/dashboard_widgets/*: виджеты-партиалы; dashboard.html — цикл по виджетам; dashboard_customize.html — настройка (HTMX + ↑↓) - admin: DashboardDefaults (чекбоксами по локации) + read-only раскладки; пункт меню «Дашборд по умолчанию» - core/tests_dashboard.py: 11 тестов (логика раскладки + view) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+71
-2
@@ -11,7 +11,11 @@ from unfold.contrib.filters.admin import DropdownFilter
|
||||
from treenode.forms import TreeNodeForm
|
||||
from django.forms import TextInput, Textarea
|
||||
from django.db.models import Count
|
||||
from core.models import DirectoryType, Directory, ConnectorConfig, AccessSystem
|
||||
from django import forms
|
||||
from core.models import (
|
||||
DirectoryType, Directory, ConnectorConfig, AccessSystem,
|
||||
DashboardDefaults, DashboardLayout,
|
||||
)
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
from unfold.contrib.import_export.forms import ExportForm, ImportForm
|
||||
|
||||
@@ -289,4 +293,69 @@ class ConnectorConfigAdmin(ModelAdmin):
|
||||
color, obj.get_connector_type_display()
|
||||
)
|
||||
connector_type_display.short_description = _('Коннектор')
|
||||
connector_type_display.admin_order_field = 'connector_type'
|
||||
connector_type_display.admin_order_field = 'connector_type'
|
||||
|
||||
|
||||
class DashboardDefaultsForm(forms.ModelForm):
|
||||
"""Наборы виджетов дашборда по умолчанию — чекбоксами по типу локации."""
|
||||
|
||||
class Meta:
|
||||
model = DashboardDefaults
|
||||
fields = ('office', 'pharmacy', 'warehouse')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
from core.dashboard import DASHBOARD_WIDGETS, valid_keys
|
||||
choices = [(w.key, f'{w.label} — {w.description}') for w in DASHBOARD_WIDGETS]
|
||||
for field in ('office', 'pharmacy', 'warehouse'):
|
||||
self.fields[field] = forms.MultipleChoiceField(
|
||||
label=self._meta.model._meta.get_field(field).verbose_name,
|
||||
required=False, choices=choices, widget=forms.CheckboxSelectMultiple,
|
||||
)
|
||||
if self.instance and self.instance.pk:
|
||||
self.initial[field] = valid_keys(getattr(self.instance, field) or [])
|
||||
|
||||
def clean(self):
|
||||
from core.dashboard import DEFAULT_WIDGET_KEYS
|
||||
cleaned = super().clean()
|
||||
for field in ('office', 'pharmacy', 'warehouse'):
|
||||
selected = set(cleaned.get(field) or [])
|
||||
# сохраняем в порядке реестра виджетов
|
||||
cleaned[field] = [k for k in DEFAULT_WIDGET_KEYS if k in selected]
|
||||
return cleaned
|
||||
|
||||
|
||||
@admin.register(DashboardDefaults)
|
||||
class DashboardDefaultsAdmin(ModelAdmin):
|
||||
form = DashboardDefaultsForm
|
||||
fieldsets = (
|
||||
(_('Виджеты дашборда по типу локации'), {
|
||||
'fields': ('office', 'pharmacy', 'warehouse'),
|
||||
'description': _(
|
||||
'Набор виджетов, который видит сотрудник, ещё не настроивший '
|
||||
'свой дашборд. Порядок — как в списке ниже; пользователь может '
|
||||
'переопределить состав и порядок под себя.'
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return not DashboardDefaults.objects.exists()
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
@admin.register(DashboardLayout)
|
||||
class DashboardLayoutAdmin(ModelAdmin):
|
||||
"""Персональные раскладки (read-only; удалить = сбросить к дефолту)."""
|
||||
list_display = ('employee', 'widgets_display', 'updated_at')
|
||||
search_fields = ('employee__username', 'employee__last_name', 'employee__first_name')
|
||||
readonly_fields = ('employee', 'widgets', 'updated_at')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def widgets_display(self, obj):
|
||||
return ', '.join(obj.widgets or []) or '—'
|
||||
widgets_display.short_description = _('Виджеты')
|
||||
Reference in New Issue
Block a user