From cd65a149e5b0145320f58a01979894d67ccce03c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 3 Mar 2025 16:05:14 +0100 Subject: [PATCH 01/10] move robots and sitemap into prod urls --- hub/urls.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hub/urls.py b/hub/urls.py index fd454d1..3a3e87f 100644 --- a/hub/urls.py +++ b/hub/urls.py @@ -42,6 +42,13 @@ urlpatterns = [ path("admin/", admin.site.urls), path("", include("hub.services.urls")), path("broker/", include("hub.broker.urls", namespace="broker")), + path( + "sitemap.xml", + sitemap, + {"sitemaps": sitemaps}, + name="django.contrib.sitemaps.views.sitemap", + ), + path("robots.txt", robots_txt, name="robots_txt"), ] if settings.DEBUG: urlpatterns += [ @@ -50,12 +57,5 @@ if settings.DEBUG: path("test-400/", lambda request: render(request, "400.html"), name="test_400"), path("test-404/", lambda request: render(request, "404.html"), name="test_404"), path("test-500/", lambda request: render(request, "500.html"), name="test_500"), - path( - "sitemap.xml", - sitemap, - {"sitemaps": sitemaps}, - name="django.contrib.sitemaps.views.sitemap", - ), - path("robots.txt", robots_txt, name="robots_txt"), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From be473f903e2b5782fc282ccacb3742629fa944a1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 3 Mar 2025 16:07:46 +0100 Subject: [PATCH 02/10] untested and unused json-ld code --- hub/services/templatetags/json_ld_tags.py | 228 ++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 hub/services/templatetags/json_ld_tags.py diff --git a/hub/services/templatetags/json_ld_tags.py b/hub/services/templatetags/json_ld_tags.py new file mode 100644 index 0000000..b1298de --- /dev/null +++ b/hub/services/templatetags/json_ld_tags.py @@ -0,0 +1,228 @@ +# hub/services/templatetags/json_ld_tags.py +from django import template +from django.urls import resolve +from django.utils.safestring import mark_safe +import json + +register = template.Library() + + +@register.simple_tag(takes_context=True) +def json_ld_structured_data(context): + """ + Generates appropriate JSON-LD structured data based on the current page. + """ + request = context["request"] + current_url = request.path + resolved_view = resolve(current_url) + view_name = resolved_view.url_name + + # Base URL for building absolute URLs + base_url = request.build_absolute_uri("/").rstrip("/") + + # Default organization data (for Servala) + organization_data = { + "@context": "https://schema.org", + "@type": "Organization", + "name": "Servala", + "url": base_url, + "logo": f"{base_url}/static/img/header-logo.png", + "contactPoint": { + "@type": "ContactPoint", + "telephone": "+41 44 545 53 00", + "email": "hi@serva.la", + "contactType": "Customer Support", + }, + "address": { + "@type": "PostalAddress", + "streetAddress": "Neugasse 10", + "addressLocality": "Zurich", + "postalCode": "8005", + "addressCountry": "CH", + }, + } + + # Handle different page types + if view_name == "homepage": + data = { + "@context": "https://schema.org", + "@type": "WebSite", + "name": "Servala - The Cloud Native Service Hub", + "url": base_url, + "description": "Servala connects businesses, developers, and cloud service providers on one unique hub with secure, scalable, and easy-to-use cloud-native services.", + "potentialAction": { + "@type": "SearchAction", + "target": f"{base_url}/services/?search={{search_term_string}}", + "query-input": "required name=search_term_string", + }, + } + + elif view_name == "service_list": + data = { + "@context": "https://schema.org", + "@type": "CollectionPage", + "name": "Cloud Services - Servala", + "url": f"{base_url}/services/", + "description": "Explore all available cloud services on Servala, with new services added regularly.", + "isPartOf": {"@type": "WebSite", "name": "Servala", "url": base_url}, + } + + elif view_name == "provider_list": + data = { + "@context": "https://schema.org", + "@type": "CollectionPage", + "name": "Cloud Providers - Servala", + "url": f"{base_url}/providers/", + "description": "Discover cloud providers on Servala offering reliable infrastructure and innovative cloud computing solutions.", + "isPartOf": {"@type": "WebSite", "name": "Servala", "url": base_url}, + } + + elif view_name == "partner_list": + data = { + "@context": "https://schema.org", + "@type": "CollectionPage", + "name": "Consulting Partners - Servala", + "url": f"{base_url}/partners/", + "description": "Browse our network of expert consulting partners on Servala who can help implement and optimize cloud services.", + "isPartOf": {"@type": "WebSite", "name": "Servala", "url": base_url}, + } + + elif view_name == "service_detail" and "service" in context: + service = context["service"] + service_url = request.build_absolute_uri() + + data = { + "@context": "https://schema.org", + "@type": "Product", + "name": service.name, + "description": service.description, + "url": service_url, + "category": "Cloud Service", + } + + # Add image if available + if hasattr(service, "logo") and service.logo: + data["image"] = request.build_absolute_uri(service.logo.url) + + # Add offerings if available + if hasattr(service, "offerings") and service.offerings.exists(): + data["offers"] = { + "@type": "AggregateOffer", + "availability": "https://schema.org/InStock", + "offerCount": service.offerings.count(), + } + + elif view_name == "provider_detail" and "provider" in context: + provider = context["provider"] + provider_url = request.build_absolute_uri() + + data = { + "@context": "https://schema.org", + "@type": "Organization", + "name": provider.name, + "description": provider.description, + "url": provider_url, + } + + # Add image if available + if hasattr(provider, "logo") and provider.logo: + data["logo"] = request.build_absolute_uri(provider.logo.url) + + # Add contact information if available + contact_point = {"@type": "ContactPoint", "contactType": "Customer Support"} + + if hasattr(provider, "website") and provider.website: + contact_point["url"] = provider.website + if hasattr(provider, "email") and provider.email: + contact_point["email"] = provider.email + if hasattr(provider, "phone") and provider.phone: + contact_point["telephone"] = provider.phone + + if len(contact_point) > 2: # If we have more than the @type and contactType + data["contactPoint"] = contact_point + + # Add address if available + if hasattr(provider, "address") and provider.address: + data["address"] = { + "@type": "PostalAddress", + "addressCountry": "CH", # Default to Switzerland + } + + elif view_name == "partner_detail" and "partner" in context: + partner = context["partner"] + partner_url = request.build_absolute_uri() + + data = { + "@context": "https://schema.org", + "@type": "Organization", + "name": partner.name, + "description": partner.description, + "url": partner_url, + } + + # Add image if available + if hasattr(partner, "logo") and partner.logo: + data["logo"] = request.build_absolute_uri(partner.logo.url) + + # Add contact information if available + contact_point = {"@type": "ContactPoint", "contactType": "Customer Support"} + + if hasattr(partner, "website") and partner.website: + contact_point["url"] = partner.website + if hasattr(partner, "email") and partner.email: + contact_point["email"] = partner.email + if hasattr(partner, "phone") and partner.phone: + contact_point["telephone"] = partner.phone + + if len(contact_point) > 2: # If we have more than the @type and contactType + data["contactPoint"] = contact_point + + # Add address if available + if hasattr(partner, "address") and partner.address: + data["address"] = { + "@type": "PostalAddress", + "addressCountry": "CH", # Default to Switzerland + } + + elif view_name == "offering_detail" and "offering" in context: + offering = context["offering"] + offering_url = request.build_absolute_uri() + + data = { + "@context": "https://schema.org", + "@type": "Product", + "name": f"{offering.service.name} on {offering.cloud_provider.name}", + "description": offering.description or offering.service.description, + "url": offering_url, + "category": "Cloud Service", + } + + # Add brand (service) + data["brand"] = {"@type": "Brand", "name": offering.service.name} + + # Add image if available + if hasattr(offering.service, "logo") and offering.service.logo: + data["image"] = request.build_absolute_uri(offering.service.logo.url) + + # Add offers if available + if hasattr(offering, "plans") and offering.plans.exists(): + data["offers"] = { + "@type": "AggregateOffer", + "availability": "https://schema.org/InStock", + "offerCount": offering.plans.count(), + "seller": { + "@type": "Organization", + "name": offering.cloud_provider.name, + "url": request.build_absolute_uri( + offering.cloud_provider.get_absolute_url() + ), + }, + } + + else: + # Default to organization data if no specific page type matches + data = organization_data + + # Return the JSON-LD as a script tag + json_ld = json.dumps(data, indent=2) + return mark_safe(f'') From 7f7694887584076ee59fe69b9d5508e238bdcdb8 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 3 Mar 2025 16:18:15 +0100 Subject: [PATCH 03/10] dont break on 404 --- hub/services/templatetags/social_meta_tags.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/hub/services/templatetags/social_meta_tags.py b/hub/services/templatetags/social_meta_tags.py index 4cfe92b..d8eb107 100644 --- a/hub/services/templatetags/social_meta_tags.py +++ b/hub/services/templatetags/social_meta_tags.py @@ -1,5 +1,5 @@ from django import template -from django.urls import resolve +from django.urls import resolve, Resolver404 from django.utils.safestring import mark_safe register = template.Library() @@ -12,8 +12,12 @@ def social_meta_tags(context): """ request = context["request"] current_url = request.path - resolved_view = resolve(current_url) - view_name = resolved_view.url_name + + try: + resolved_view = resolve(current_url) + view_name = resolved_view.view_name + except Resolver404: + view_name = None # Default values (used for listing pages) title = context.get("self.title", "Servala") From b11fa2c21b6e7f444802076878cfa4af2467ba90 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 3 Mar 2025 17:28:38 +0100 Subject: [PATCH 04/10] only show relevant filters on service listing --- .../templates/services/service_list.html | 10 +- hub/services/views/services.py | 143 +++++++++++++----- 2 files changed, 109 insertions(+), 44 deletions(-) diff --git a/hub/services/templates/services/service_list.html b/hub/services/templates/services/service_list.html index 1fbc7f0..fa56146 100644 --- a/hub/services/templates/services/service_list.html +++ b/hub/services/templates/services/service_list.html @@ -86,12 +86,12 @@
- {% for partner in consulting_partners %} + {% for partner in available_consulting_partners %} @@ -127,7 +127,7 @@
- {% for service in services %} + {% for service in available_services %} @@ -94,23 +94,6 @@
- -
-
- -
-
- -
-
-
Clear diff --git a/hub/services/views/partners.py b/hub/services/views/partners.py index 1ed9cca..becad07 100644 --- a/hub/services/views/partners.py +++ b/hub/services/views/partners.py @@ -4,35 +4,70 @@ from hub.services.models import ConsultingPartner, CloudProvider, Service def partner_list(request): - partners = ( - ConsultingPartner.objects.filter(disable_listing=False) - .order_by("name") - .prefetch_related("services", "cloud_providers") - ) + # Get basic filter parameters + search_query = request.GET.get("search", "") + service_id = request.GET.get("service", "") + cloud_provider_id = request.GET.get("cloud_provider", "") - services = Service.objects.all().order_by("name") + # Start with all active partners + partners = ConsultingPartner.objects.filter(disable_listing=False).order_by("name") - # Handle cloud provider filter - if request.GET.get("cloud_provider"): - provider_id = request.GET.get("cloud_provider") - partners = partners.filter(cloud_providers__id=provider_id) - - # Handle service filter - if request.GET.get("service"): - service_id = request.GET.get("service") - partners = partners.filter(services__id=service_id) - - # Handle search - if request.GET.get("search"): - query = request.GET.get("search") + # Apply filters based on request parameters + if search_query: partners = partners.filter( - Q(name__icontains=query) | Q(description__icontains=query) + Q(name__icontains=search_query) | Q(description__icontains=search_query) ) + if service_id: + partners = partners.filter(services__id=service_id) + + if cloud_provider_id: + partners = partners.filter(cloud_providers__id=cloud_provider_id) + + # Get available services from filtered partners + available_service_ids = partners.values_list("services__id", flat=True).distinct() + available_services = Service.objects.filter( + id__in=available_service_ids, disable_listing=False + ).order_by("name") + + # Get available cloud providers from filtered partners + available_cloud_provider_ids = partners.values_list( + "cloud_providers__id", flat=True + ).distinct() + available_cloud_providers = CloudProvider.objects.filter( + id__in=available_cloud_provider_ids, disable_listing=False + ).order_by("name") + + # For the current selection, we need to make sure we include the selected items + # even if they don't match other filters + if service_id: + try: + selected_service_id = int(service_id) + if selected_service_id not in available_service_ids: + selected_service = Service.objects.get(id=selected_service_id) + available_services = list(available_services) + available_services.append(selected_service) + except (ValueError, Service.DoesNotExist): + pass + + if cloud_provider_id: + try: + cp_id = int(cloud_provider_id) + if cp_id not in available_cloud_provider_ids: + selected_provider = CloudProvider.objects.get(id=cp_id) + available_cloud_providers = list(available_cloud_providers) + available_cloud_providers.append(selected_provider) + except (ValueError, CloudProvider.DoesNotExist): + pass + context = { - "partners": partners, - "services": services, - "cloud_providers": CloudProvider.objects.all(), + "partners": partners.prefetch_related("services", "cloud_providers"), + "services": Service.objects.filter(disable_listing=False).order_by("name"), + "cloud_providers": CloudProvider.objects.filter(disable_listing=False).order_by( + "name" + ), + "available_services": available_services, + "available_cloud_providers": available_cloud_providers, } return render(request, "services/partner_list.html", context) From 5157d7c781780684ac8295265580cba0b92706ab Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 3 Mar 2025 17:45:28 +0100 Subject: [PATCH 06/10] proper service ordering on list view --- hub/services/views/services.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/hub/services/views/services.py b/hub/services/views/services.py index 02ff2cd..adb7e22 100644 --- a/hub/services/views/services.py +++ b/hub/services/views/services.py @@ -35,6 +35,13 @@ def service_list(request): # Filter through offerings instead of direct cloud_providers relation services = services.filter(offerings__cloud_provider__id=cloud_provider_id) + # Order services: featured first, then regular services, then coming soon + services = services.order_by( + "-is_featured", # Featured first (True before False) + "is_coming_soon", # Coming soon last (False before True) + "name", # Alphabetically within each group + ) + # Get all available categories from filtered services available_category_ids = services.values_list( "categories__id", flat=True @@ -109,15 +116,9 @@ def service_list(request): context = { "services": services, - "categories": Category.objects.filter( - parent=None - ), # Keep original for reference if needed - "consulting_partners": ConsultingPartner.objects.filter( - disable_listing=False - ), # Keep original but filter out disabled - "cloud_providers": CloudProvider.objects.filter( - disable_listing=False - ), # Keep original but filter out disabled + "categories": Category.objects.filter(parent=None), + "consulting_partners": ConsultingPartner.objects.filter(disable_listing=False), + "cloud_providers": CloudProvider.objects.filter(disable_listing=False), "available_categories": available_categories, "available_consulting_partners": available_consulting_partners, "available_cloud_providers": available_cloud_providers, From 928bd0818e84ba0ffc135e7ed11f1cf2a1c7d815 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 3 Mar 2025 17:54:47 +0100 Subject: [PATCH 07/10] add service tagline --- .../migrations/0017_service_tagline.py | 18 ++++++++++++++++++ hub/services/models.py | 1 + .../templates/services/service_detail.html | 7 +++++++ .../templates/services/service_list.html | 5 +++++ 4 files changed, 31 insertions(+) create mode 100644 hub/services/migrations/0017_service_tagline.py diff --git a/hub/services/migrations/0017_service_tagline.py b/hub/services/migrations/0017_service_tagline.py new file mode 100644 index 0000000..e207609 --- /dev/null +++ b/hub/services/migrations/0017_service_tagline.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2025-03-03 16:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("services", "0016_cloudprovider_disable_listing_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="service", + name="tagline", + field=models.TextField(blank=True, max_length=500, null=True), + ), + ] diff --git a/hub/services/models.py b/hub/services/models.py index 7035fd3..9b126bd 100644 --- a/hub/services/models.py +++ b/hub/services/models.py @@ -116,6 +116,7 @@ class Service(models.Model): name = models.CharField(max_length=200) slug = models.SlugField(max_length=250, unique=True) description = ProseEditorField() + tagline = models.TextField(max_length=500, blank=True, null=True) logo = models.ImageField( upload_to="service_logos/", validators=[validate_image_size], diff --git a/hub/services/templates/services/service_detail.html b/hub/services/templates/services/service_detail.html index a68433a..a0e0552 100644 --- a/hub/services/templates/services/service_detail.html +++ b/hub/services/templates/services/service_detail.html @@ -106,6 +106,13 @@ {% endfor %}
+ {% if service.tagline %} +
+

+ "{{ service.tagline }}" +

+
+ {% endif %} diff --git a/hub/services/templates/services/service_list.html b/hub/services/templates/services/service_list.html index fa56146..25a0bf6 100644 --- a/hub/services/templates/services/service_list.html +++ b/hub/services/templates/services/service_list.html @@ -179,6 +179,11 @@ {{ category.full_path }} {% endfor %}

+ {% if service.tagline %} +

+ "{{ service.tagline }}" +

+ {% endif %}

{{ service.description|safe|truncatewords:30 }}

From aa4ec33c93c7e16d18d13bcf366b3075f6528885 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 4 Mar 2025 16:49:30 +0100 Subject: [PATCH 08/10] move plan choice into form --- .../services/embedded_contact_form.html | 11 +++++++++++ .../templates/services/offering_detail.html | 17 ++++++++++++----- hub/services/templatetags/contact_tags.py | 17 ++++++++++++++++- hub/services/views/leads.py | 18 +++++++++++++++++- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/hub/services/templates/services/embedded_contact_form.html b/hub/services/templates/services/embedded_contact_form.html index d0f719d..5d22f21 100644 --- a/hub/services/templates/services/embedded_contact_form.html +++ b/hub/services/templates/services/embedded_contact_form.html @@ -54,6 +54,17 @@ {% endif %}
+ {% if choices %} +
+ + +
+ {% endif %} +
{{ form.message|addclass:"form-control" }} diff --git a/hub/services/templates/services/offering_detail.html b/hub/services/templates/services/offering_detail.html index 9fbd3e0..daae63c 100644 --- a/hub/services/templates/services/offering_detail.html +++ b/hub/services/templates/services/offering_detail.html @@ -149,11 +149,6 @@
{% endif %} -
-

Order This Plan

- {% load contact_tags %} - {% embedded_contact_form source="Plan Order" service=offering.service offering_id=offering.id plan_id=plan.id %} -
{% empty %} @@ -167,6 +162,18 @@ {% endfor %} + + {% if offering.plans.exists %} +
+

I'm interested in a plan

+
+
+ {% load contact_tags %} + {% embedded_contact_form source="Plan Order" service=offering.service offering_id=offering.id choices=offering.plans.all choice_label="Select a Plan" %} +
+
+
+ {% endif %} diff --git a/hub/services/templatetags/contact_tags.py b/hub/services/templatetags/contact_tags.py index 748a2cb..6d7791b 100644 --- a/hub/services/templatetags/contact_tags.py +++ b/hub/services/templatetags/contact_tags.py @@ -8,7 +8,14 @@ register = template.Library() @register.inclusion_tag("services/embedded_contact_form.html", takes_context=True) def embedded_contact_form( - context, source=None, details=None, service=None, offering_id=None, plan_id=None + context, + source=None, + details=None, + service=None, + offering_id=None, + plan_id=None, + choices=None, + choice_label=None, ): """ Renders an embedded contact form with optional service context information. @@ -17,6 +24,7 @@ def embedded_contact_form( {% load contact_tags %} {% embedded_contact_form source="Partner Page" details="ACME Corp" %} {% embedded_contact_form service=service offering_id=offering.id plan_id=plan.id %} + {% embedded_contact_form service=service offering_id=offering.id choices=offering.plans.all choice_label="Select a Plan" %} """ request = context["request"] form = LeadForm() @@ -25,6 +33,11 @@ def embedded_contact_form( offering_obj = None plan_obj = None + # Process choices if they're QuerySet objects (like plans) + processed_choices = None + if choices: + processed_choices = [(str(choice.id), choice.name) for choice in choices] + # Resolve service/offering/plan objects if IDs provided if service and isinstance(service, str): try: @@ -56,4 +69,6 @@ def embedded_contact_form( "offering": offering_obj, "plan": plan_obj, "request": request, + "choices": processed_choices, + "choice_label": choice_label, } diff --git a/hub/services/views/leads.py b/hub/services/views/leads.py index 72108b4..00ce109 100644 --- a/hub/services/views/leads.py +++ b/hub/services/views/leads.py @@ -26,7 +26,7 @@ def contact_form(request): lead = Lead( name=form.cleaned_data["name"], email=form.cleaned_data["email"], - message=form.cleaned_data["message"], + message=form.cleaned_data["message"] or "", company=form.cleaned_data["company"], phone=form.cleaned_data["phone"], ) @@ -87,6 +87,22 @@ def contact_form(request): if plan_name: service_info.append(f"Plan: {plan_name}") + # Handle selected choice if present + selected_choice = request.POST.get("selected_choice", "") + if selected_choice: + try: + choice_id, choice_name = selected_choice.split("|", 1) + # Add selected choice to message + service_info.append(f"Selected Plan: {choice_name}") + + # Try to set the plan based on the choice_id + try: + lead.plan = Plan.objects.get(id=choice_id) + except Plan.DoesNotExist: + pass + except ValueError: + pass + if service_info: context_info.append("Service Information: " + ", ".join(service_info)) From d81e76e8ab5cb4d44c67d041ce0f80764abfbf2e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 4 Mar 2025 17:01:03 +0100 Subject: [PATCH 09/10] basic spam protection --- .../services/embedded_contact_form.html | 7 ++++++ hub/services/templatetags/contact_tags.py | 5 +++++ hub/services/views/leads.py | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/hub/services/templates/services/embedded_contact_form.html b/hub/services/templates/services/embedded_contact_form.html index 5d22f21..660be51 100644 --- a/hub/services/templates/services/embedded_contact_form.html +++ b/hub/services/templates/services/embedded_contact_form.html @@ -8,6 +8,13 @@ {% if details %} {% endif %} + + +
+ + +
+ {% if service %} diff --git a/hub/services/templatetags/contact_tags.py b/hub/services/templatetags/contact_tags.py index 6d7791b..7d071bf 100644 --- a/hub/services/templatetags/contact_tags.py +++ b/hub/services/templatetags/contact_tags.py @@ -2,6 +2,7 @@ from django import template from hub.services.forms import LeadForm from hub.services.models import Service, ServiceOffering, Plan +import time register = template.Library() @@ -29,6 +30,9 @@ def embedded_contact_form( request = context["request"] form = LeadForm() + # Add timestamp for spam protection + timestamp = int(time.time()) + service_obj = None offering_obj = None plan_obj = None @@ -71,4 +75,5 @@ def embedded_contact_form( "request": request, "choices": processed_choices, "choice_label": choice_label, + "timestamp": timestamp, } diff --git a/hub/services/views/leads.py b/hub/services/views/leads.py index 00ce109..2e3ccda 100644 --- a/hub/services/views/leads.py +++ b/hub/services/views/leads.py @@ -1,4 +1,5 @@ import logging +import time from django.shortcuts import render, redirect from django.contrib import messages @@ -18,6 +19,27 @@ def thank_you(request): def contact_form(request): if request.method == "POST": + # Spam protection checks + honeypot_value = request.POST.get("website", "") + timestamp_value = request.POST.get("form_timestamp", "0") + current_time = int(time.time()) + + # Check 1: Honeypot field should be empty + if honeypot_value: + # Bot detected - silently redirect + return redirect("services:homepage") + + # Check 2: Form shouldn't be submitted too quickly (< 3 seconds) + try: + form_time = int(timestamp_value) + if current_time - form_time < 3: + # Too quick submission - likely a bot + return redirect("services:homepage") + except ValueError: + # Invalid timestamp - likely a bot + return redirect("services:homepage") + + # Continue with normal form processing form = LeadForm(request.POST) if form.is_valid(): from hub.services.models import Lead, Service, ServiceOffering, Plan From ac75e964cbafd0b301f8d2b38b87ed1f9d0a4308 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 4 Mar 2025 21:21:42 +0100 Subject: [PATCH 10/10] emphasize the openness --- hub/services/templates/services/homepage.html | 6 +++--- hub/services/templatetags/json_ld_tags.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hub/services/templates/services/homepage.html b/hub/services/templates/services/homepage.html index 91d9b46..258ccbc 100644 --- a/hub/services/templates/services/homepage.html +++ b/hub/services/templates/services/homepage.html @@ -1,7 +1,7 @@ {% extends 'services/base.html' %} {% load static %} -{% block title %}The Cloud Native Services Hub{% endblock %} +{% block title %}Open Cloud Native Services Hub{% endblock %} {% block content %}
@@ -9,7 +9,7 @@
-

Servala - The Cloud Native Service Hub

+

Servala - Open Cloud Native Service Hub

Unlock the Power of Cloud Native Applications.

Servala connects businesses, developers, and cloud service providers on one unique hub with secure, scalable, and easy-to-use cloud-native services.

@@ -169,7 +169,7 @@
-

Servala - The Cloud Native Service Hub

+

Servala - Open Cloud Native Service Hub

Servala connects businesses, developers, and cloud service providers on one unique hub with secure, scalable, and easy-to-use cloud-native services.

Discover:

diff --git a/hub/services/templatetags/json_ld_tags.py b/hub/services/templatetags/json_ld_tags.py index b1298de..977452e 100644 --- a/hub/services/templatetags/json_ld_tags.py +++ b/hub/services/templatetags/json_ld_tags.py @@ -47,7 +47,7 @@ def json_ld_structured_data(context): data = { "@context": "https://schema.org", "@type": "WebSite", - "name": "Servala - The Cloud Native Service Hub", + "name": "Servala - Open Cloud Native Service Hub", "url": base_url, "description": "Servala connects businesses, developers, and cloud service providers on one unique hub with secure, scalable, and easy-to-use cloud-native services.", "potentialAction": {