diff --git a/hub/services/admin.py b/hub/services/admin.py index 0b4dc4b..4481180 100644 --- a/hub/services/admin.py +++ b/hub/services/admin.py @@ -1,6 +1,15 @@ from django.contrib import admin from django.utils.html import format_html -from .models import CloudProvider, Country, ServiceLevel, Service +from .models import CloudProvider, Country, ServiceLevel, Service, Category + + +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ("name", "slug", "parent", "order") + list_filter = ("parent",) + search_fields = ("name", "description") + prepopulated_fields = {"slug": ("name",)} + ordering = ("order", "name") @admin.register(CloudProvider) @@ -30,10 +39,17 @@ class ServiceLevelAdmin(admin.ModelAdmin): @admin.register(Service) class ServiceAdmin(admin.ModelAdmin): - list_display = ("name", "cloud_provider", "service_level", "price", "logo_preview") - list_filter = ("cloud_provider", "service_level", "countries") + list_display = ( + "name", + "cloud_provider", + "service_level", + "price", + "logo_preview", + "category_list", + ) + list_filter = ("cloud_provider", "service_level", "countries", "categories") search_fields = ("name", "description") - filter_horizontal = ("countries",) + filter_horizontal = ("countries", "categories") def logo_preview(self, obj): if obj.logo: @@ -41,3 +57,8 @@ class ServiceAdmin(admin.ModelAdmin): '', obj.logo.url ) return "No logo" + + def category_list(self, obj): + return ", ".join([cat.name for cat in obj.categories.all()]) + + category_list.short_description = "Categories" diff --git a/hub/services/migrations/0003_category_service_categories.py b/hub/services/migrations/0003_category_service_categories.py new file mode 100644 index 0000000..0909d50 --- /dev/null +++ b/hub/services/migrations/0003_category_service_categories.py @@ -0,0 +1,53 @@ +# Generated by Django 5.1.5 on 2025-01-27 14:19 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("services", "0002_cloudprovider_logo_service_logo"), + ] + + operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ("slug", models.SlugField(unique=True)), + ("description", models.TextField(blank=True)), + ("order", models.IntegerField(default=0)), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="services.category", + ), + ), + ], + options={ + "verbose_name_plural": "Categories", + "ordering": ["order", "name"], + }, + ), + migrations.AddField( + model_name="service", + name="categories", + field=models.ManyToManyField( + related_name="services", to="services.category" + ), + ), + ] diff --git a/hub/services/models.py b/hub/services/models.py index 3f34064..43b2a88 100644 --- a/hub/services/models.py +++ b/hub/services/models.py @@ -1,5 +1,6 @@ from django.db import models from django.core.exceptions import ValidationError +from django.utils.text import slugify def validate_image_size(value): @@ -8,6 +9,39 @@ def validate_image_size(value): raise ValidationError("Maximum file size is 1MB") +class Category(models.Model): + name = models.CharField(max_length=100) + slug = models.SlugField(unique=True) + parent = models.ForeignKey( + "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children" + ) + description = models.TextField(blank=True) + order = models.IntegerField(default=0) + + class Meta: + verbose_name_plural = "Categories" + ordering = ["order", "name"] + + def __str__(self): + if self.parent: + return f"{self.parent} > {self.name}" + return self.name + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + @property + def full_path(self): + path = [self.name] + parent = self.parent + while parent: + path.append(parent.name) + parent = parent.parent + return " > ".join(reversed(path)) + + class CloudProvider(models.Model): name = models.CharField(max_length=100) description = models.TextField(blank=True) @@ -47,6 +81,7 @@ class Service(models.Model): description = models.TextField() cloud_provider = models.ForeignKey(CloudProvider, on_delete=models.CASCADE) service_level = models.ForeignKey(ServiceLevel, on_delete=models.CASCADE) + categories = models.ManyToManyField(Category, related_name="services") countries = models.ManyToManyField(Country) price = models.DecimalField(max_digits=10, decimal_places=2) features = models.TextField() diff --git a/hub/services/templates/services/service_detail.html b/hub/services/templates/services/service_detail.html index 69bd185..01a1efb 100644 --- a/hub/services/templates/services/service_detail.html +++ b/hub/services/templates/services/service_detail.html @@ -3,6 +3,12 @@ {% block content %}
+
Categories
+ {% for category in service.categories.all %} +
+ {{ category.full_path }} +
+ {% endfor %}
{% if service.logo %} {{ service.name }} logo diff --git a/hub/services/templates/services/service_list.html b/hub/services/templates/services/service_list.html index 5ee4fb7..872aa8e 100644 --- a/hub/services/templates/services/service_list.html +++ b/hub/services/templates/services/service_list.html @@ -12,6 +12,23 @@
+
+ + +
+