Show service ordering form
All checks were successful
Tests / test (push) Successful in 24s

This commit is contained in:
Tobias Kunze 2025-03-25 18:30:31 +01:00
parent ee8fba07ef
commit a1a150b85e
4 changed files with 87 additions and 35 deletions

View file

@ -1,6 +1,6 @@
from django import forms from django import forms
from servala.core.models import CloudProvider, ServiceCategory from servala.core.models import CloudProvider, ControlPlane, ServiceCategory
class ServiceFilterForm(forms.Form): class ServiceFilterForm(forms.Form):
@ -20,3 +20,11 @@ class ServiceFilterForm(forms.Form):
offerings__control_planes__cloud_provider=cloud_provider offerings__control_planes__cloud_provider=cloud_provider
) )
return queryset 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

View file

@ -1,11 +1,23 @@
{% extends "frontend/base.html" %} {% extends "frontend/base.html" %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load partials %}
{% block html_title %} {% block html_title %}
{% block page_title %} {% block page_title %}
{{ offering }} {{ offering }}
{% endblock page_title %} {% endblock page_title %}
{% endblock html_title %} {% endblock html_title %}
{% partialdef service-form %}
{% if service_form %}
{% if form_error %}
<div class="alert alert-danger">
{% translate "Oops! Something went wrong with the service form generation. Please try again later." %}
</div>
{% else %}
{% include "includes/form.html" with form=service_form %}
{% endif %}
{% endif %}
{% endpartialdef %}
{% block content %} {% block content %}
<section class="section"> <section class="section">
<div class="card"> <div class="card">
@ -23,18 +35,17 @@
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> {% if offering.control_planes.all.count == 0 %}
{% if offering.control_planes.all.count > 1 %} <p>{% translate "We currently cannot offer this service, sorry!" %}</p>
<p>{% translate "Please choose your zone." %}</p>
{% else %} {% else %}
<p> <form hx-trigger="change"
{% blocktranslate trimmed with zone=offering.control_planes.all.first.name %} hx-get="{{ request.path }}?fragment=service-form"
Your zone will be <strong>{{ zone }}</strong>. hx-target="#service-form">
{% endblocktranslate %} {{ select_form }}
</p> </form>
<div id="service-form">{% partial service-form %}</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
</section> </section>
{% endblock content %} {% endblock content %}

View file

@ -5,13 +5,30 @@ from rules.contrib.views import AutoPermissionRequiredMixin, PermissionRequiredM
from servala.core.models import Organization from servala.core.models import Organization
class HtmxUpdateView(AutoPermissionRequiredMixin, UpdateView): class HtmxViewMixin:
fragments = [] fragments = []
@cached_property @cached_property
def is_htmx(self): def is_htmx(self):
return self.request.headers.get("HX-Request") 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 @property
def permission_type(self): def permission_type(self):
if self.request.method == "POST" or getattr( if self.request.method == "POST" or getattr(
@ -31,20 +48,6 @@ class HtmxUpdateView(AutoPermissionRequiredMixin, UpdateView):
result["has_change_permission"] = self.has_change_permission() result["has_change_permission"] = self.has_change_permission()
return result 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): def get_form_kwargs(self):
result = super().get_form_kwargs() result = super().get_form_kwargs()
if self.is_htmx: if self.is_htmx:
@ -82,8 +85,8 @@ class OrganizationViewMixin(PermissionRequiredMixin):
def get_permission_object(self): def get_permission_object(self):
return self.organization return self.organization
def has_organization_permission(self):
return self.request.user.has_perm("core.view_organization", self.organization)
def has_permission(self): def has_permission(self):
return ( return self.has_organization_permission() and super().has_permission()
self.request.user.has_perm("core.view_organization", self.organization)
and super().has_permission()
)

View file

@ -1,9 +1,9 @@
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from servala.core.models import Service, ServiceOffering from servala.core.models import Service, ServiceOffering, ServiceOfferingControlPlane
from servala.frontend.forms.service import ServiceFilterForm from servala.frontend.forms.service import ControlPlaneSelectForm, ServiceFilterForm
from servala.frontend.views.mixins import OrganizationViewMixin from servala.frontend.views.mixins import HtmxViewMixin, OrganizationViewMixin
class ServiceListView(OrganizationViewMixin, ListView): 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" template_name = "frontend/organizations/service_offering_detail.html"
context_object_name = "offering" context_object_name = "offering"
model = ServiceOffering model = ServiceOffering
permission_type = "view" permission_type = "view"
fragments = ("service-form",)
def has_permission(self):
return self.has_organization_permission()
def get_queryset(self): def get_queryset(self):
return ServiceOffering.objects.all().select_related( return ServiceOffering.objects.all().select_related(
"service", "service__category", "provider" "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