From 5f833bf6892e4784452a2762db823c46aaec00cc Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Wed, 11 Dec 2024 14:23:37 +0100 Subject: [PATCH 1/3] fix/category-slug: added category slug field --- server/apps/research/admin/category_admin.py | 3 ++- .../research/migrations/0016_category_slug.py | 23 ++++++++++++++++ server/apps/research/models/category.py | 27 +++++++++++++++++++ .../serializers/category_serializer.py | 3 ++- server/apps/research/views.py | 8 +++--- 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 server/apps/research/migrations/0016_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/0016_category_slug.py b/server/apps/research/migrations/0016_category_slug.py new file mode 100644 index 0000000..000051c --- /dev/null +++ b/server/apps/research/migrations/0016_category_slug.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2024-12-11 13:08 + +from django.db import migrations, models + +def remove_duplicate_slugs(apps, schema_editor): + Category = apps.get_model('research', 'Category') + duplicate_slugs = Category.objects.values('slug').annotate(count=models.Count('slug')).filter(count__gt=1) + for slug in duplicate_slugs: + Category.objects.filter(slug=slug['slug']).exclude(id=models.Min('id')).delete() + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0015_relatedarticle_article_related_articles_and_more'), + ] + + 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..54f82ed 100644 --- a/server/apps/research/models/category.py +++ b/server/apps/research/models/category.py @@ -1,12 +1,39 @@ 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): + if not self.slug: + self.slug = self.generate_slug() + 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 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: From 6ec47b29853e1a9ebcef41acdbc7c37e848e9b33 Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Wed, 11 Dec 2024 14:34:57 +0100 Subject: [PATCH 2/3] fix: update model and migration --- .../research/migrations/0016_category_slug.py | 15 +++++++++++---- server/apps/research/models/category.py | 9 +++++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/server/apps/research/migrations/0016_category_slug.py b/server/apps/research/migrations/0016_category_slug.py index 000051c..93b550d 100644 --- a/server/apps/research/migrations/0016_category_slug.py +++ b/server/apps/research/migrations/0016_category_slug.py @@ -4,10 +4,17 @@ def remove_duplicate_slugs(apps, schema_editor): Category = apps.get_model('research', 'Category') - duplicate_slugs = Category.objects.values('slug').annotate(count=models.Count('slug')).filter(count__gt=1) - for slug in duplicate_slugs: - Category.objects.filter(slug=slug['slug']).exclude(id=models.Min('id')).delete() - + 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 = [ diff --git a/server/apps/research/models/category.py b/server/apps/research/models/category.py index 54f82ed..aaa82df 100644 --- a/server/apps/research/models/category.py +++ b/server/apps/research/models/category.py @@ -12,8 +12,13 @@ class Meta: verbose_name_plural = 'Categories' def save(self, *args, **kwargs): - if not self.slug: - self.slug = self.generate_slug() + 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: {str(e)}") super().save(*args, **kwargs) def __str__(self): From 0145b87d40624e70ae976d8e90c142c47ae8f98b Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Wed, 11 Dec 2024 14:41:07 +0100 Subject: [PATCH 3/3] fix --- server/apps/research/models/category.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/apps/research/models/category.py b/server/apps/research/models/category.py index aaa82df..f4b1491 100644 --- a/server/apps/research/models/category.py +++ b/server/apps/research/models/category.py @@ -12,14 +12,15 @@ class Meta: verbose_name_plural = 'Categories' def save(self, *args, **kwargs): - 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: {str(e)}") - super().save(*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