partner categories
This commit is contained in:
parent
83504f6b7c
commit
c6b50da971
8 changed files with 75 additions and 4 deletions
|
@ -107,6 +107,7 @@ class ConsultingPartnerAdmin(SortableAdminMixin, admin.ModelAdmin):
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
"name",
|
"name",
|
||||||
|
"category",
|
||||||
"website",
|
"website",
|
||||||
"logo_preview",
|
"logo_preview",
|
||||||
"disable_listing",
|
"disable_listing",
|
||||||
|
@ -114,12 +115,13 @@ class ConsultingPartnerAdmin(SortableAdminMixin, admin.ModelAdmin):
|
||||||
"order",
|
"order",
|
||||||
)
|
)
|
||||||
search_fields = ("name", "description")
|
search_fields = ("name", "description")
|
||||||
|
list_filter = ("category", "is_featured", "disable_listing")
|
||||||
prepopulated_fields = {"slug": ("name",)}
|
prepopulated_fields = {"slug": ("name",)}
|
||||||
filter_horizontal = ("services", "cloud_providers")
|
filter_horizontal = ("services", "cloud_providers")
|
||||||
ordering = ("order",)
|
ordering = ("order",)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {"fields": ("name", "slug", "description", "order")}),
|
(None, {"fields": ("name", "slug", "description", "category", "order")}),
|
||||||
(
|
(
|
||||||
"Images",
|
"Images",
|
||||||
{
|
{
|
||||||
|
|
23
hub/services/migrations/0046_add_partner_category.py
Normal file
23
hub/services/migrations/0046_add_partner_category.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.2 on 2025-07-11 08:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("services", "0045_add_og_image_to_article"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="consultingpartner",
|
||||||
|
name="category",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[("CONSULTING", "Consulting"), ("TRAINING", "Training")],
|
||||||
|
default="CONSULTING",
|
||||||
|
help_text="Category of the consulting partner",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -106,6 +106,11 @@ class Unit(models.TextChoices):
|
||||||
CPU = "CPU", "vCPU"
|
CPU = "CPU", "vCPU"
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerCategory(models.TextChoices):
|
||||||
|
CONSULTING = "CONSULTING", "Consulting"
|
||||||
|
TRAINING = "TRAINING", "Training"
|
||||||
|
|
||||||
|
|
||||||
# This should be a relation, but for now this is good enough :TM:
|
# This should be a relation, but for now this is good enough :TM:
|
||||||
class ManagedServiceProvider(models.TextChoices):
|
class ManagedServiceProvider(models.TextChoices):
|
||||||
VS = "VS", "VSHN"
|
VS = "VS", "VSHN"
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from .base import validate_image_size, get_prose_editor_field
|
from .base import validate_image_size, get_prose_editor_field, PartnerCategory
|
||||||
from .images import ImageReference
|
from .images import ImageReference
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,6 +51,14 @@ class ConsultingPartner(ImageReference):
|
||||||
email = models.EmailField(max_length=254, blank=True, null=True)
|
email = models.EmailField(max_length=254, blank=True, null=True)
|
||||||
address = models.TextField(max_length=250, blank=True, null=True)
|
address = models.TextField(max_length=250, blank=True, null=True)
|
||||||
|
|
||||||
|
# Partner category (hardcoded choices as requested)
|
||||||
|
category = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=PartnerCategory.choices,
|
||||||
|
default=PartnerCategory.CONSULTING,
|
||||||
|
help_text="Category of the partner",
|
||||||
|
)
|
||||||
|
|
||||||
services = models.ManyToManyField(
|
services = models.ManyToManyField(
|
||||||
"services.Service", related_name="consulting_partners", blank=True
|
"services.Service", related_name="consulting_partners", blank=True
|
||||||
)
|
)
|
||||||
|
@ -69,7 +77,11 @@ class ConsultingPartner(ImageReference):
|
||||||
ordering = ["order"]
|
ordering = ["order"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return f"{self.name} ({self.get_category_display()})"
|
||||||
|
|
||||||
|
def get_category_display_badge(self):
|
||||||
|
"""Returns category display suitable for badges/UI"""
|
||||||
|
return self.get_category_display()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
|
|
|
@ -65,6 +65,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="card-text">{{ article.related_consulting_partner.name }}</p>
|
<p class="card-text">{{ article.related_consulting_partner.name }}</p>
|
||||||
|
<div class="mb-2">
|
||||||
|
<span class="badge bg-primary">{{ article.related_consulting_partner.get_category_display_badge }}</span>
|
||||||
|
</div>
|
||||||
<a href="{{ article.related_consulting_partner.get_absolute_url }}" class="btn btn-primary btn-sm">View Partner</a>
|
<a href="{{ article.related_consulting_partner.get_absolute_url }}" class="btn btn-primary btn-sm">View Partner</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -153,7 +153,7 @@
|
||||||
<h2 class="fs-50 fw-semibold lh-1 mb-12">{{ partner.name }}</h2>
|
<h2 class="fs-50 fw-semibold lh-1 mb-12">{{ partner.name }}</h2>
|
||||||
</header>
|
</header>
|
||||||
<div class="fs-19 text-gray-500">
|
<div class="fs-19 text-gray-500">
|
||||||
<button class="btn btn-tertiary btn-sm mr-12">Servala Consulting Partner</button>
|
<button class="btn btn-tertiary btn-sm mr-12">{{ partner.get_category_display_badge }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,23 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Category Filter -->
|
||||||
|
<div class="pt-24 mb-24">
|
||||||
|
<div class="d-flex justify-content-between align-items-center h-33 mb-5px" role="button">
|
||||||
|
<h3 class="sidebar-title mb-0">Category</h3>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<select class="form-select" id="category" name="category" @change="submitForm()">
|
||||||
|
<option value="">All Categories</option>
|
||||||
|
{% for category_value, category_label in partner_categories %}
|
||||||
|
<option value="{{ category_value }}" {% if request.GET.category == category_value %}selected{% endif %}>
|
||||||
|
{{ category_label }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filter Actions -->
|
<!-- Filter Actions -->
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<a href="{% url 'services:partner_list' %}" class="btn btn-outline-secondary btn-sm">Clear</a>
|
<a href="{% url 'services:partner_list' %}" class="btn btn-outline-secondary btn-sm">Clear</a>
|
||||||
|
@ -126,6 +143,9 @@
|
||||||
<h3 class="card__title">
|
<h3 class="card__title">
|
||||||
<a href="{{ partner.get_absolute_url }}" class="text-decoration-none clickable-link">{{ partner.name }}</a>
|
<a href="{{ partner.get_absolute_url }}" class="text-decoration-none clickable-link">{{ partner.name }}</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
<div class="mb-2">
|
||||||
|
<span class="badge bg-primary">{{ partner.get_category_display_badge }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card__desc flex-grow-1 rich-text-content">
|
<div class="card__desc flex-grow-1 rich-text-content">
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from hub.services.models import ConsultingPartner, CloudProvider, Service
|
from hub.services.models import ConsultingPartner, CloudProvider, Service
|
||||||
|
from hub.services.models.base import PartnerCategory
|
||||||
|
|
||||||
|
|
||||||
def partner_list(request):
|
def partner_list(request):
|
||||||
|
@ -8,6 +9,7 @@ def partner_list(request):
|
||||||
search_query = request.GET.get("search", "")
|
search_query = request.GET.get("search", "")
|
||||||
service_id = request.GET.get("service", "")
|
service_id = request.GET.get("service", "")
|
||||||
cloud_provider_id = request.GET.get("cloud_provider", "")
|
cloud_provider_id = request.GET.get("cloud_provider", "")
|
||||||
|
category = request.GET.get("category", "")
|
||||||
|
|
||||||
# Start with all active partners
|
# Start with all active partners
|
||||||
partners = ConsultingPartner.objects.filter(disable_listing=False).order_by("order")
|
partners = ConsultingPartner.objects.filter(disable_listing=False).order_by("order")
|
||||||
|
@ -24,6 +26,9 @@ def partner_list(request):
|
||||||
if cloud_provider_id:
|
if cloud_provider_id:
|
||||||
partners = partners.filter(cloud_providers__id=cloud_provider_id)
|
partners = partners.filter(cloud_providers__id=cloud_provider_id)
|
||||||
|
|
||||||
|
if category:
|
||||||
|
partners = partners.filter(category=category)
|
||||||
|
|
||||||
# Get available services from filtered partners
|
# Get available services from filtered partners
|
||||||
available_service_ids = partners.values_list("services__id", flat=True).distinct()
|
available_service_ids = partners.values_list("services__id", flat=True).distinct()
|
||||||
available_services = Service.objects.filter(
|
available_services = Service.objects.filter(
|
||||||
|
@ -68,6 +73,7 @@ def partner_list(request):
|
||||||
),
|
),
|
||||||
"available_services": available_services,
|
"available_services": available_services,
|
||||||
"available_cloud_providers": available_cloud_providers,
|
"available_cloud_providers": available_cloud_providers,
|
||||||
|
"partner_categories": PartnerCategory.choices,
|
||||||
}
|
}
|
||||||
return render(request, "services/partner_list.html", context)
|
return render(request, "services/partner_list.html", context)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue