From 58790c3b1604ae1eb27853d4ea34b2a8865af141 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 21 Mar 2025 17:23:51 +0100 Subject: [PATCH] Add skeleton service detail page --- .../organizations/service_detail.html | 126 ++++++++++++++++++ src/servala/frontend/urls.py | 7 +- src/servala/frontend/views/__init__.py | 5 +- src/servala/frontend/views/mixins.py | 4 +- src/servala/frontend/views/service.py | 23 +++- 5 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 src/servala/frontend/templates/frontend/organizations/service_detail.html diff --git a/src/servala/frontend/templates/frontend/organizations/service_detail.html b/src/servala/frontend/templates/frontend/organizations/service_detail.html new file mode 100644 index 0000000..bdff6b9 --- /dev/null +++ b/src/servala/frontend/templates/frontend/organizations/service_detail.html @@ -0,0 +1,126 @@ +{% extends "frontend/base.html" %} +{% load i18n %} +{% load static %} +{% block html_title %} + {% block page_title %} + {{ service.name }} + {% endblock page_title %} +{% endblock html_title %} +{% block content %} +
+
+
+ {% if service.logo %} + {{ service.name }} + {% endif %} +
+

{{ service.name }}

+ {{ service.category }} +
+
+
+
+

{{ service.description|default:"No description available." }}

+
+
+
+
+
+

{% trans "Available Offerings" %}

+
+
+ {% for offering in service.offerings.all %} +
+
+
+ {% if offering.provider.logo %} + {{ offering.provider.name }} + {% endif %} +
{{ service.name }} on {{ offering.provider.name }}
+
+
+
+
+
+

{{ offering.description|default:"No description available." }}

+
+
+
{% trans "Control Planes" %}
+
    + {% for control_plane in offering.control_planes.all %} +
  • {{ control_plane.name }}
  • + {% empty %} +
  • {% trans "No control planes available" %}
  • + {% endfor %} +
+
+
+
{% trans "Available Plans" %}
+
+ + + + + + + + + + + + {% for plan in plans %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Plan" %}{% trans "Term" %}{% trans "Features" %}{% trans "Pricing" %}{% trans "Actions" %}
+ {{ plan.name }} + {% if plan.description %} +
+ {{ plan.description }} + {% endif %} +
{{ plan.term }} {% trans "months" %} + {% if plan.features %} +
    + {% for feature, value in plan.features.items %}
  • {{ feature }}: {{ value }}
  • {% endfor %} +
+ {% else %} + {% trans "No features specified" %} + {% endif %} +
+ {% if plan.pricing %} +
    + {% for price_type, price in plan.pricing.items %}
  • {{ price_type }}: {{ price }}
  • {% endfor %} +
+ {% else %} + {% trans "No pricing specified" %} + {% endif %} +
+ +
{% trans "No plans available for this offering" %}
+
+
+
+ {% empty %} +
{% trans "No offerings available for this service yet." %}
+ {% endfor %} +
+
+
+{% endblock content %} diff --git a/src/servala/frontend/urls.py b/src/servala/frontend/urls.py index 43a4a51..75deb43 100644 --- a/src/servala/frontend/urls.py +++ b/src/servala/frontend/urls.py @@ -22,7 +22,12 @@ urlpatterns = [ ), path( "services/", - views.OrganizationServicesView.as_view(), + views.ServiceListView.as_view(), + name="organization.services", + ), + path( + "services//", + views.ServiceDetailView.as_view(), name="organization.services", ), path( diff --git a/src/servala/frontend/views/__init__.py b/src/servala/frontend/views/__init__.py index a9f323a..dcd8d9e 100644 --- a/src/servala/frontend/views/__init__.py +++ b/src/servala/frontend/views/__init__.py @@ -5,7 +5,7 @@ from .organization import ( OrganizationDashboardView, OrganizationUpdateView, ) -from .service import OrganizationServicesView +from .service import ServiceDetailView, ServiceListView __all__ = [ "IndexView", @@ -13,6 +13,7 @@ __all__ = [ "OrganizationCreateView", "OrganizationDashboardView", "OrganizationUpdateView", - "OrganizationServicesView", + "ServiceDetailView", + "ServiceListView", "ProfileView", ] diff --git a/src/servala/frontend/views/mixins.py b/src/servala/frontend/views/mixins.py index 502a26a..f304e42 100644 --- a/src/servala/frontend/views/mixins.py +++ b/src/servala/frontend/views/mixins.py @@ -71,7 +71,9 @@ class OrganizationViewMixin(PermissionRequiredMixin): return self.request.organization def get_object(self): - return self.organization + if self.model == Organization: + return self.organization + return super().get_object() @cached_property def object(self): diff --git a/src/servala/frontend/views/service.py b/src/servala/frontend/views/service.py index 708205e..f407250 100644 --- a/src/servala/frontend/views/service.py +++ b/src/servala/frontend/views/service.py @@ -1,12 +1,12 @@ from django.utils.functional import cached_property -from django.views.generic import ListView +from django.views.generic import DetailView, ListView -from servala.core.models import Service +from servala.core.models import Plan, Service, ServiceOffering from servala.frontend.forms.service import ServiceFilterForm from servala.frontend.views.mixins import OrganizationViewMixin -class OrganizationServicesView(OrganizationViewMixin, ListView): +class ServiceListView(OrganizationViewMixin, ListView): """View to display all available services for an organization.""" template_name = "frontend/organizations/services.html" @@ -29,3 +29,20 @@ class OrganizationServicesView(OrganizationViewMixin, ListView): context = super().get_context_data(**kwargs) context["filter_form"] = self.filter_form return context + + +class ServiceDetailView(OrganizationViewMixin, DetailView): + """View to display details of a specific service and its offerings.""" + + template_name = "frontend/organizations/service_detail.html" + context_object_name = "service" + model = Service + permission_type = "view" + + def get_queryset(self): + return Service.objects.select_related("category").prefetch_related( + "offerings", + "offerings__provider", + "offerings__control_planes", + "offerings__plans", + )