From ae82a63fed9b6519d3fb198c48794604ffe8e9ab Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Mon, 9 Dec 2024 11:54:26 +0100 Subject: [PATCH 1/5] update: add slug to category --- .../research/migrations/0015_category_slug.py | 18 ++++++++++++++++++ server/apps/research/models/category.py | 9 ++++++++- .../serializers/category_serializer.py | 2 +- server/apps/research/views.py | 6 ++++-- 4 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 server/apps/research/migrations/0015_category_slug.py 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..5f0fd72 --- /dev/null +++ b/server/apps/research/migrations/0015_category_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-12-09 10:51 + +from django.db import migrations, models + + +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, unique=True), + ), + ] diff --git a/server/apps/research/models/category.py b/server/apps/research/models/category.py index e86f32c..0192ef5 100644 --- a/server/apps/research/models/category.py +++ b/server/apps/research/models/category.py @@ -1,12 +1,19 @@ from django.db import models from apps.common.models import BaseModel +from django.utils.text import slugify class Category(BaseModel): """Model for categories.""" name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, unique=True, blank=True) class Meta: verbose_name_plural = 'Categories' + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) def __str__(self): - return self.name + return self.name \ 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..0ff11e0 100644 --- a/server/apps/research/serializers/category_serializer.py +++ b/server/apps/research/serializers/category_serializer.py @@ -5,4 +5,4 @@ class CategorySerializer(serializers.ModelSerializer): """Serializer for the Category model.""" 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..107ef9f 100644 --- a/server/apps/research/views.py +++ b/server/apps/research/views.py @@ -103,10 +103,12 @@ def retrieve_by_identifier(self, request, identifier=None): # 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): + 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 30967ed1b0f7426a1ff1c9bc67f96d503f98b7ad Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Mon, 9 Dec 2024 12:28:33 +0100 Subject: [PATCH 2/5] feat(update): category slug implementation --- .../research/migrations/0015_category_slug.py | 4 ++-- .../migrations/0016_alter_category_slug.py | 18 ++++++++++++++++++ server/apps/research/models/category.py | 15 +++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 server/apps/research/migrations/0016_alter_category_slug.py diff --git a/server/apps/research/migrations/0015_category_slug.py b/server/apps/research/migrations/0015_category_slug.py index 5f0fd72..4ee94d1 100644 --- a/server/apps/research/migrations/0015_category_slug.py +++ b/server/apps/research/migrations/0015_category_slug.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.8 on 2024-12-09 10:51 +# Generated by Django 5.0.8 on 2024-12-09 11:07 from django.db import migrations, models @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='category', name='slug', - field=models.SlugField(blank=True, max_length=255, unique=True), + field=models.SlugField(blank=True, max_length=255), ), ] diff --git a/server/apps/research/migrations/0016_alter_category_slug.py b/server/apps/research/migrations/0016_alter_category_slug.py new file mode 100644 index 0000000..6df3e9e --- /dev/null +++ b/server/apps/research/migrations/0016_alter_category_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-12-09 11:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0015_category_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='category', + name='slug', + field=models.SlugField(blank=True, max_length=255, unique=True), + ), + ] diff --git a/server/apps/research/models/category.py b/server/apps/research/models/category.py index 0192ef5..655f9dd 100644 --- a/server/apps/research/models/category.py +++ b/server/apps/research/models/category.py @@ -12,8 +12,19 @@ class Meta: def save(self, *args, **kwargs): if not self.slug: - self.slug = slugify(self.name) + self.generate_slug() + self.generate_slug() super().save(*args, **kwargs) def __str__(self): - return self.name \ No newline at end of file + return self.name + + def generate_slug(self): + + base_slug = slugify(self.name) + slug = base_slug + num = 1 + while Category.objects.filter(slug=slug).exists(): + slug = f"{base_slug}-{num}" + num += 1 + return slug \ No newline at end of file From 659080e06d34ad13050798df01bdf2ec6c6e0d50 Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Mon, 9 Dec 2024 12:36:43 +0100 Subject: [PATCH 3/5] update: added slug to list display on admin panel --- server/apps/research/admin/category_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/apps/research/admin/category_admin.py b/server/apps/research/admin/category_admin.py index 8dcc690..fb87367 100644 --- a/server/apps/research/admin/category_admin.py +++ b/server/apps/research/admin/category_admin.py @@ -5,7 +5,7 @@ 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',) From 695afae81546d560f654abe52af27df3e7bc3e89 Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Mon, 9 Dec 2024 14:08:22 +0100 Subject: [PATCH 4/5] fix: updated codebase inline with code review --- server/apps/research/models/category.py | 19 ++++++++++++++----- .../serializers/category_serializer.py | 1 + server/apps/research/views.py | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/server/apps/research/models/category.py b/server/apps/research/models/category.py index 655f9dd..080d673 100644 --- a/server/apps/research/models/category.py +++ b/server/apps/research/models/category.py @@ -1,6 +1,7 @@ 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.""" @@ -12,8 +13,7 @@ class Meta: def save(self, *args, **kwargs): if not self.slug: - self.generate_slug() - self.generate_slug() + self.slug = self.generate_slug() super().save(*args, **kwargs) def __str__(self): @@ -21,10 +21,19 @@ def __str__(self): 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 - while Category.objects.filter(slug=slug).exists(): - slug = f"{base_slug}-{num}" - 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 0ff11e0..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', 'slug'] \ No newline at end of file diff --git a/server/apps/research/views.py b/server/apps/research/views.py index 107ef9f..dd98ce9 100644 --- a/server/apps/research/views.py +++ b/server/apps/research/views.py @@ -102,7 +102,7 @@ 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]+)') + @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: From 7286a89adf0e4aeabd9703b72b805fa988727112 Mon Sep 17 00:00:00 2001 From: Happy Felix Chukwuma Date: Mon, 9 Dec 2024 14:26:39 +0100 Subject: [PATCH 5/5] made category slug field readonly --- server/apps/research/admin/category_admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/apps/research/admin/category_admin.py b/server/apps/research/admin/category_admin.py index fb87367..0edf8ef 100644 --- a/server/apps/research/admin/category_admin.py +++ b/server/apps/research/admin/category_admin.py @@ -10,3 +10,4 @@ class CategoryAdmin(admin.ModelAdmin): search_fields = ('name',) list_filter = ('created_at',) ordering = ('name',) + readonly_fields = ('slug',)