Исправлена ошибка public admin для мультитенантной архитектуры

Проблема: при входе в localhost/admin/ (public схема) возникала ошибка
"relation user_roles_userrole does not exist", так как tenant-only
таблицы не существуют в public схеме.

Решение:
- Создан TenantAdminOnlyMixin для скрытия tenant-only моделей от public admin
- Применён миксин ко всем ModelAdmin классам в tenant-only приложениях:
  user_roles, customers, orders, inventory, products
- Добавлена проверка _is_public_schema() в RoleBasedPermissionBackend
  для предотвращения запросов к tenant-only таблицам в public схеме

Теперь:
- localhost/admin/ показывает только public модели (Client, Domain, User)
- shop.localhost/admin/ показывает все модели магазина

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-31 01:05:47 +03:00
parent b59ad725cb
commit eb6a3c1874
7 changed files with 184 additions and 39 deletions

View File

@@ -1,3 +1,8 @@
"""
Админка для приложения products.
Все модели tenant-only, поэтому используют TenantAdminOnlyMixin
для скрытия от public admin (localhost/admin/).
"""
from django.contrib import admin
from django.utils.html import format_html
from django.utils import timezone
@@ -14,6 +19,7 @@ from .admin_displays import (
format_photo_inline_quality,
format_photo_preview_with_quality,
)
from tenants.admin_mixins import TenantAdminOnlyMixin
class DeletedFilter(admin.SimpleListFilter):
@@ -292,7 +298,7 @@ def disable_delete_selected(admin_class):
@admin.register(ProductVariantGroup)
class ProductVariantGroupAdmin(admin.ModelAdmin):
class ProductVariantGroupAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ['name', 'get_products_count', 'created_at']
search_fields = ['name', 'description']
list_filter = ['created_at']
@@ -303,7 +309,7 @@ class ProductVariantGroupAdmin(admin.ModelAdmin):
get_products_count.short_description = 'Товаров'
class ProductCategoryAdmin(admin.ModelAdmin):
class ProductCategoryAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ('photo_with_quality', 'name', 'sku', 'slug', 'parent', 'is_active', 'get_deleted_status')
list_filter = (DeletedFilter, 'is_active', QualityLevelFilter, 'parent')
prepopulated_fields = {'slug': ('name',)}
@@ -376,14 +382,14 @@ class ProductCategoryAdmin(admin.ModelAdmin):
photo_preview_large.short_description = "Превью основного фото"
class ProductTagAdmin(admin.ModelAdmin):
class ProductTagAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ('name', 'slug', 'is_active')
list_filter = ('is_active',)
prepopulated_fields = {'slug': ('name',)}
search_fields = ('name',)
class ProductAdmin(admin.ModelAdmin):
class ProductAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ('photo_with_quality', 'name', 'sku', 'get_categories_display', 'cost_price', 'price', 'sale_price', 'get_variant_groups_display', 'get_status_display')
list_filter = (DeletedFilter, QualityLevelFilter, 'categories', 'tags', 'variant_groups')
search_fields = ('name', 'sku', 'description', 'search_keywords')
@@ -576,7 +582,7 @@ class ProductAdmin(admin.ModelAdmin):
photo_preview_large.short_description = "Превью основного фото"
class ProductKitAdmin(admin.ModelAdmin):
class ProductKitAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ('photo_with_quality', 'name', 'slug', 'get_categories_display', 'get_price_display', 'is_temporary', 'get_order_link', 'get_status_display')
list_filter = (DeletedFilter, 'is_temporary', QualityLevelFilter, 'categories', 'tags')
prepopulated_fields = {'slug': ('name',)}
@@ -836,7 +842,7 @@ class ProductCategoryAdminWithPhotos(ProductCategoryAdmin):
@admin.register(KitItem)
class KitItemAdmin(admin.ModelAdmin):
class KitItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ['__str__', 'kit', 'get_type', 'quantity', 'has_priorities']
list_filter = ['kit']
list_select_related = ['kit', 'product', 'variant_group']
@@ -856,7 +862,7 @@ class KitItemAdmin(admin.ModelAdmin):
@admin.register(SKUCounter)
class SKUCounterAdmin(admin.ModelAdmin):
class SKUCounterAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ['counter_type', 'current_value', 'get_next_preview']
list_filter = ['counter_type']
readonly_fields = ['get_next_preview']
@@ -879,7 +885,7 @@ class SKUCounterAdmin(admin.ModelAdmin):
@admin.register(CostPriceHistory)
class CostPriceHistoryAdmin(admin.ModelAdmin):
class CostPriceHistoryAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
list_display = ['product', 'get_price_change', 'reason', 'created_at']
list_filter = ['reason', 'created_at', 'product']
search_fields = ['product__name', 'product__sku', 'notes']
@@ -965,7 +971,7 @@ class ConfigurableProductAttributeInline(admin.TabularInline):
@admin.register(ConfigurableProduct)
class ConfigurableProductAdmin(admin.ModelAdmin):
class ConfigurableProductAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
"""Админка для вариативных товаров"""
list_display = ('name', 'sku', 'status', 'get_options_count', 'created_at')
list_filter = ('status', 'created_at')