From 9e0ccb6025eb4df3721c84487b8fb3465b4a7747 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 23 Jun 2025 11:58:09 +0200 Subject: [PATCH] plan price admin --- hub/services/admin/services.py | 72 +++++++++++++++++++++++++++++++--- hub/settings.py | 2 + 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/hub/services/admin/services.py b/hub/services/admin/services.py index 44f848b..8d86c08 100644 --- a/hub/services/admin/services.py +++ b/hub/services/admin/services.py @@ -5,7 +5,14 @@ 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 +from ..models import ( + Service, + ServiceOffering, + ExternalLink, + ExternalLinkOffering, + Plan, + PlanPrice, +) class ExternalLinkInline(admin.TabularInline): @@ -26,14 +33,22 @@ class ExternalLinkOfferingInline(admin.TabularInline): ordering = ("order", "description") +class PlanPriceInline(admin.TabularInline): + """Inline admin for PlanPrice model""" + + model = PlanPrice + extra = 1 + fields = ("currency", "amount") + ordering = ("currency",) + + class PlanInline(admin.StackedInline): """Inline admin for Plan model""" model = Plan extra = 1 - fieldsets = ( - (None, {"fields": ("name", "description", "pricing", "plan_description")}), - ) + fieldsets = ((None, {"fields": ("name", "description", "plan_description")}),) + show_change_link = True # This allows clicking through to the Plan admin where prices can be managed class OfferingInline(admin.StackedInline): @@ -102,7 +117,54 @@ class ServiceAdmin(admin.ModelAdmin): class ServiceOfferingAdmin(admin.ModelAdmin): """Admin configuration for ServiceOffering model""" - list_display = ("service", "cloud_provider") + list_display = ("service", "cloud_provider", "plan_count", "total_prices") list_filter = ("service", "cloud_provider") search_fields = ("service__name", "cloud_provider__name", "description") inlines = [ExternalLinkOfferingInline, PlanInline] + + def plan_count(self, obj): + """Display number of plans for this offering""" + return obj.plans.count() + + plan_count.short_description = "Plans" + + def total_prices(self, obj): + """Display total number of plan prices for this offering""" + total = sum(plan.plan_prices.count() for plan in obj.plans.all()) + return f"{total} prices" + + total_prices.short_description = "Total Prices" + + +@admin.register(Plan) +class PlanAdmin(admin.ModelAdmin): + """Admin configuration for Plan model""" + + list_display = ("name", "offering", "price_summary") + list_filter = ("offering__service", "offering__cloud_provider") + search_fields = ("name", "description", "offering__service__name") + inlines = [PlanPriceInline] + + def price_summary(self, obj): + """Display a summary of prices for this plan""" + prices = obj.plan_prices.all() + if prices: + price_strs = [f"{price.amount} {price.currency}" for price in prices] + return ", ".join(price_strs) + return "No prices set" + + price_summary.short_description = "Prices" + + +@admin.register(PlanPrice) +class PlanPriceAdmin(admin.ModelAdmin): + """Admin configuration for PlanPrice model""" + + list_display = ("plan", "currency", "amount") + list_filter = ( + "currency", + "plan__offering__service", + "plan__offering__cloud_provider", + ) + search_fields = ("plan__name", "plan__offering__service__name") + ordering = ("plan__offering__service__name", "plan__name", "currency") diff --git a/hub/settings.py b/hub/settings.py index 409b3d6..8e6f3e6 100644 --- a/hub/settings.py +++ b/hub/settings.py @@ -255,6 +255,8 @@ JAZZMIN_SETTINGS = { "services.ProgressiveDiscountModel": "single", "services.VSHNAppCatPrice": "single", "services.VSHNAppCatAddon": "single", + "services.ServiceOffering": "single", + "services.Plan": "single", }, "related_modal_active": True, }