diff --git a/hub/services/admin.py b/hub/services/admin.py index 8afe9d1..65a18c4 100644 --- a/hub/services/admin.py +++ b/hub/services/admin.py @@ -1,6 +1,13 @@ from django.contrib import admin from django.utils.html import format_html -from .models import CloudProvider, Country, ServiceLevel, Service, Category +from .models import ( + CloudProvider, + ConsultingPartner, + Country, + ServiceLevel, + Service, + Category, +) @admin.register(Category) @@ -42,17 +49,23 @@ class ServiceLevelAdmin(admin.ModelAdmin): class ServiceAdmin(admin.ModelAdmin): list_display = ( "name", - "slug", "cloud_provider", "service_level", "price", "logo_preview", "category_list", + "partner_list", ) - list_filter = ("cloud_provider", "service_level", "countries", "categories") + list_filter = ( + "cloud_provider", + "service_level", + "countries", + "categories", + "consulting_partners", + ) + filter_horizontal = ("countries", "categories", "consulting_partners") search_fields = ("name", "description", "slug") prepopulated_fields = {"slug": ("name",)} - filter_horizontal = ("countries", "categories") def logo_preview(self, obj): if obj.logo: @@ -64,4 +77,22 @@ class ServiceAdmin(admin.ModelAdmin): def category_list(self, obj): return ", ".join([cat.name for cat in obj.categories.all()]) + def partner_list(self, obj): + return ", ".join([partner.name for partner in obj.consulting_partners.all()]) + + partner_list.short_description = "Consulting Partners" category_list.short_description = "Categories" + + +@admin.register(ConsultingPartner) +class ConsultingPartnerAdmin(admin.ModelAdmin): + list_display = ("name", "website", "logo_preview") + search_fields = ("name", "description") + prepopulated_fields = {"slug": ("name",)} + + def logo_preview(self, obj): + if obj.logo: + return format_html( + '', obj.logo.url + ) + return "No logo" diff --git a/hub/services/migrations/0007_consultingpartner_service_consulting_partners.py b/hub/services/migrations/0007_consultingpartner_service_consulting_partners.py new file mode 100644 index 0000000..6387c47 --- /dev/null +++ b/hub/services/migrations/0007_consultingpartner_service_consulting_partners.py @@ -0,0 +1,50 @@ +# Generated by Django 5.1.5 on 2025-01-28 07:49 + +import services.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("services", "0006_service_slug"), + ] + + operations = [ + migrations.CreateModel( + name="ConsultingPartner", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ("description", models.TextField(blank=True)), + ( + "logo", + models.ImageField( + blank=True, + null=True, + upload_to="partner_logos/", + validators=[services.models.validate_image_size], + ), + ), + ("website", models.URLField(blank=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ], + ), + migrations.AddField( + model_name="service", + name="consulting_partners", + field=models.ManyToManyField( + blank=True, related_name="services", to="services.consultingpartner" + ), + ), + ] diff --git a/hub/services/models.py b/hub/services/models.py index f6576b3..75b1260 100644 --- a/hub/services/models.py +++ b/hub/services/models.py @@ -68,6 +68,32 @@ class CloudProvider(models.Model): return reverse("services:provider_detail", kwargs={"slug": self.slug}) +class ConsultingPartner(models.Model): + name = models.CharField(max_length=200) + slug = models.SlugField(unique=True) + description = ProseEditorField(blank=True) + logo = models.ImageField( + upload_to="partner_logos/", + validators=[validate_image_size], + null=True, + blank=True, + ) + website = models.URLField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + def get_absolute_url(self): + return reverse("services:partner_detail", kwargs={"slug": self.slug}) + + class Country(models.Model): name = models.CharField(max_length=100) code = models.CharField(max_length=2) @@ -93,6 +119,9 @@ class Service(models.Model): slug = models.SlugField(max_length=250, unique=True) description = ProseEditorField() cloud_provider = models.ForeignKey(CloudProvider, on_delete=models.CASCADE) + consulting_partners = models.ManyToManyField( + ConsultingPartner, related_name="services", blank=True + ) service_level = models.ForeignKey(ServiceLevel, on_delete=models.CASCADE) categories = models.ManyToManyField(Category, related_name="services") countries = models.ManyToManyField(Country) diff --git a/hub/services/templates/services/partner_detail.html b/hub/services/templates/services/partner_detail.html new file mode 100644 index 0000000..0e01ff7 --- /dev/null +++ b/hub/services/templates/services/partner_detail.html @@ -0,0 +1,77 @@ +{% extends 'services/base.html' %} + +{% block content %} +
+
+
+ {% if partner.logo %} + {{ partner.name }} logo + {% endif %} +
+

{{ partner.name }}

+ {% if partner.website %} + + Visit Website + + {% endif %} +
+ {{ partner.description|safe }} +
+
+
+ +

Available Services

+
+ {% for service in services %} +
+
+
+
+ {% if service.logo %} + {{ service.name }} logo + {% endif %} +
+
{{ service.name }}
+
+ {% if service.cloud_provider.logo %} + {{ service.cloud_provider.name }} logo + {% endif %} +
+ {{ service.cloud_provider.name }} +
+
+
+
+
+ {{ service.description|safe|truncatewords_html:30 }} +
+
+ {% for category in service.categories.all %} + {{ category.full_path }} + {% endfor %} +
+

+ + Service Level: {{ service.service_level.name }}
+ Price: ${{ service.price }} +
+

+ View Details +
+
+
+ {% empty %} +
+
+ No services available from this partner yet. +
+
+ {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/hub/services/templates/services/service_detail.html b/hub/services/templates/services/service_detail.html index 923cf5e..ad76666 100644 --- a/hub/services/templates/services/service_detail.html +++ b/hub/services/templates/services/service_detail.html @@ -63,4 +63,35 @@ Back to Services + +{% if service.consulting_partners.exists %} +
+
+
Consulting Partners
+
+ {% for partner in service.consulting_partners.all %} +
+
+ {% if partner.logo %} + {{ partner.name }} logo + {% endif %} +
+ + {{ partner.name }} + + {% if partner.website %} +
+ + Visit Website + + {% endif %} +
+
+
+ {% endfor %} +
+
+
+{% endif %} {% endblock %} diff --git a/hub/services/templates/services/service_list.html b/hub/services/templates/services/service_list.html index 852d0c2..9400c22 100644 --- a/hub/services/templates/services/service_list.html +++ b/hub/services/templates/services/service_list.html @@ -40,6 +40,18 @@ {% endfor %} + +
+ + +
diff --git a/hub/services/urls.py b/hub/services/urls.py index c332763..2aa99b3 100644 --- a/hub/services/urls.py +++ b/hub/services/urls.py @@ -9,4 +9,5 @@ urlpatterns = [ path("service//interest/", views.create_lead, name="create_lead"), path("service//thank-you/", views.thank_you, name="thank_you"), path("provider//", views.provider_detail, name="provider_detail"), + path("partner//", views.partner_detail, name="partner_detail"), ] diff --git a/hub/services/views.py b/hub/services/views.py index 981aa78..7060f83 100644 --- a/hub/services/views.py +++ b/hub/services/views.py @@ -4,7 +4,14 @@ from django.conf import settings from django.shortcuts import render, get_object_or_404, redirect from django.contrib import messages from django.db.models import Q -from .models import Service, CloudProvider, Country, ServiceLevel, Category +from .models import ( + Service, + CloudProvider, + ConsultingPartner, + Country, + ServiceLevel, + Category, +) from .forms import LeadForm from .odoo import OdooAPI @@ -17,9 +24,9 @@ def service_list(request): cloud_providers = CloudProvider.objects.all() countries = Country.objects.all() service_levels = ServiceLevel.objects.all() - categories = Category.objects.filter(parent=None) # Top-level categories + categories = Category.objects.filter(parent=None) + consulting_partners = ConsultingPartner.objects.all() - # Filter handling if request.GET.get("cloud_provider"): services = services.filter(cloud_provider_id=request.GET.get("cloud_provider")) @@ -32,13 +39,16 @@ def service_list(request): if request.GET.get("category"): category_id = request.GET.get("category") category = Category.objects.get(id=category_id) - # Get all subcategories subcategories = Category.objects.filter(parent=category) - # Filter services in this category or its subcategories services = services.filter( Q(categories=category) | Q(categories__in=subcategories) ).distinct() + if request.GET.get("consulting_partner"): + services = services.filter( + consulting_partners__id=request.GET.get("consulting_partner") + ) + if request.GET.get("search"): query = request.GET.get("search") services = services.filter( @@ -51,6 +61,7 @@ def service_list(request): "countries": countries, "service_levels": service_levels, "categories": categories, + "consulting_partners": consulting_partners, } return render(request, "services/service_list.html", context) @@ -70,6 +81,16 @@ def provider_detail(request, slug): return render(request, "services/provider_detail.html", context) +def partner_detail(request, slug): + partner = get_object_or_404(ConsultingPartner, slug=slug) + services = Service.objects.filter(consulting_partners=partner) + return render( + request, + "services/partner_detail.html", + {"partner": partner, "services": services}, + ) + + def thank_you(request, slug): service = get_object_or_404(Service, slug=slug) return render(request, "services/thank_you.html", {"service": service})