From fda2f14ef579b6ccf34db4c45ff5b8e7eb5d84f0 Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Fri, 13 Dec 2024 08:51:50 +0100 Subject: [PATCH] rev: category slug url --- server/apps/research/admin/category_admin.py | 3 +- .../research/migrations/0015_category_slug.py | 30 +++++++++++++++++ server/apps/research/models/category.py | 33 +++++++++++++++++++ .../serializers/category_serializer.py | 3 +- server/apps/research/views.py | 8 +++-- 5 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 server/apps/research/migrations/0015_category_slug.py diff --git a/server/apps/research/admin/category_admin.py b/server/apps/research/admin/category_admin.py index 8dcc690..01d742d 100644 --- a/server/apps/research/admin/category_admin.py +++ b/server/apps/research/admin/category_admin.py @@ -5,8 +5,9 @@ class CategoryAdmin(admin.ModelAdmin): """Admin interface for the Category model.""" - list_display = ('name', 'created_at') + list_display = ('name', 'slug', 'created_at') list_per_page = 25 search_fields = ('name',) list_filter = ('created_at',) ordering = ('name',) + readonly_fields = ('slug',) \ No newline at end of file diff --git a/server/apps/research/migrations/0015_category_slug.py b/server/apps/research/migrations/0015_category_slug.py new file mode 100644 index 0000000..6c06f45 --- /dev/null +++ b/server/apps/research/migrations/0015_category_slug.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.8 on 2024-12-13 07:34 + +from django.db import migrations, models + +def remove_duplicate_slugs(apps, schema_editor): + Category = apps.get_model('research', 'Category') + seen_slugs = {} + + for category in Category.objects.order_by('id'): + base_slug = category.slug + counter = 1 + while category.slug in seen_slugs: + category.slug = f"{base_slug}-{counter}" + counter += 1 + seen_slugs[category.slug] = True + category.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0014_alter_article_authors'), + ] + + operations = [ + migrations.AddField( + model_name='category', + name='slug', + field=models.SlugField(blank=True, max_length=255), + ), + ] diff --git a/server/apps/research/models/category.py b/server/apps/research/models/category.py index e86f32c..5f8da82 100644 --- a/server/apps/research/models/category.py +++ b/server/apps/research/models/category.py @@ -1,12 +1,45 @@ from django.db import models from apps.common.models import BaseModel +from django.utils.text import slugify +from django.db import transaction class Category(BaseModel): """Model for categories.""" name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, blank=True) class Meta: verbose_name_plural = 'Categories' + + def save(self, *args, **kwargs): + with transaction.atomic(): + try: + if not self.slug: + self.slug = self.generate_slug() + if len(self.slug) > 255: + raise ValueError("Generated slug exceeds maximum length") + except ValueError as e: + raise ValueError(f"Failed to generate valid slug") from e + super().save(*args, **kwargs) def __str__(self): return self.name + + def generate_slug(self): + + if not self.name: + raise ValueError("Name is required to generate slug") + + base_slug = slugify(self.name) + slug = base_slug + num = 1 + with transaction.atomic(): + while ( + Category.objects.select_for_update() + .filter(slug=slug) + .exclude(id=self.id) # Exclude current instance when updating + .exists() + ): + slug = f"{base_slug}-{num}" + num += 1 + return slug \ No newline at end of file diff --git a/server/apps/research/serializers/category_serializer.py b/server/apps/research/serializers/category_serializer.py index 45f2ec0..79cf2b3 100644 --- a/server/apps/research/serializers/category_serializer.py +++ b/server/apps/research/serializers/category_serializer.py @@ -3,6 +3,7 @@ class CategorySerializer(serializers.ModelSerializer): """Serializer for the Category model.""" + slug = serializers.SlugField(read_only=True, max_length=255, help_text='URL-friendly version of the category name.') class Meta: model = Category - fields = ['id', 'name'] \ No newline at end of file + fields = ['id', 'name', 'slug'] \ No newline at end of file diff --git a/server/apps/research/views.py b/server/apps/research/views.py index eb355b9..dd98ce9 100644 --- a/server/apps/research/views.py +++ b/server/apps/research/views.py @@ -102,11 +102,13 @@ def retrieve_by_identifier(self, request, identifier=None): status=status.HTTP_404_NOT_FOUND) # Custom action to retrieve articles by category - @action(detail=False, methods=['get'], url_path=r'category/(?P[-\w]+)') - def retrieve_by_category(self, request, category=None): + @action(detail=False, methods=['get'], url_path=r'category/(?P[-\w]+)') + def retrieve_by_category(self, request, category_slug=None): """Retrieve article list by category.""" try: - instances = Article.objects.filter(categories__name=category) + instances = Article.objects.filter(categories__slug=category_slug) + if not instances.exists(): + return Response({'error': 'No articles found for this category'}, status=status.HTTP_404_NOT_FOUND) serializer = self.get_serializer(instances, many=True) return Response({'success': True, 'data': serializer.data}) except Exception as e: