From 819e13481e0ad1c362c740473291b1fbc89d5ae4 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 7 Oct 2025 14:34:18 +0200 Subject: [PATCH] Show non-deactivated services separately ref #38 --- src/servala/core/models/organization.py | 19 +++++++ .../frontend/organizations/services.html | 51 +++++++------------ .../templates/includes/service_card.html | 31 +++++++++++ src/servala/frontend/views/service.py | 3 ++ src/servala/static/css/servala.css | 23 +++++++++ 5 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 src/servala/frontend/templates/includes/service_card.html diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index 32dc12e..84453b9 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -146,12 +146,31 @@ class Organization(ServalaModelMixin, models.Model): def get_visible_services(self): from servala.core.models import Service + queryset = Service.objects.all() + if self.limit_osb_services.exists(): + queryset = self.limit_osb_services.all() + if self.limit_cloudproviders.exists(): + allowed_providers = self.limit_cloudproviders.all() + queryset = queryset.filter( + offerings__provider__in=allowed_providers + ).distinct() + return queryset.prefetch_related( + "offerings", "offerings__provider" + ).select_related("category") + + def get_deactivated_services(self): + from servala.core.models import Service + + if not self.limit_osb_services.exists(): + return Service.objects.none() + queryset = Service.objects.select_related("category") if self.limit_cloudproviders.exists(): allowed_providers = self.limit_cloudproviders.all() queryset = queryset.filter( offerings__provider__in=allowed_providers ).distinct() + queryset = queryset.exclude(id__in=self.limit_osb_services.all()) return queryset.prefetch_related("offerings", "offerings__provider") class Meta: diff --git a/src/servala/frontend/templates/frontend/organizations/services.html b/src/servala/frontend/templates/frontend/organizations/services.html index 3a48ff9..4b8c95a 100644 --- a/src/servala/frontend/templates/frontend/organizations/services.html +++ b/src/servala/frontend/templates/frontend/organizations/services.html @@ -16,40 +16,9 @@ -
+
{% for service in services %} -
-
- -
-
- {% if service.description %}

{{ service.description|urlize }}

{% endif %} -
-
- -
-
+
{% include "includes/service_card.html" %}
{% empty %}
@@ -60,6 +29,22 @@
{% endfor %}
+ {% if deactivated_services %} +
+
+
{% translate "You may also be interested in one of these …" %}
+

+ + {% translate "These services need to be enabled on Exoscale first before they become available in the Servala portal." %} +

+
+
+
+ {% for service in deactivated_services %} +
{% include "includes/service_card.html" %}
+ {% endfor %} +
+ {% endif %} {% endblock content %} diff --git a/src/servala/frontend/templates/includes/service_card.html b/src/servala/frontend/templates/includes/service_card.html new file mode 100644 index 0000000..0dae3ae --- /dev/null +++ b/src/servala/frontend/templates/includes/service_card.html @@ -0,0 +1,31 @@ +{% load i18n %} +
+ +
+
+ {% if service.description %}

{{ service.description|urlize }}

{% endif %} +
+
+ +
diff --git a/src/servala/frontend/views/service.py b/src/servala/frontend/views/service.py index 97d1a72..a0390b8 100644 --- a/src/servala/frontend/views/service.py +++ b/src/servala/frontend/views/service.py @@ -49,6 +49,9 @@ class ServiceListView(OrganizationViewMixin, ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["filter_form"] = self.filter_form + context["deactivated_services"] = ( + self.request.organization.get_deactivated_services() + ) return context diff --git a/src/servala/static/css/servala.css b/src/servala/static/css/servala.css index 9a59b8f..eb4fd01 100644 --- a/src/servala/static/css/servala.css +++ b/src/servala/static/css/servala.css @@ -279,3 +279,26 @@ html[data-bs-theme="dark"] .crd-form .nav-tabs .nav-link .mandatory-indicator { .crd-form .nav-tabs .nav-link.has-mandatory { position: relative; } + +.service-deactivated .card { + opacity: 50%; + cursor: not-allowed; + img { + opacity: 75% + } + h4, small, p { + color: var(--bs-secondary-color) !important; + } + a.btn-outline-secondary { + color: var(--bs-btn-disabled-color) !important; + background-color: var(--bs-btn-disabled-bg) !important; + border-color: var(--bs-btn-disabled-border-color) !important; + opacity: var(--bs-btn-disabled-opacity); + } + a.btn-secondary { + color: white !important; + } + a.btn { + pointer-events: none; + } +}