From a1a150b85e8c7bdadb21efe8d3889f3e0a1acd8e Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Tue, 25 Mar 2025 18:30:31 +0100 Subject: [PATCH] Show service ordering form --- src/servala/frontend/forms/service.py | 10 ++++- .../service_offering_detail.html | 33 ++++++++++----- src/servala/frontend/views/mixins.py | 41 ++++++++++--------- src/servala/frontend/views/service.py | 38 +++++++++++++++-- 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/src/servala/frontend/forms/service.py b/src/servala/frontend/forms/service.py index d7ea60f..0afb1b5 100644 --- a/src/servala/frontend/forms/service.py +++ b/src/servala/frontend/forms/service.py @@ -1,6 +1,6 @@ from django import forms -from servala.core.models import CloudProvider, ServiceCategory +from servala.core.models import CloudProvider, ControlPlane, ServiceCategory class ServiceFilterForm(forms.Form): @@ -20,3 +20,11 @@ class ServiceFilterForm(forms.Form): offerings__control_planes__cloud_provider=cloud_provider ) return queryset + + +class ControlPlaneSelectForm(forms.Form): + control_plane = forms.ModelChoiceField(queryset=ControlPlane.objects.none()) + + def __init__(self, *args, planes=None, **kwargs): + super().__init__(*args, **kwargs) + self.fields["control_plane"].queryset = planes diff --git a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html index 1ed9a12..bd6a575 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html @@ -1,11 +1,23 @@ {% extends "frontend/base.html" %} {% load i18n %} {% load static %} +{% load partials %} {% block html_title %} {% block page_title %} {{ offering }} {% endblock page_title %} {% endblock html_title %} +{% partialdef service-form %} +{% if service_form %} + {% if form_error %} +
+ {% translate "Oops! Something went wrong with the service form generation. Please try again later." %} +
+ {% else %} + {% include "includes/form.html" with form=service_form %} + {% endif %} +{% endif %} +{% endpartialdef %} {% block content %}
@@ -23,17 +35,16 @@
-
- {% if offering.control_planes.all.count > 1 %} -

{% translate "Please choose your zone." %}

- {% else %} -

- {% blocktranslate trimmed with zone=offering.control_planes.all.first.name %} - Your zone will be {{ zone }}. - {% endblocktranslate %} -

- {% endif %} -
+ {% if offering.control_planes.all.count == 0 %} +

{% translate "We currently cannot offer this service, sorry!" %}

+ {% else %} +
+ {{ select_form }} +
+
{% partial service-form %}
+ {% endif %}
diff --git a/src/servala/frontend/views/mixins.py b/src/servala/frontend/views/mixins.py index f304e42..fd5a494 100644 --- a/src/servala/frontend/views/mixins.py +++ b/src/servala/frontend/views/mixins.py @@ -5,13 +5,30 @@ from rules.contrib.views import AutoPermissionRequiredMixin, PermissionRequiredM from servala.core.models import Organization -class HtmxUpdateView(AutoPermissionRequiredMixin, UpdateView): +class HtmxViewMixin: fragments = [] @cached_property def is_htmx(self): return self.request.headers.get("HX-Request") + def _get_fragment(self): + if self.request.method == "POST": + fragment = self.request.POST.get("fragment") + else: + fragment = self.request.GET.get("fragment") + if fragment and fragment in self.fragments: + return fragment + + def get_template_names(self): + template_names = super().get_template_names() + if self.is_htmx and (fragment := self._get_fragment()): + return [f"{template_names[0]}#{fragment}"] + return template_names + + +class HtmxUpdateView(AutoPermissionRequiredMixin, HtmxViewMixin, UpdateView): + @property def permission_type(self): if self.request.method == "POST" or getattr( @@ -31,20 +48,6 @@ class HtmxUpdateView(AutoPermissionRequiredMixin, UpdateView): result["has_change_permission"] = self.has_change_permission() return result - def _get_fragment(self): - if self.request.method == "POST": - fragment = self.request.POST.get("fragment") - else: - fragment = self.request.GET.get("fragment") - if fragment and fragment in self.fragments: - return fragment - - def get_template_names(self): - template_names = super().get_template_names() - if self.is_htmx and (fragment := self._get_fragment()): - return [f"{template_names[0]}#{fragment}"] - return template_names - def get_form_kwargs(self): result = super().get_form_kwargs() if self.is_htmx: @@ -82,8 +85,8 @@ class OrganizationViewMixin(PermissionRequiredMixin): def get_permission_object(self): return self.organization + def has_organization_permission(self): + return self.request.user.has_perm("core.view_organization", self.organization) + def has_permission(self): - return ( - self.request.user.has_perm("core.view_organization", self.organization) - and super().has_permission() - ) + return self.has_organization_permission() and super().has_permission() diff --git a/src/servala/frontend/views/service.py b/src/servala/frontend/views/service.py index f5c33f0..faead0f 100644 --- a/src/servala/frontend/views/service.py +++ b/src/servala/frontend/views/service.py @@ -1,9 +1,9 @@ from django.utils.functional import cached_property from django.views.generic import DetailView, ListView -from servala.core.models import Service, ServiceOffering -from servala.frontend.forms.service import ServiceFilterForm -from servala.frontend.views.mixins import OrganizationViewMixin +from servala.core.models import Service, ServiceOffering, ServiceOfferingControlPlane +from servala.frontend.forms.service import ControlPlaneSelectForm, ServiceFilterForm +from servala.frontend.views.mixins import HtmxViewMixin, OrganizationViewMixin class ServiceListView(OrganizationViewMixin, ListView): @@ -44,13 +44,43 @@ class ServiceDetailView(OrganizationViewMixin, DetailView): ) -class ServiceOfferingDetailView(OrganizationViewMixin, DetailView): +class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView): template_name = "frontend/organizations/service_offering_detail.html" context_object_name = "offering" model = ServiceOffering permission_type = "view" + fragments = ("service-form",) + + def has_permission(self): + return self.has_organization_permission() def get_queryset(self): return ServiceOffering.objects.all().select_related( "service", "service__category", "provider" ) + + @cached_property + def planes(self): + return self.object.control_planes.all() + + @cached_property + def select_form(self): + data = None + if "control_plane" in self.request.GET: + data = self.request.GET + return ControlPlaneSelectForm(data=data, planes=self.planes) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["select_form"] = self.select_form + if "control_plane" in self.request.GET: + if self.select_form.is_valid(): + so_cp = ServiceOfferingControlPlane.objects.filter( + control_plane=self.select_form.cleaned_data["control_plane"], + service_offering=self.object, + ).first() + if not so_cp: + context["form_error"] = True + else: + context["service_form"] = so_cp.model_form_class() + return context