diff --git a/pyproject.toml b/pyproject.toml
index a26e2ef..ef119ef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,6 @@ dependencies = [
"django==5.2.4",
"django-allauth>=65.10.0",
"django-fernet-encrypted-fields>=0.3.0",
- "django-jsonform>=2.22.0",
"django-scopes>=2.0.0",
"django-storages[s3]>=1.14.6",
"django-template-partials>=24.4",
diff --git a/src/servala/core/admin.py b/src/servala/core/admin.py
index ed07574..740b7bf 100644
--- a/src/servala/core/admin.py
+++ b/src/servala/core/admin.py
@@ -1,6 +1,5 @@
from django.contrib import admin, messages
from django.utils.translation import gettext_lazy as _
-from django_jsonform.widgets import JSONFormWidget
from servala.core.forms import ControlPlaneAdminForm, ServiceDefinitionAdminForm
from servala.core.models import (
@@ -112,60 +111,12 @@ class ServiceAdmin(admin.ModelAdmin):
autocomplete_fields = ("category",)
prepopulated_fields = {"slug": ["name"]}
- def get_form(self, request, obj=None, **kwargs):
- form = super().get_form(request, obj, **kwargs)
- # JSON schema for external_links field
- external_links_schema = {
- "type": "array",
- "title": "External Links",
- "items": {
- "type": "object",
- "title": "Link",
- "properties": {
- "url": {"type": "string", "format": "uri", "title": "URL"},
- "title": {"type": "string", "title": "Title"},
- "featured": {
- "type": "boolean",
- "title": "Featured",
- "default": False,
- "description": "Featured links will be shown on the service list page",
- },
- },
- "required": ["url", "title"],
- },
- }
- form.base_fields["external_links"].widget = JSONFormWidget(
- schema=external_links_schema
- )
- return form
-
@admin.register(CloudProvider)
class CloudProviderAdmin(admin.ModelAdmin):
list_display = ("name",)
search_fields = ("name", "description")
- def get_form(self, request, obj=None, **kwargs):
- form = super().get_form(request, obj, **kwargs)
- # JSON schema for external_links field
- external_links_schema = {
- "type": "array",
- "title": "External Links",
- "items": {
- "type": "object",
- "title": "Link",
- "properties": {
- "url": {"type": "string", "format": "uri", "title": "URL"},
- "title": {"type": "string", "title": "Title"},
- },
- "required": ["url", "title"],
- },
- }
- form.base_fields["external_links"].widget = JSONFormWidget(
- schema=external_links_schema
- )
- return form
-
@admin.register(ControlPlane)
class ControlPlaneAdmin(admin.ModelAdmin):
diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py
index 997b0a2..e517c83 100644
--- a/src/servala/core/models/organization.py
+++ b/src/servala/core/models/organization.py
@@ -3,8 +3,8 @@ import urlman
from django.conf import settings
from django.db import models, transaction
from django.utils.functional import cached_property
-from django.utils.safestring import mark_safe
from django.utils.text import slugify
+from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py
index a06db4e..ba1871d 100644
--- a/src/servala/core/models/service.py
+++ b/src/servala/core/models/service.py
@@ -71,14 +71,9 @@ class Service(ServalaModelMixin, models.Model):
logo = models.ImageField(
upload_to="public/services", blank=True, null=True, verbose_name=_("Logo")
)
+ # TODO schema
external_links = models.JSONField(
- null=True,
- blank=True,
- verbose_name=_("External links"),
- help_text=(
- 'JSON array of link objects: {"url": "…", "title": "…", "featured": false}. '
- "Featured links will be shown on the service list page, all other links will only show on the service and offering detail pages."
- ),
+ null=True, blank=True, verbose_name=_("External links")
)
class Meta:
@@ -88,13 +83,6 @@ class Service(ServalaModelMixin, models.Model):
def __str__(self):
return self.name
- @property
- def featured_links(self):
- """Return external links marked as featured."""
- if not self.external_links:
- return []
- return [link for link in self.external_links if link.get("featured")]
-
def validate_dict(data, required_fields=None, allow_empty=True):
if not data:
@@ -275,10 +263,7 @@ class CloudProvider(ServalaModelMixin, models.Model):
verbose_name=_("Logo"),
)
external_links = models.JSONField(
- null=True,
- blank=True,
- verbose_name=_("External links"),
- help_text=('JSON array of link objects: {"url": "…", "title": "…"}. '),
+ null=True, blank=True, verbose_name=_("External links")
)
class Meta:
diff --git a/src/servala/frontend/templates/account/login.html b/src/servala/frontend/templates/account/login.html
index 58cd49b..233118b 100644
--- a/src/servala/frontend/templates/account/login.html
+++ b/src/servala/frontend/templates/account/login.html
@@ -1,22 +1,21 @@
{% extends "frontend/base.html" %}
{% load static i18n %}
{% load allauth account socialaccount %}
+
{% block html_title %}
{% translate "Sign in" %}
{% endblock html_title %}
+
{% block page_title %}
{% translate "Welcome to Servala" %}
{% endblock page_title %}
+
{% block card_header %}
-
+
@@ -98,6 +96,7 @@
+
- {% if offering.description %}
-
-
-
{{ offering.description|urlize }}
-
-
- {% endif %}
{% if not has_control_planes %}
{% translate "We currently cannot offer this service, sorry!" %}
{% else %}
@@ -56,24 +49,6 @@
{{ select_form }}
{% endif %}
- {% if service.external_links %}
-
-
-
{% translate "External Links" %}
-
-
-
- {% endif %}
{% partial service-form %}
diff --git a/src/servala/frontend/templates/frontend/organizations/services.html b/src/servala/frontend/templates/frontend/organizations/services.html
index 2227e86..022b6e5 100644
--- a/src/servala/frontend/templates/frontend/organizations/services.html
+++ b/src/servala/frontend/templates/frontend/organizations/services.html
@@ -16,10 +16,10 @@
-
+
{% for service in services %}
-
+
-
+
{% if service.description %}
{{ service.description|urlize }}
{% endif %}
-
diff --git a/src/servala/frontend/templates/frontend/profile.html b/src/servala/frontend/templates/frontend/profile.html
index b9c1799..dc12100 100644
--- a/src/servala/frontend/templates/frontend/profile.html
+++ b/src/servala/frontend/templates/frontend/profile.html
@@ -116,8 +116,7 @@
{% endblocktranslate %}
-
{% translate "VSHN Account Console" %}
diff --git a/src/servala/frontend/templates/includes/k8s_error.html b/src/servala/frontend/templates/includes/k8s_error.html
index 5707dcc..f97474d 100644
--- a/src/servala/frontend/templates/includes/k8s_error.html
+++ b/src/servala/frontend/templates/includes/k8s_error.html
@@ -1,12 +1,14 @@
{% if show_error %}
-
- {% if has_list %}
- {% if message %}{{ message }}{% endif %}
-
- {% for error in errors %}- {{ error }}
{% endfor %}
-
- {% else %}
- {{ message }}
- {% endif %}
-
+
+ {% if has_list %}
+ {% if message %}{{ message }}{% endif %}
+
+ {% for error in errors %}
+ - {{ error }}
+ {% endfor %}
+
+ {% else %}
+ {{ message }}
+ {% endif %}
+
{% endif %}
diff --git a/src/servala/frontend/templates/includes/message.html b/src/servala/frontend/templates/includes/message.html
index 278c009..d5debc9 100644
--- a/src/servala/frontend/templates/includes/message.html
+++ b/src/servala/frontend/templates/includes/message.html
@@ -1,29 +1,28 @@
-
+
{{ message }}
+
+document.addEventListener('DOMContentLoaded', function() {
+ const alert = document.getElementById('auto-dismiss-alert-{{ forloop.counter0|default:'0' }}');
+ if (alert && !alert.classList.contains('alert-danger')) {
+ setTimeout(function() {
+ let opacity = 1;
+ const fadeOutInterval = setInterval(function() {
+ if (opacity > 0.05) {
+ opacity -= 0.05;
+ alert.style.opacity = opacity;
+ } else {
+ clearInterval(fadeOutInterval);
+ const bsAlert = new bootstrap.Alert(alert);
+ bsAlert.close();
+ }
+ }, 25);
+ }, 5000);
+ }
+});
+
\ No newline at end of file
diff --git a/src/servala/frontend/templatetags/error_filters.py b/src/servala/frontend/templatetags/error_filters.py
index 410d49a..18c4c4d 100644
--- a/src/servala/frontend/templatetags/error_filters.py
+++ b/src/servala/frontend/templatetags/error_filters.py
@@ -3,7 +3,6 @@ Template filters for safe error formatting.
"""
import html
-
from django import template
from django.utils.safestring import mark_safe
diff --git a/src/servala/frontend/views/support.py b/src/servala/frontend/views/support.py
index 6f4c4aa..a5de757 100644
--- a/src/servala/frontend/views/support.py
+++ b/src/servala/frontend/views/support.py
@@ -51,8 +51,7 @@ class SupportView(OrganizationViewMixin, FormView):
self.request,
mark_safe(
_(
- "There was an error submitting your support request. "
- 'Please try again or contact us directly at
servala-support@vshn.ch.'
+ 'There was an error submitting your support request. Please try again or contact us directly at
servala-support@vshn.ch.'
)
),
)
diff --git a/src/servala/settings.py b/src/servala/settings.py
index 63f7895..ed28324 100644
--- a/src/servala/settings.py
+++ b/src/servala/settings.py
@@ -150,7 +150,6 @@ INSTALLED_APPS = [
# The frontend app is loaded early in order to supersede some allauth views/behaviour
"servala.frontend",
"django.forms",
- "django_jsonform",
"template_partials",
"rules.apps.AutodiscoverRulesConfig",
"allauth",
diff --git a/src/servala/static/css/servala.css b/src/servala/static/css/servala.css
index ee4c080..db9052a 100644
--- a/src/servala/static/css/servala.css
+++ b/src/servala/static/css/servala.css
@@ -174,21 +174,3 @@ a.btn-keycloak {
margin-top: -16px;
padding-right: 28px;
}
-
-/* Service cards equal height styling */
-.service-cards-container .card {
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.service-cards-container .card-body {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
-}
-
-.service-cards-container .card-footer {
- margin-top: auto;
-}
diff --git a/uv.lock b/uv.lock
index df4202a..68b4f94 100644
--- a/uv.lock
+++ b/uv.lock
@@ -338,18 +338,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/75/8a/2c5d88cd540d83ceaa1cb3191ed35dfed0caacc6fe2ff5fe74c9ecc7776f/django_fernet_encrypted_fields-0.3.0-py3-none-any.whl", hash = "sha256:a17cca5bf3638ee44674e64f30792d5960b1d4d4b291ec478c27515fc4860612", size = 5400, upload-time = "2025-02-21T02:58:40.832Z" },
]
-[[package]]
-name = "django-jsonform"
-version = "2.23.2"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "django" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/9b/a8/83c57acbc153b86615be279cee5a194ce1163b578f29a9f6d658f267785e/django_jsonform-2.23.2.tar.gz", hash = "sha256:6fa2ba7c082be51d738e6c66e35075a3cb9ebc2f941e3a477c988900a7fe3269", size = 108182, upload-time = "2025-01-29T22:31:34.093Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/3a/8e/8766f4bc535917ccbc6b3dcb57caf82210a6bacd613c3d9dbaec81018935/django_jsonform-2.23.2-py3-none-any.whl", hash = "sha256:1b7f94c5a2bd22c844e035a9940a9c8586f7b8fc3346ef2a6a13ba608e0059d7", size = 109148, upload-time = "2025-01-29T22:31:31.186Z" },
-]
-
[[package]]
name = "django-scopes"
version = "2.0.0"
@@ -1062,7 +1050,6 @@ dependencies = [
{ name = "django" },
{ name = "django-allauth" },
{ name = "django-fernet-encrypted-fields" },
- { name = "django-jsonform" },
{ name = "django-scopes" },
{ name = "django-storages", extra = ["s3"] },
{ name = "django-template-partials" },
@@ -1099,7 +1086,6 @@ requires-dist = [
{ name = "django", specifier = "==5.2.4" },
{ name = "django-allauth", specifier = ">=65.10.0" },
{ name = "django-fernet-encrypted-fields", specifier = ">=0.3.0" },
- { name = "django-jsonform", specifier = ">=2.22.0" },
{ name = "django-scopes", specifier = ">=2.0.0" },
{ name = "django-storages", extras = ["s3"], specifier = ">=1.14.6" },
{ name = "django-template-partials", specifier = ">=24.4" },