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 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

View file

@ -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 %}
<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 %}
<section class="section">
<div class="card">
@ -23,17 +35,16 @@
</div>
</div>
<div class="card-body">
<div class="row">
{% if offering.control_planes.all.count > 1 %}
<p>{% translate "Please choose your zone." %}</p>
{% else %}
<p>
{% blocktranslate trimmed with zone=offering.control_planes.all.first.name %}
Your zone will be <strong>{{ zone }}</strong>.
{% endblocktranslate %}
</p>
{% endif %}
</div>
{% if offering.control_planes.all.count == 0 %}
<p>{% translate "We currently cannot offer this service, sorry!" %}</p>
{% else %}
<form hx-trigger="change"
hx-get="{{ request.path }}?fragment=service-form"
hx-target="#service-form">
{{ select_form }}
</form>
<div id="service-form">{% partial service-form %}</div>
{% endif %}
</div>
</div>
</section>

View file

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

View file

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