website/hub/services/admin.py

409 lines
11 KiB
Python
Raw Normal View History

2025-01-27 14:58:23 +01:00
from django.contrib import admin
2025-01-27 15:14:58 +01:00
from django.utils.html import format_html
2025-03-07 14:58:51 +01:00
from adminsortable2.admin import SortableAdminMixin
2025-05-20 14:26:31 +02:00
from import_export.admin import ImportExportModelAdmin
from import_export import resources
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
2025-03-07 14:58:51 +01:00
2025-01-28 08:53:00 +01:00
from .models import (
2025-01-28 13:55:43 +01:00
Category,
2025-01-28 08:53:00 +01:00
CloudProvider,
2025-05-20 11:26:52 +02:00
ComputePlan,
2025-05-20 15:27:45 +02:00
ComputePlanPrice,
2025-01-28 08:53:00 +01:00
ConsultingPartner,
2025-01-28 13:55:43 +01:00
ExternalLink,
2025-02-28 14:13:51 +01:00
ExternalLinkOffering,
Lead,
2025-01-28 10:41:39 +01:00
Plan,
2025-02-28 14:13:51 +01:00
ReusableText,
2025-01-28 13:55:43 +01:00
Service,
ServiceOffering,
2025-05-22 16:34:15 +02:00
StoragePlan,
StoragePlanPrice,
2025-05-20 15:27:45 +02:00
VSHNAppCatBaseFee,
VSHNAppCatPrice,
VSHNAppCatUnitRate,
2025-03-06 11:35:55 +01:00
WebsiteFaq,
2025-01-28 08:53:00 +01:00
)
2025-01-27 15:23:50 +01:00
2025-03-07 15:37:22 +01:00
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")
2025-03-07 15:37:22 +01:00
class OfferingInline(admin.StackedInline):
model = ServiceOffering
extra = 1
fieldsets = (
(
None,
{
"fields": (
"description",
"service",
"cloud_provider",
"offer_description",
)
},
),
)
2025-03-07 15:37:22 +01:00
show_change_link = True
2025-01-27 15:23:50 +01:00
2025-02-28 14:13:51 +01:00
@admin.register(ReusableText)
class ReusableTextAdmin(admin.ModelAdmin):
list_display = ("name",)
search_fields = ("name", "text")
ordering = ("name",)
2025-01-27 15:23:50 +01:00
@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")
2025-01-27 14:58:23 +01:00
2025-05-20 15:27:45 +02:00
class ComputePlanItemInline(admin.TabularInline):
2025-05-20 11:26:52 +02:00
model = ComputePlan
extra = 1
2025-05-20 15:27:45 +02:00
fields = ("name", "vcpus", "ram", "active", "valid_from", "valid_to")
2025-05-20 11:26:52 +02:00
2025-01-27 14:58:23 +01:00
@admin.register(CloudProvider)
class CloudProviderAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = (
"name",
"slug",
"logo_preview",
"disable_listing",
"is_featured",
"order",
)
2025-01-28 13:55:43 +01:00
search_fields = ("name", "description")
2025-01-27 15:54:37 +01:00
prepopulated_fields = {"slug": ("name",)}
inlines = [OfferingInline]
ordering = ("order",)
2025-01-27 14:58:23 +01:00
2025-01-27 15:14:58 +01:00
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
2025-01-28 13:55:43 +01:00
logo_preview.short_description = "Logo"
class ExternalLinkInline(admin.TabularInline):
model = ExternalLink
extra = 1
fields = ("description", "url", "order")
ordering = ("order", "description")
2025-01-27 14:58:23 +01:00
@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
list_display = (
"name",
"logo_preview",
"category_list",
"is_featured",
"is_coming_soon",
"disable_listing",
)
2025-01-28 13:55:43 +01:00
list_filter = ("categories",)
2025-01-27 17:00:56 +01:00
search_fields = ("name", "description", "slug")
prepopulated_fields = {"slug": ("name",)}
2025-01-28 13:55:43 +01:00
filter_horizontal = ("categories",)
inlines = [ExternalLinkInline, OfferingInline]
2025-01-27 15:14:58 +01:00
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
2025-01-27 15:23:50 +01:00
2025-01-28 13:55:43 +01:00
logo_preview.short_description = "Logo"
2025-01-27 15:23:50 +01:00
def category_list(self, obj):
return ", ".join([cat.name for cat in obj.categories.all()])
2025-01-28 13:55:43 +01:00
category_list.short_description = "Categories"
2025-01-28 08:53:00 +01:00
def partner_list(self, obj):
return ", ".join([partner.name for partner in obj.consulting_partners.all()])
partner_list.short_description = "Consulting Partners"
2025-01-28 13:55:43 +01:00
@admin.register(ServiceOffering)
class ServiceOfferingAdmin(admin.ModelAdmin):
2025-02-28 14:25:35 +01:00
list_display = ("service", "cloud_provider")
2025-01-28 13:55:43 +01:00
list_filter = ("service", "cloud_provider")
search_fields = ("service__name", "cloud_provider__name", "description")
2025-02-28 14:13:51 +01:00
inlines = [ExternalLinkOfferingInline, PlanInline]
2025-01-28 10:41:39 +01:00
2025-01-28 08:53:00 +01:00
@admin.register(ConsultingPartner)
class ConsultingPartnerAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = (
"name",
"website",
"logo_preview",
"disable_listing",
"is_featured",
"order",
)
2025-01-28 08:53:00 +01:00
search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)}
2025-01-28 13:55:43 +01:00
filter_horizontal = ("services", "cloud_providers")
ordering = ("order",)
2025-01-28 08:53:00 +01:00
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
2025-01-28 10:41:39 +01:00
2025-01-28 13:55:43 +01:00
logo_preview.short_description = "Logo"
2025-01-31 11:12:00 +01:00
@admin.register(Lead)
class LeadAdmin(admin.ModelAdmin):
list_display = ("name", "company", "created_at", "odoo_lead_id")
search_fields = ("name", "company")
2025-03-06 11:35:55 +01:00
@admin.register(WebsiteFaq)
2025-03-07 14:58:51 +01:00
class WebsiteFaqAdmin(SortableAdminMixin, admin.ModelAdmin):
2025-03-06 11:35:55 +01:00
list_display = ("question", "order")
search_fields = ("question", "answer")
ordering = ("order",)
2025-05-20 11:26:52 +02:00
2025-05-20 15:27:45 +02:00
class ComputePlanPriceInline(admin.TabularInline):
model = ComputePlanPrice
extra = 1
fields = ("currency", "amount")
2025-05-20 14:26:31 +02:00
class ComputePlanResource(resources.ModelResource):
cloud_provider = Field(
column_name="cloud_provider",
attribute="cloud_provider",
widget=ForeignKeyWidget(CloudProvider, "name"),
)
2025-05-22 16:34:15 +02:00
prices = Field(column_name="prices", attribute=None)
2025-05-20 14:26:31 +02:00
class Meta:
model = ComputePlan
skip_unchanged = True
report_skipped = False
import_id_fields = ["name"]
fields = (
"name",
"vcpus",
"ram",
"cpu_mem_ratio",
"cloud_provider",
2025-05-20 15:27:45 +02:00
"active",
2025-05-22 16:34:15 +02:00
"term",
2025-05-20 15:27:45 +02:00
"valid_from",
"valid_to",
2025-05-22 16:34:15 +02:00
"prices",
2025-05-20 14:26:31 +02:00
)
2025-05-22 16:34:15 +02:00
def dehydrate_prices(self, compute_plan):
prices = compute_plan.prices.all()
if not prices:
return ""
return "|".join([f"{p.currency} {p.amount}" for p in prices])
def save_m2m(self, instance, row, *args, **kwargs):
super().save_m2m(instance, row, *args, **kwargs)
# Handle prices
if "prices" in row and row["prices"]:
# Clear existing prices first
instance.prices.all().delete()
# Create new prices
price_entries = row["prices"].split("|")
for entry in price_entries:
if " " in entry:
currency, amount = entry.split(" ")
ComputePlanPrice.objects.create(
compute_plan=instance, currency=currency, amount=amount
)
2025-05-20 14:26:31 +02:00
2025-05-20 11:26:52 +02:00
@admin.register(ComputePlan)
2025-05-20 14:26:31 +02:00
class ComputePlansAdmin(ImportExportModelAdmin):
resource_class = ComputePlanResource
2025-05-20 15:27:45 +02:00
list_display = (
"name",
"cloud_provider",
"vcpus",
"ram",
2025-05-22 16:34:15 +02:00
"term",
2025-05-20 15:27:45 +02:00
"display_prices",
"active",
)
search_fields = ("name", "cloud_provider__name")
list_filter = ("active", "cloud_provider")
2025-05-20 11:26:52 +02:00
ordering = ("name",)
2025-05-20 15:27:45 +02:00
inlines = [ComputePlanPriceInline]
def display_prices(self, obj):
prices = obj.prices.all()
if not prices:
return "No prices set"
return format_html("<br>".join([f"{p.amount} {p.currency}" for p in prices]))
display_prices.short_description = "Prices (Amount Currency)"
class VSHNAppCatBaseFeeInline(admin.TabularInline):
model = VSHNAppCatBaseFee
extra = 1
fields = ("currency", "amount")
class VSHNAppCatUnitRateInline(admin.TabularInline):
model = VSHNAppCatUnitRate
extra = 1
fields = ("currency", "service_level", "amount")
@admin.register(VSHNAppCatPrice)
class VSHNAppCatPriceAdmin(admin.ModelAdmin):
list_display = (
"service",
"variable_unit",
2025-05-22 16:34:15 +02:00
"term",
2025-05-20 15:27:45 +02:00
"admin_display_base_fees",
"admin_display_unit_rates",
)
list_filter = ("variable_unit", "service")
search_fields = ("service__name",)
inlines = [VSHNAppCatBaseFeeInline, VSHNAppCatUnitRateInline]
def admin_display_base_fees(self, obj):
fees = obj.base_fees.all()
if not fees:
return "No base fees"
return format_html(
"<br>".join([f"{fee.amount} {fee.currency}" for fee in fees])
)
admin_display_base_fees.short_description = "Base Fees"
def admin_display_unit_rates(self, obj):
rates = obj.unit_rates.all()
if not rates:
return "No unit rates"
return format_html(
"<br>".join(
[
f"{rate.amount} {rate.currency} ({rate.get_service_level_display()})"
for rate in rates
]
)
)
admin_display_unit_rates.short_description = "Unit Rates"
2025-05-22 16:34:15 +02:00
class StoragePlanPriceInline(admin.TabularInline):
model = StoragePlanPrice
extra = 1
fields = ("currency", "amount")
class StoragePlanResource(resources.ModelResource):
cloud_provider = Field(
column_name="cloud_provider",
attribute="cloud_provider",
widget=ForeignKeyWidget(CloudProvider, "name"),
)
prices = Field(column_name="prices", attribute=None)
class Meta:
model = StoragePlan
skip_unchanged = True
report_skipped = False
import_id_fields = ["name"]
fields = (
"name",
"cloud_provider",
"term",
"unit",
"valid_from",
"valid_to",
"prices",
)
def dehydrate_prices(self, storage_plan):
prices = storage_plan.prices.all()
if not prices:
return ""
return "|".join([f"{p.currency} {p.amount}" for p in prices])
def save_m2m(self, instance, row, *args, **kwargs):
super().save_m2m(instance, row, *args, **kwargs)
# Handle prices
if "prices" in row and row["prices"]:
# Clear existing prices first
instance.prices.all().delete()
# Create new prices
price_entries = row["prices"].split("|")
for entry in price_entries:
if " " in entry:
currency, amount = entry.split(" ")
StoragePlanPrice.objects.create(
storage_plan=instance, currency=currency, amount=amount
)
@admin.register(StoragePlan)
class StoragePlanAdmin(ImportExportModelAdmin):
resource_class = StoragePlanResource
list_display = (
"name",
"cloud_provider",
"term",
"unit",
"display_prices",
)
search_fields = ("name", "cloud_provider__name")
list_filter = ("cloud_provider",)
ordering = ("name",)
inlines = [StoragePlanPriceInline]
def display_prices(self, obj):
prices = obj.prices.all()
if not prices:
return "No prices set"
return format_html("<br>".join([f"{p.amount} {p.currency}" for p in prices]))
display_prices.short_description = "Prices (Amount Currency)"