Добавлена функциональность системного клиента для анонимных покупок

- Добавлено поле is_system_customer в модель Customer с индексом
- Системный клиент создается автоматически при создании нового тенанта
- Реализована защита системного клиента от редактирования и удаления:
  - Защита на уровне модели (save/delete методы)
  - Защита на уровне формы (валидация)
  - Защита на уровне представлений (проверки с дружественными сообщениями)
  - Защита в админке (readonly поля, запрет удаления)
- Системный клиент скрыт из списков и поиска на фронтенде
- Создан информационный шаблон для отображения системного клиента
- Исправлена обработка NULL значений для полей email/phone (Django best practice)
- Добавлено отображение "Не указано" вместо None в карточке клиента

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-20 00:07:38 +03:00
parent 755e4fc9d9
commit 685c06d94d
8 changed files with 215 additions and 65 deletions

View File

@@ -21,6 +21,24 @@ class IsVipFilter(admin.SimpleListFilter):
return queryset
class IsSystemCustomerFilter(admin.SimpleListFilter):
title = 'Системный клиент'
parameter_name = 'is_system_customer'
def lookups(self, request, model_admin):
return (
('yes', 'Системный'),
('no', 'Обычный'),
)
def queryset(self, request, queryset):
if self.value() == 'yes':
return queryset.filter(is_system_customer=True)
if self.value() == 'no':
return queryset.filter(is_system_customer=False)
return queryset
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
"""Административный интерфейс для управления клиентами цветочного магазина"""
@@ -31,11 +49,13 @@ class CustomerAdmin(admin.ModelAdmin):
'loyalty_tier',
'total_spent',
'is_vip',
'is_system_customer',
'created_at'
)
list_filter = (
'loyalty_tier',
IsVipFilter,
IsSystemCustomerFilter,
'created_at'
)
search_fields = (
@@ -45,19 +65,45 @@ class CustomerAdmin(admin.ModelAdmin):
)
date_hierarchy = 'created_at'
ordering = ('-created_at',)
readonly_fields = ('created_at', 'updated_at', 'total_spent', 'is_vip')
readonly_fields = ('created_at', 'updated_at', 'total_spent', 'is_vip', 'is_system_customer')
fieldsets = (
('Основная информация', {
'fields': ('name', 'email', 'phone')
'fields': ('name', 'email', 'phone', 'is_system_customer')
}),
('Программа лояльности', {
'fields': ('loyalty_tier', 'total_spent', 'is_vip'),
'classes': ('collapse',)
}),
('Заметки', {
'fields': ('notes',)
}),
('Даты', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def get_readonly_fields(self, request, obj=None):
"""Делаем все поля read-only для системного клиента"""
if obj and obj.is_system_customer:
# Для системного клиента все поля только для чтения
return ['name', 'email', 'phone', 'loyalty_tier', 'total_spent', 'is_vip', 'is_system_customer', 'notes', 'created_at', 'updated_at']
return self.readonly_fields
def has_delete_permission(self, request, obj=None):
"""Запрет на удаление системного клиента"""
if obj and obj.is_system_customer:
return False
return super().has_delete_permission(request, obj)
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
"""Добавляем предупреждение для системного клиента"""
extra_context = extra_context or {}
if object_id:
obj = self.get_object(request, object_id)
if obj and obj.is_system_customer:
extra_context['readonly'] = True
from django.contrib import messages
messages.warning(request, 'Это системный клиент. Редактирование запрещено для обеспечения корректной работы системы.')
return super().changeform_view(request, object_id, form_url, extra_context)