diff --git a/server/apps/research/admin/article_admin.py b/server/apps/research/admin/article_admin.py index 9ba92fd..7544e33 100644 --- a/server/apps/research/admin/article_admin.py +++ b/server/apps/research/admin/article_admin.py @@ -1,19 +1,19 @@ from django.contrib import admin from django import forms from django_ckeditor_5.widgets import CKEditor5Widget -from apps.research.models import Article, Author +from apps.research.models import Article, Author, Category @admin.register(Article) class ArticleAdmin(admin.ModelAdmin): """Admin interface for the Article model.""" fieldsets = [ - ('Article Details', {'fields': ['title', 'category', 'thumb', 'content', 'summary', 'status', 'authors']}), + ('Article Details', {'fields': ['title', 'categories', 'thumb', 'content', 'summary', 'status', 'authors']}), ] - list_display = ('title', 'display_authors', 'status', 'views', 'category', 'created_at') + list_display = ('title', 'display_authors', 'status', 'views', 'display_categories', 'created_at') search_fields = ('title', 'authors__user__username', 'authors__twitter_username', 'content') - list_per_page = 25 - list_filter = ('authors', 'status', 'category', 'created_at') + list_per_page = 25 + list_filter = ('authors', 'status', 'categories', 'created_at') readonly_fields = ('views', 'slug') list_editable = ('status',) @@ -29,17 +29,23 @@ def display_authors(self, obj): return ", ".join(author.user.username for author in obj.authors.all()) display_authors.short_description = 'Authors' + def display_categories(self, obj): + """Return a comma-separated list of categories for the article.""" + return ", ".join(category.name for category in obj.categories.all()) + display_categories.short_description = 'Categories' + def save_model(self, request, obj, form, change): """Automatically add the logged-in user as the author when creating a new article.""" if not change: # If creating a new article - author = Author.objects.filter(user=request.user).first() # Get the Author instance for the logged-in user + author = Author.objects.filter(user=request.user).first() if author: + obj.save() obj.authors.add(author) - super().save_model(request, obj, form, change) + else: + super().save_model(request, obj, form, change) def has_change_permission(self, request, obj=None): """Check if the user has permission to change the article.""" - if request.user.is_superuser: return True if not super().has_change_permission(request, obj): diff --git a/server/apps/research/admin/category_admin.py b/server/apps/research/admin/category_admin.py index 6e44f82..8dcc690 100644 --- a/server/apps/research/admin/category_admin.py +++ b/server/apps/research/admin/category_admin.py @@ -8,3 +8,5 @@ class CategoryAdmin(admin.ModelAdmin): list_display = ('name', 'created_at') list_per_page = 25 search_fields = ('name',) + list_filter = ('created_at',) + ordering = ('name',) diff --git a/server/apps/research/migrations/0006_remove_article_category_article_categories_and_more.py b/server/apps/research/migrations/0006_remove_article_category_article_categories_and_more.py new file mode 100644 index 0000000..ea5472e --- /dev/null +++ b/server/apps/research/migrations/0006_remove_article_category_article_categories_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.7 on 2024-08-14 09:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0005_alter_author_bio_alter_author_twitter_username'), + ] + + operations = [ + migrations.RemoveField( + model_name='article', + name='category', + ), + migrations.AddField( + model_name='article', + name='categories', + field=models.ManyToManyField(blank=True, related_name='articles', to='research.category'), + ), + migrations.AlterField( + model_name='article', + name='summary', + field=models.CharField(blank=True, max_length=100), + ), + ] diff --git a/server/apps/research/models/article.py b/server/apps/research/models/article.py index 1ef3f7b..92b29f2 100644 --- a/server/apps/research/models/article.py +++ b/server/apps/research/models/article.py @@ -16,10 +16,10 @@ class Article(BaseModel): title = models.CharField(max_length=100) content = CKEditor5Field(null=True, blank=True, config_name='extends') - summary = models.TextField(blank=True) + summary = models.CharField(max_length=100, blank=True) authors = models.ManyToManyField(Author, blank=True, related_name='articles') slug = models.SlugField(blank=True) - category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name="articles") + categories = models.ManyToManyField(Category, blank=True, related_name='articles') thumb = models.ImageField(upload_to='images/', default='../media/images/2077-Collective.png', blank=True) views = models.PositiveBigIntegerField(default=0) status = models.CharField(max_length=10, choices=options, default='draft') diff --git a/server/apps/research/serializers/article_serializer.py b/server/apps/research/serializers/article_serializer.py index 6986a02..c55bd2e 100644 --- a/server/apps/research/serializers/article_serializer.py +++ b/server/apps/research/serializers/article_serializer.py @@ -1,13 +1,13 @@ from rest_framework import serializers -from ..models import Article, Author +from ..models import Article, Author, Category from .author_serializer import AuthorSerializer from .category_serializer import CategorySerializer class ArticleSerializer(serializers.ModelSerializer): """Serializer for the Article model.""" authors = AuthorSerializer(many=True, read_only=True) - slug = serializers.ReadOnlyField() - category = CategorySerializer() + categories = CategorySerializer(many=True) + slug = serializers.ReadOnlyField() views = serializers.ReadOnlyField() class Meta: @@ -17,27 +17,42 @@ class Meta: class ArticleCreateUpdateSerializer(serializers.ModelSerializer): """Serializer for creating and updating articles.""" authors = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all(), many=True, required=False) + categories = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(), many=True, required=False) class Meta: model = Article - fields = ['title', 'category', 'thumb', 'content', 'summary', 'status', 'authors'] + fields = ['title', 'categories', 'thumb', 'content', 'summary', 'status', 'authors'] # TODO : Debug this method to add the logged-in user as the author when creating a new article def create(self, validated_data): request = self.context.get('request') authors = validated_data.pop('authors', []) + categories = validated_data.pop('categories', []) + if not authors and request and hasattr(request, 'user'): user_author = Author.objects.filter(user=request.user).first() if user_author: authors = [user_author] + article = Article.objects.create(**validated_data) + if authors: article.authors.set(authors) + if categories: + article.categories.set(categories) + return article + def update(self, instance, validated_data): authors = validated_data.pop('authors', []) + categories = validated_data.pop('categories', []) + instance = super().update(instance, validated_data) + if authors: - instance.authors.set(authors) # Update authors for the article + instance.authors.set(authors) + if categories: + instance.categories.set(categories) + return instance diff --git a/server/core/config/ckeditor.py b/server/core/config/ckeditor.py index 1cfcaac..6c0fcdd 100644 --- a/server/core/config/ckeditor.py +++ b/server/core/config/ckeditor.py @@ -38,6 +38,7 @@ 'bulletedList', 'numberedList', 'findAndReplace', 'highlight', 'subscript', 'superscript', 'specialCharacters', '|', 'imageInsert', 'code', 'codeBlock', 'insertTable', 'mediaEmbed', '|', 'blockQuote', 'fullscreen', 'removeFormat'], + 'extraAllowedContent': '*(*);*[*]', 'image': { 'toolbar': ['imageTextAlternative', '|', 'imageStyle:alignLeft', diff --git a/server/db.sqlite3 b/server/db.sqlite3 index 48a0385..15c517f 100644 Binary files a/server/db.sqlite3 and b/server/db.sqlite3 differ diff --git a/server/media/images/UnderstandingBlockchainGovernance.BB5AHiNA_1c0KSU.webp b/server/media/images/UnderstandingBlockchainGovernance.BB5AHiNA_1c0KSU.webp new file mode 100644 index 0000000..d37d20a Binary files /dev/null and b/server/media/images/UnderstandingBlockchainGovernance.BB5AHiNA_1c0KSU.webp differ