refactor admin into several files

This commit is contained in:
Tobias Brunner 2025-05-26 11:33:04 +02:00
parent d9a04655ed
commit a3cf1cc590
No known key found for this signature in database
7 changed files with 340 additions and 184 deletions

View file

@ -0,0 +1,9 @@
# Admin module initialization
# Import all admin classes to register them with Django admin
from .base import *
from .content import *
from .leads import *
from .pricing import *
from .providers import *
from .services import *

View file

@ -0,0 +1,38 @@
"""
Base admin classes and common functionality
"""
from django.contrib import admin
from django.utils.html import format_html
from adminsortable2.admin import SortableAdminMixin
from ..models import ReusableText, Category, WebsiteFaq
@admin.register(ReusableText)
class ReusableTextAdmin(admin.ModelAdmin):
"""Admin configuration for ReusableText model"""
list_display = ("name",)
search_fields = ("name", "text")
ordering = ("name",)
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
"""Admin configuration for Category model"""
list_display = ("name", "slug", "parent", "order")
list_filter = ("parent",)
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
ordering = ("order", "name")
@admin.register(WebsiteFaq)
class WebsiteFaqAdmin(SortableAdminMixin, admin.ModelAdmin):
"""Admin configuration for WebsiteFaq model"""
list_display = ("question", "order")
search_fields = ("question", "answer")
ordering = ("order",)

View file

@ -0,0 +1,35 @@
"""
Admin classes for content-related models like external links and plans
"""
from django.contrib import admin
from ..models import ExternalLink, ExternalLinkOffering, Plan
class PlanInline(admin.StackedInline):
"""Inline admin for Plan model"""
model = Plan
extra = 1
fieldsets = (
(None, {"fields": ("name", "description", "pricing", "plan_description")}),
)
class ExternalLinkOfferingInline(admin.TabularInline):
"""Inline admin for ExternalLinkOffering model"""
model = ExternalLinkOffering
extra = 1
fields = ("description", "url", "order")
ordering = ("order", "description")
class ExternalLinkInline(admin.TabularInline):
"""Inline admin for ExternalLink model"""
model = ExternalLink
extra = 1
fields = ("description", "url", "order")
ordering = ("order", "description")

View file

@ -0,0 +1,15 @@
"""
Admin classes for lead management
"""
from django.contrib import admin
from ..models import Lead
@admin.register(Lead)
class LeadAdmin(admin.ModelAdmin):
"""Admin configuration for Lead model"""
list_display = ("name", "company", "created_at", "odoo_lead_id")
search_fields = ("name", "company")

View file

@ -1,3 +1,7 @@
"""
Admin classes for pricing models including compute plans, storage plans, and VSHN AppCat pricing
"""
from django.contrib import admin from django.contrib import admin
from django.utils.html import format_html from django.utils.html import format_html
from adminsortable2.admin import SortableAdminMixin from adminsortable2.admin import SortableAdminMixin
@ -6,220 +10,55 @@ from import_export import resources
from import_export.fields import Field from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget from import_export.widgets import ForeignKeyWidget
from .models import ( from ..models import (
Category,
CloudProvider,
ComputePlan, ComputePlan,
ComputePlanGroup, ComputePlanGroup,
ComputePlanPrice, ComputePlanPrice,
ConsultingPartner, CloudProvider,
ExternalLink,
ExternalLinkOffering,
Lead,
Plan,
ProgressiveDiscountModel,
DiscountTier,
ReusableText,
Service,
ServiceOffering,
StoragePlan, StoragePlan,
StoragePlanPrice, StoragePlanPrice,
VSHNAppCatBaseFee, VSHNAppCatBaseFee,
VSHNAppCatPrice, VSHNAppCatPrice,
VSHNAppCatUnitRate, VSHNAppCatUnitRate,
WebsiteFaq, ProgressiveDiscountModel,
DiscountTier,
) )
class PlanInline(admin.StackedInline):
model = Plan
extra = 1
fieldsets = (
(None, {"fields": ("name", "description", "pricing", "plan_description")}),
)
class ExternalLinkOfferingInline(admin.TabularInline):
model = ExternalLinkOffering
extra = 1
fields = ("description", "url", "order")
ordering = ("order", "description")
class OfferingInline(admin.StackedInline):
model = ServiceOffering
extra = 1
fieldsets = (
(
None,
{
"fields": (
"description",
"service",
"cloud_provider",
"offer_description",
)
},
),
)
show_change_link = True
@admin.register(ReusableText)
class ReusableTextAdmin(admin.ModelAdmin):
list_display = ("name",)
search_fields = ("name", "text")
ordering = ("name",)
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ("name", "slug", "parent", "order")
list_filter = ("parent",)
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
ordering = ("order", "name")
class ComputePlanItemInline(admin.TabularInline):
model = ComputePlan
extra = 1
fields = ("name", "vcpus", "ram", "active", "valid_from", "valid_to")
@admin.register(CloudProvider)
class CloudProviderAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = (
"name",
"slug",
"logo_preview",
"disable_listing",
"is_featured",
"order",
)
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
inlines = [OfferingInline]
ordering = ("order",)
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
logo_preview.short_description = "Logo"
class ExternalLinkInline(admin.TabularInline):
model = ExternalLink
extra = 1
fields = ("description", "url", "order")
ordering = ("order", "description")
@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
list_display = (
"name",
"logo_preview",
"category_list",
"is_featured",
"is_coming_soon",
"disable_listing",
)
list_filter = ("categories",)
search_fields = ("name", "description", "slug")
prepopulated_fields = {"slug": ("name",)}
filter_horizontal = ("categories",)
inlines = [ExternalLinkInline, OfferingInline]
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
logo_preview.short_description = "Logo"
def category_list(self, obj):
return ", ".join([cat.name for cat in obj.categories.all()])
category_list.short_description = "Categories"
def partner_list(self, obj):
return ", ".join([partner.name for partner in obj.consulting_partners.all()])
partner_list.short_description = "Consulting Partners"
@admin.register(ServiceOffering)
class ServiceOfferingAdmin(admin.ModelAdmin):
list_display = ("service", "cloud_provider")
list_filter = ("service", "cloud_provider")
search_fields = ("service__name", "cloud_provider__name", "description")
inlines = [ExternalLinkOfferingInline, PlanInline]
@admin.register(ConsultingPartner)
class ConsultingPartnerAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = (
"name",
"website",
"logo_preview",
"disable_listing",
"is_featured",
"order",
)
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
filter_horizontal = ("services", "cloud_providers")
ordering = ("order",)
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
logo_preview.short_description = "Logo"
@admin.register(Lead)
class LeadAdmin(admin.ModelAdmin):
list_display = ("name", "company", "created_at", "odoo_lead_id")
search_fields = ("name", "company")
@admin.register(WebsiteFaq)
class WebsiteFaqAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = ("question", "order")
search_fields = ("question", "answer")
ordering = ("order",)
class ComputePlanPriceInline(admin.TabularInline): class ComputePlanPriceInline(admin.TabularInline):
"""Inline admin for ComputePlanPrice model"""
model = ComputePlanPrice model = ComputePlanPrice
extra = 1 extra = 1
fields = ("currency", "amount") fields = ("currency", "amount")
class ComputePlanItemInline(admin.TabularInline):
"""Inline admin for ComputePlan model"""
model = ComputePlan
extra = 1
fields = ("name", "vcpus", "ram", "active", "valid_from", "valid_to")
@admin.register(ComputePlanGroup) @admin.register(ComputePlanGroup)
class ComputePlanGroupAdmin(SortableAdminMixin, admin.ModelAdmin): class ComputePlanGroupAdmin(SortableAdminMixin, admin.ModelAdmin):
"""Admin configuration for ComputePlanGroup model"""
list_display = ("name", "node_label", "compute_plans_count") list_display = ("name", "node_label", "compute_plans_count")
search_fields = ("name", "description", "node_label") search_fields = ("name", "description", "node_label")
ordering = ("order",) ordering = ("order",)
def compute_plans_count(self, obj): def compute_plans_count(self, obj):
"""Display count of compute plans in this group"""
return obj.compute_plans.count() return obj.compute_plans.count()
compute_plans_count.short_description = "Compute Plans" compute_plans_count.short_description = "Compute Plans"
class ComputePlanResource(resources.ModelResource): class ComputePlanResource(resources.ModelResource):
"""Import/Export resource for ComputePlan model"""
cloud_provider = Field( cloud_provider = Field(
column_name="cloud_provider", column_name="cloud_provider",
attribute="cloud_provider", attribute="cloud_provider",
@ -252,12 +91,14 @@ class ComputePlanResource(resources.ModelResource):
) )
def dehydrate_prices(self, compute_plan): def dehydrate_prices(self, compute_plan):
"""Export prices in a custom format"""
prices = compute_plan.prices.all() prices = compute_plan.prices.all()
if not prices: if not prices:
return "" return ""
return "|".join([f"{p.currency} {p.amount}" for p in prices]) return "|".join([f"{p.currency} {p.amount}" for p in prices])
def save_m2m(self, instance, row, *args, **kwargs): def save_m2m(self, instance, row, *args, **kwargs):
"""Handle many-to-many relationships during import"""
super().save_m2m(instance, row, *args, **kwargs) super().save_m2m(instance, row, *args, **kwargs)
# Handle prices # Handle prices
@ -277,6 +118,8 @@ class ComputePlanResource(resources.ModelResource):
@admin.register(ComputePlan) @admin.register(ComputePlan)
class ComputePlansAdmin(ImportExportModelAdmin): class ComputePlansAdmin(ImportExportModelAdmin):
"""Admin configuration for ComputePlan model with import/export functionality"""
resource_class = ComputePlanResource resource_class = ComputePlanResource
list_display = ( list_display = (
"name", "name",
@ -294,6 +137,7 @@ class ComputePlansAdmin(ImportExportModelAdmin):
inlines = [ComputePlanPriceInline] inlines = [ComputePlanPriceInline]
def display_prices(self, obj): def display_prices(self, obj):
"""Display formatted prices for the list view"""
prices = obj.prices.all() prices = obj.prices.all()
if not prices: if not prices:
return "No prices set" return "No prices set"
@ -303,18 +147,24 @@ class ComputePlansAdmin(ImportExportModelAdmin):
class VSHNAppCatBaseFeeInline(admin.TabularInline): class VSHNAppCatBaseFeeInline(admin.TabularInline):
"""Inline admin for VSHNAppCatBaseFee model"""
model = VSHNAppCatBaseFee model = VSHNAppCatBaseFee
extra = 1 extra = 1
fields = ("currency", "amount") fields = ("currency", "amount")
class VSHNAppCatUnitRateInline(admin.TabularInline): class VSHNAppCatUnitRateInline(admin.TabularInline):
"""Inline admin for VSHNAppCatUnitRate model"""
model = VSHNAppCatUnitRate model = VSHNAppCatUnitRate
extra = 1 extra = 1
fields = ("currency", "service_level", "amount") fields = ("currency", "service_level", "amount")
class DiscountTierInline(admin.TabularInline): class DiscountTierInline(admin.TabularInline):
"""Inline admin for DiscountTier model"""
model = DiscountTier model = DiscountTier
extra = 1 extra = 1
fields = ("min_units", "max_units", "discount_percent") fields = ("min_units", "max_units", "discount_percent")
@ -323,6 +173,8 @@ class DiscountTierInline(admin.TabularInline):
@admin.register(ProgressiveDiscountModel) @admin.register(ProgressiveDiscountModel)
class ProgressiveDiscountModelAdmin(admin.ModelAdmin): class ProgressiveDiscountModelAdmin(admin.ModelAdmin):
"""Admin configuration for ProgressiveDiscountModel"""
list_display = ("name", "description", "active") list_display = ("name", "description", "active")
search_fields = ("name", "description") search_fields = ("name", "description")
inlines = [DiscountTierInline] inlines = [DiscountTierInline]
@ -330,6 +182,8 @@ class ProgressiveDiscountModelAdmin(admin.ModelAdmin):
@admin.register(VSHNAppCatPrice) @admin.register(VSHNAppCatPrice)
class VSHNAppCatPriceAdmin(admin.ModelAdmin): class VSHNAppCatPriceAdmin(admin.ModelAdmin):
"""Admin configuration for VSHNAppCatPrice model"""
list_display = ( list_display = (
"service", "service",
"variable_unit", "variable_unit",
@ -343,6 +197,7 @@ class VSHNAppCatPriceAdmin(admin.ModelAdmin):
inlines = [VSHNAppCatBaseFeeInline, VSHNAppCatUnitRateInline] inlines = [VSHNAppCatBaseFeeInline, VSHNAppCatUnitRateInline]
def admin_display_base_fees(self, obj): def admin_display_base_fees(self, obj):
"""Display base fees in admin list view"""
fees = obj.base_fees.all() fees = obj.base_fees.all()
if not fees: if not fees:
return "No base fees" return "No base fees"
@ -353,6 +208,7 @@ class VSHNAppCatPriceAdmin(admin.ModelAdmin):
admin_display_base_fees.short_description = "Base Fees" admin_display_base_fees.short_description = "Base Fees"
def admin_display_unit_rates(self, obj): def admin_display_unit_rates(self, obj):
"""Display unit rates in admin list view"""
rates = obj.unit_rates.all() rates = obj.unit_rates.all()
if not rates: if not rates:
return "No unit rates" return "No unit rates"
@ -369,12 +225,16 @@ class VSHNAppCatPriceAdmin(admin.ModelAdmin):
class StoragePlanPriceInline(admin.TabularInline): class StoragePlanPriceInline(admin.TabularInline):
"""Inline admin for StoragePlanPrice model"""
model = StoragePlanPrice model = StoragePlanPrice
extra = 1 extra = 1
fields = ("currency", "amount") fields = ("currency", "amount")
class StoragePlanResource(resources.ModelResource): class StoragePlanResource(resources.ModelResource):
"""Import/Export resource for StoragePlan model"""
cloud_provider = Field( cloud_provider = Field(
column_name="cloud_provider", column_name="cloud_provider",
attribute="cloud_provider", attribute="cloud_provider",
@ -398,12 +258,14 @@ class StoragePlanResource(resources.ModelResource):
) )
def dehydrate_prices(self, storage_plan): def dehydrate_prices(self, storage_plan):
"""Export prices in a custom format"""
prices = storage_plan.prices.all() prices = storage_plan.prices.all()
if not prices: if not prices:
return "" return ""
return "|".join([f"{p.currency} {p.amount}" for p in prices]) return "|".join([f"{p.currency} {p.amount}" for p in prices])
def save_m2m(self, instance, row, *args, **kwargs): def save_m2m(self, instance, row, *args, **kwargs):
"""Handle many-to-many relationships during import"""
super().save_m2m(instance, row, *args, **kwargs) super().save_m2m(instance, row, *args, **kwargs)
# Handle prices # Handle prices
@ -423,6 +285,8 @@ class StoragePlanResource(resources.ModelResource):
@admin.register(StoragePlan) @admin.register(StoragePlan)
class StoragePlanAdmin(ImportExportModelAdmin): class StoragePlanAdmin(ImportExportModelAdmin):
"""Admin configuration for StoragePlan model with import/export functionality"""
resource_class = StoragePlanResource resource_class = StoragePlanResource
list_display = ( list_display = (
"name", "name",
@ -437,6 +301,7 @@ class StoragePlanAdmin(ImportExportModelAdmin):
inlines = [StoragePlanPriceInline] inlines = [StoragePlanPriceInline]
def display_prices(self, obj): def display_prices(self, obj):
"""Display formatted prices for the list view"""
prices = obj.prices.all() prices = obj.prices.all()
if not prices: if not prices:
return "No prices set" return "No prices set"

View file

@ -0,0 +1,86 @@
"""
Admin classes for cloud providers and consulting partners
"""
from django.contrib import admin
from django.utils.html import format_html
from adminsortable2.admin import SortableAdminMixin
from ..models import CloudProvider, ConsultingPartner, ServiceOffering
class OfferingInline(admin.StackedInline):
"""Inline admin for ServiceOffering model"""
model = ServiceOffering
extra = 1
fieldsets = (
(
None,
{
"fields": (
"description",
"service",
"cloud_provider",
"offer_description",
)
},
),
)
show_change_link = True
@admin.register(CloudProvider)
class CloudProviderAdmin(SortableAdminMixin, admin.ModelAdmin):
"""Admin configuration for CloudProvider model"""
list_display = (
"name",
"slug",
"logo_preview",
"disable_listing",
"is_featured",
"order",
)
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
inlines = [OfferingInline]
ordering = ("order",)
def logo_preview(self, obj):
"""Display logo preview in admin list view"""
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
logo_preview.short_description = "Logo"
@admin.register(ConsultingPartner)
class ConsultingPartnerAdmin(SortableAdminMixin, admin.ModelAdmin):
"""Admin configuration for ConsultingPartner model"""
list_display = (
"name",
"website",
"logo_preview",
"disable_listing",
"is_featured",
"order",
)
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
filter_horizontal = ("services", "cloud_providers")
ordering = ("order",)
def logo_preview(self, obj):
"""Display logo preview in admin list view"""
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
logo_preview.short_description = "Logo"

View file

@ -0,0 +1,108 @@
"""
Admin classes for services and service offerings
"""
from django.contrib import admin
from django.utils.html import format_html
from ..models import Service, ServiceOffering, ExternalLink, ExternalLinkOffering, Plan
class ExternalLinkInline(admin.TabularInline):
"""Inline admin for ExternalLink model"""
model = ExternalLink
extra = 1
fields = ("description", "url", "order")
ordering = ("order", "description")
class ExternalLinkOfferingInline(admin.TabularInline):
"""Inline admin for ExternalLinkOffering model"""
model = ExternalLinkOffering
extra = 1
fields = ("description", "url", "order")
ordering = ("order", "description")
class PlanInline(admin.StackedInline):
"""Inline admin for Plan model"""
model = Plan
extra = 1
fieldsets = (
(None, {"fields": ("name", "description", "pricing", "plan_description")}),
)
class OfferingInline(admin.StackedInline):
"""Inline admin for ServiceOffering model"""
model = ServiceOffering
extra = 1
fieldsets = (
(
None,
{
"fields": (
"description",
"service",
"cloud_provider",
"offer_description",
)
},
),
)
show_change_link = True
@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
"""Admin configuration for Service model"""
list_display = (
"name",
"logo_preview",
"category_list",
"is_featured",
"is_coming_soon",
"disable_listing",
)
list_filter = ("categories",)
search_fields = ("name", "description", "slug")
prepopulated_fields = {"slug": ("name",)}
filter_horizontal = ("categories",)
inlines = [ExternalLinkInline, OfferingInline]
def logo_preview(self, obj):
"""Display logo preview in admin list view"""
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
logo_preview.short_description = "Logo"
def category_list(self, obj):
"""Display categories as comma-separated list"""
return ", ".join([cat.name for cat in obj.categories.all()])
category_list.short_description = "Categories"
def partner_list(self, obj):
"""Display consulting partners as comma-separated list"""
return ", ".join([partner.name for partner in obj.consulting_partners.all()])
partner_list.short_description = "Consulting Partners"
@admin.register(ServiceOffering)
class ServiceOfferingAdmin(admin.ModelAdmin):
"""Admin configuration for ServiceOffering model"""
list_display = ("service", "cloud_provider")
list_filter = ("service", "cloud_provider")
search_fields = ("service__name", "cloud_provider__name", "description")
inlines = [ExternalLinkOfferingInline, PlanInline]