Add skeleton service detail page

This commit is contained in:
Tobias Kunze 2025-03-21 17:23:51 +01:00 committed by Tobias Brunner
parent 4fcc9154b6
commit 58790c3b16
No known key found for this signature in database
5 changed files with 158 additions and 7 deletions

View file

@ -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 %}
<section class="section">
<div class="card">
<div class="card-header d-flex align-items-center">
{% if service.logo %}
<img src="{{ service.logo.url }}"
alt="{{ service.name }}"
class="me-3"
style="max-width: 48px;
max-height: 48px">
{% endif %}
<div class="d-flex flex-column">
<h4 class="card-title mb-0">{{ service.name }}</h4>
<small class="text-muted">{{ service.category }}</small>
</div>
</div>
<div class="card-body">
<div class="row">
<p>{{ service.description|default:"No description available." }}</p>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h4 class="card-title">{% trans "Available Offerings" %}</h4>
</div>
<div class="card-body">
{% for offering in service.offerings.all %}
<div class="card mb-4">
<div class="card-header bg-light">
<div class="d-flex align-items-center">
{% if offering.provider.logo %}
<img src="{{ offering.provider.logo.url }}"
alt="{{ offering.provider.name }}"
class="me-3"
style="max-height: 40px">
{% endif %}
<h5 class="mb-0">{{ service.name }} on {{ offering.provider.name }}</h5>
</div>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-8">
<p>{{ offering.description|default:"No description available." }}</p>
</div>
<div class="col-md-4">
<h6>{% trans "Control Planes" %}</h6>
<ul>
{% for control_plane in offering.control_planes.all %}
<li>{{ control_plane.name }}</li>
{% empty %}
<li>{% trans "No control planes available" %}</li>
{% endfor %}
</ul>
</div>
</div>
<h6>{% trans "Available Plans" %}</h6>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Plan" %}</th>
<th>{% trans "Term" %}</th>
<th>{% trans "Features" %}</th>
<th>{% trans "Pricing" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for plan in plans %}
<tr>
<td>
<strong>{{ plan.name }}</strong>
{% if plan.description %}
<br>
<small>{{ plan.description }}</small>
{% endif %}
</td>
<td>{{ plan.term }} {% trans "months" %}</td>
<td>
{% if plan.features %}
<ul class="mb-0">
{% for feature, value in plan.features.items %}<li>{{ feature }}: {{ value }}</li>{% endfor %}
</ul>
{% else %}
<span class="text-muted">{% trans "No features specified" %}</span>
{% endif %}
</td>
<td>
{% if plan.pricing %}
<ul class="mb-0">
{% for price_type, price in plan.pricing.items %}<li>{{ price_type }}: {{ price }}</li>{% endfor %}
</ul>
{% else %}
<span class="text-muted">{% trans "No pricing specified" %}</span>
{% endif %}
</td>
<td>
<button class="btn btn-primary btn-sm">{% trans "Order" %}</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center">{% trans "No plans available for this offering" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% empty %}
<div class="alert alert-info">{% trans "No offerings available for this service yet." %}</div>
{% endfor %}
</div>
</div>
</section>
{% endblock content %}

View file

@ -22,7 +22,12 @@ urlpatterns = [
),
path(
"services/",
views.OrganizationServicesView.as_view(),
views.ServiceListView.as_view(),
name="organization.services",
),
path(
"services/<int:pk>/",
views.ServiceDetailView.as_view(),
name="organization.services",
),
path(

View file

@ -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",
]

View file

@ -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):

View file

@ -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",
)