diff --git a/.env.example b/.env.example index d83fe91..d7bb8bd 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ DB_HOST='localhost' DB_NAME='django_cameroon' DB_USER='' DB_PASSWORD='' -DB_PORT='3306' +DB_PORT='5432' # Email EMAIL_HOST='smtp.gmail.com' diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 1559f7e..f9fc5bb 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -13,3 +13,4 @@ Closes #23 ## Screenshots (Insert image, only for frontend) +Bearer d855f89533088ce049754df15432f411 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 09f2680..3d2b97e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # Ignore the virtual environment venv/ +.venv # Ignore the Django secret key secret_key.txt diff --git a/README.md b/README.md index e59dbc7..4687b3b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Assuming postgresql is installed in your computer, follow what's next: sudo -u postgres psql ``` - ```bash + -- Create a database CREATE DATABASE django_website_db; diff --git a/apps/blog/__init__.py b/apps/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/blog/admin.py b/apps/blog/admin.py new file mode 100644 index 0000000..6f2c072 --- /dev/null +++ b/apps/blog/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from apps.blog.models.tag import Tag +from apps.blog.models.category import Category +from apps.blog.models.author import Author +from apps.blog.models.blog import Blog +from apps.blog.models.image import Image + +admin.site.register(Tag) +admin.site.register(Category) +admin.site.register(Author) +admin.site.register(Blog) +admin.site.register(Image) diff --git a/apps/blog/apps.py b/apps/blog/apps.py new file mode 100644 index 0000000..ece6ff5 --- /dev/null +++ b/apps/blog/apps.py @@ -0,0 +1,16 @@ +from django.apps import AppConfig + + +# class BlogConfig(AppConfig): +# default_auto_field = 'django.db.models.BigAutoField' +# name = 'blog' + + + +class BlogConfig(AppConfig): + name = 'apps.blog' + default_auto_field = 'django.db.models.BigAutoField' + + def ready(self): + import apps.blog.signals + diff --git a/apps/blog/migrations/0001_initial.py b/apps/blog/migrations/0001_initial.py new file mode 100644 index 0000000..edd932a --- /dev/null +++ b/apps/blog/migrations/0001_initial.py @@ -0,0 +1,59 @@ +# Generated by Django 5.0.1 on 2024-06-05 23:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Author', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('bio', models.TextField()), + ], + ), + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Blog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('content', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.author')), + ('categories', models.ManyToManyField(to='blog.category')), + ('tags', models.ManyToManyField(to='blog.tag')), + ], + ), + migrations.CreateModel( + name='Image', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image_file', models.ImageField(upload_to='images/')), + ('uploaded_at', models.DateTimeField(auto_now_add=True)), + ('blog_post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='blog.blog')), + ], + ), + ] diff --git a/apps/blog/migrations/0002_author_active_author_created_at_author_created_by_and_more.py b/apps/blog/migrations/0002_author_active_author_created_at_author_created_by_and_more.py new file mode 100644 index 0000000..9f07ddd --- /dev/null +++ b/apps/blog/migrations/0002_author_active_author_created_at_author_created_by_and_more.py @@ -0,0 +1,162 @@ +# Generated by Django 5.0.1 on 2024-10-04 08:17 + +import django.db.models.deletion +import django.utils.timezone +import utils.main +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='author', + name='active', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='author', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='author', + name='created_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='author', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='author', + name='updated_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='blog', + name='active', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='blog', + name='created_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='blog', + name='updated_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='category', + name='active', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='category', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='category', + name='created_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='category', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='category', + name='updated_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='image', + name='active', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='image', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='image', + name='created_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='image', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='image', + name='updated_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='tag', + name='active', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='tag', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='tag', + name='created_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='tag', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='tag', + name='updated_by', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_%(class)s_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='author', + name='id', + field=models.UUIDField(default=utils.main.generate_uuid, editable=False, help_text='Unique identifier for this object', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='blog', + name='id', + field=models.UUIDField(default=utils.main.generate_uuid, editable=False, help_text='Unique identifier for this object', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='category', + name='id', + field=models.UUIDField(default=utils.main.generate_uuid, editable=False, help_text='Unique identifier for this object', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='image', + name='id', + field=models.UUIDField(default=utils.main.generate_uuid, editable=False, help_text='Unique identifier for this object', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='tag', + name='id', + field=models.UUIDField(default=utils.main.generate_uuid, editable=False, help_text='Unique identifier for this object', primary_key=True, serialize=False), + ), + ] diff --git a/apps/blog/migrations/__init__.py b/apps/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/blog/models/__init__.py b/apps/blog/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/blog/models/author.py b/apps/blog/models/author.py new file mode 100644 index 0000000..8643288 --- /dev/null +++ b/apps/blog/models/author.py @@ -0,0 +1,13 @@ +from django.db import models +from apps.users.models.base_model import BaseModel + + + +class Author(BaseModel): + name = models.CharField(max_length=100) + bio = models.TextField() + + def __str__(self): + return self.name + + diff --git a/apps/blog/models/blog.py b/apps/blog/models/blog.py new file mode 100644 index 0000000..f2dd627 --- /dev/null +++ b/apps/blog/models/blog.py @@ -0,0 +1,20 @@ +from django.db import models +from apps.users.models.base_model import BaseModel +from apps.blog.models.author import Author +from apps.blog.models.tag import Tag +from apps.blog.models.category import Category + +class Blog(BaseModel): + title = models.CharField(max_length=200) + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + author = models.ForeignKey(Author, on_delete=models.CASCADE) + categories = models.ManyToManyField(Category) + tags = models.ManyToManyField(Tag) + + def __str__(self): + return self.title + + + diff --git a/apps/blog/models/category.py b/apps/blog/models/category.py new file mode 100644 index 0000000..8f51858 --- /dev/null +++ b/apps/blog/models/category.py @@ -0,0 +1,10 @@ +from django.db import models +from apps.users.models.base_model import BaseModel + +class Category(BaseModel): + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + + diff --git a/apps/blog/models/image.py b/apps/blog/models/image.py new file mode 100644 index 0000000..c36131b --- /dev/null +++ b/apps/blog/models/image.py @@ -0,0 +1,12 @@ +from django.db import models +from apps.users.models.base_model import BaseModel +from apps.blog.models.blog import Blog + +class Image(BaseModel): + image_file = models.ImageField(upload_to='images/') + uploaded_at = models.DateTimeField(auto_now_add=True) + blog_post = models.ForeignKey(Blog, related_name='images', on_delete=models.CASCADE) + + def __str__(self): + return f"Image for {self.blog_post.title}" + diff --git a/apps/blog/models/tag.py b/apps/blog/models/tag.py new file mode 100644 index 0000000..a5f492f --- /dev/null +++ b/apps/blog/models/tag.py @@ -0,0 +1,9 @@ +from django.db import models +from apps.users.models.base_model import BaseModel + +class Tag(BaseModel): + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + \ No newline at end of file diff --git a/apps/blog/permissions.py b/apps/blog/permissions.py new file mode 100644 index 0000000..124c52d --- /dev/null +++ b/apps/blog/permissions.py @@ -0,0 +1,8 @@ + +from rest_framework.permissions import BasePermission, SAFE_METHODS + +class IsAuthorOrReadOnly(BasePermission): + def has_object_permission(self, request, view, obj): + if request.method in SAFE_METHODS: + return True + return obj.author == request.user \ No newline at end of file diff --git a/apps/blog/routes/__init__.py b/apps/blog/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/blog/routes/api.py b/apps/blog/routes/api.py new file mode 100644 index 0000000..20c88d6 --- /dev/null +++ b/apps/blog/routes/api.py @@ -0,0 +1,21 @@ +from django.urls import path +from apps.blog.views.post import PostDetail, PostList +from apps.blog.views.blog import BlogListCreateView +from apps.blog.views.author import AuthorListView +from apps.blog.views.category import CategoryListView +from apps.blog.views.image import ImageListView, ImageCreateView +from apps.blog.views.tag import TagListView +from apps.blog.views.index import index + + +urlpatterns = [ + path('posts/', PostList.as_view(), name='post-list'), + path('posts//', PostDetail.as_view(), name='post-detail'), + path('posts/create/', BlogListCreateView.as_view(), name='post-create'), + path('authors/', AuthorListView.as_view(), name='author-list'), + path('categories/', CategoryListView.as_view(), name='category-list'), + path('images/', ImageListView.as_view(), name='image-list'), + path('images/create/', ImageCreateView.as_view(), name='image-create'), + path('tags/', TagListView.as_view(), name='tag-list'), + path('', index, name='index'), +] diff --git a/apps/blog/serializers/__init__.py b/apps/blog/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/blog/serializers/author_serializer.py b/apps/blog/serializers/author_serializer.py new file mode 100644 index 0000000..63f83a9 --- /dev/null +++ b/apps/blog/serializers/author_serializer.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from apps.blog.models.author import Author + + +class AuthorSerializer(serializers.ModelSerializer): + class Meta: + model = Author + fields = ('id', 'name', 'bio') diff --git a/apps/blog/serializers/blog_serializer.py b/apps/blog/serializers/blog_serializer.py new file mode 100644 index 0000000..d6dfb99 --- /dev/null +++ b/apps/blog/serializers/blog_serializer.py @@ -0,0 +1,99 @@ +from rest_framework import serializers +from apps.blog.models.blog import Blog +from apps.blog.models.author import Author +from apps.blog.models.category import Category +from apps.blog.models.tag import Tag +from apps.blog.serializers.author_serializer import AuthorSerializer +from apps.blog.serializers.category_serializer import CategorySerializer +from apps.blog.serializers.tag_serializer import TagSerializer +from apps.blog.serializers.image_serializer import ImageSerializer + + +class BlogSerializer(serializers.ModelSerializer): + author = AuthorSerializer() + categories = CategorySerializer(many=True) + tags = TagSerializer(many=True) + images = ImageSerializer(many=True, read_only=True) + + class Meta: + model = Blog + fields = ['author', 'categories', 'tags', 'title', 'content', 'images'] + + def create(self, validated_data): + author_data = validated_data.pop('author') + categories_data = validated_data.pop('categories', []) + tags_data = validated_data.pop('tags', []) + + author_instance, _ = Author.objects.get_or_create(**author_data) + + blog = Blog.objects.create(author=author_instance, **validated_data) + + self._update_categories_and_tags(blog, categories_data, tags_data) + + return blog + + def update(self, instance, validated_data): + author_data = validated_data.pop('author', None) + categories_data = validated_data.pop('categories', []) + tags_data = validated_data.pop('tags', []) + + if author_data: + author_instance, _ = Author.objects.get_or_create(**author_data) + instance.author = author_instance + + instance.title = validated_data.get('title', instance.title) + instance.content = validated_data.get('content', instance.content) + instance.save() + + self._update_categories_and_tags(instance, categories_data, tags_data) + + return instance + + def _update_categories_and_tags(self, instance, categories_data, tags_data): + # Update categories + updated_categories = [] + for category_data in categories_data: + category, _ = Category.objects.get_or_create(**category_data) + updated_categories.append(category) + instance.categories.set(updated_categories) + + # Update tags + updated_tags = [] + for tag_data in tags_data: + tag, _ = Tag.objects.get_or_create(**tag_data) + updated_tags.append(tag) + instance.tags.set(updated_tags) + + +class BlogCreateUpdateSerializer(serializers.ModelSerializer): + author = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all()) + categories = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(), many=True) + tags = serializers.PrimaryKeyRelatedField(queryset=Tag.objects.all(), many=True) + + class Meta: + model = Blog + fields = ['author', 'categories', 'tags', 'title', 'content'] + + def create(self, validated_data): + categories = validated_data.pop('categories') + tags = validated_data.pop('tags') + blog = Blog.objects.create(**validated_data) + blog.categories.set(categories) + blog.tags.set(tags) + return blog + + def update(self, instance, validated_data): + categories = validated_data.pop('categories', None) + tags = validated_data.pop('tags', None) + + for attr, value in validated_data.items(): + setattr(instance, attr, value) + + if categories is not None: + instance.categories.set(categories) + + if tags is not None: + instance.tags.set(tags) + + instance.save() + return instance diff --git a/apps/blog/serializers/category_serializer.py b/apps/blog/serializers/category_serializer.py new file mode 100644 index 0000000..cea16f9 --- /dev/null +++ b/apps/blog/serializers/category_serializer.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from apps.blog.models.category import Category + +class CategorySerializer(serializers.ModelSerializer): + class Meta: + model = Category + fields = ["name"] \ No newline at end of file diff --git a/apps/blog/serializers/image_serializer.py b/apps/blog/serializers/image_serializer.py new file mode 100644 index 0000000..484a88b --- /dev/null +++ b/apps/blog/serializers/image_serializer.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from apps.blog.models.image import Image + +class ImageSerializer(serializers.ModelSerializer): + class Meta: + model = Image + fields = ["image_file", "uploaded_at", "blog_post"] diff --git a/apps/blog/serializers/tag_serializer.py b/apps/blog/serializers/tag_serializer.py new file mode 100644 index 0000000..3111c07 --- /dev/null +++ b/apps/blog/serializers/tag_serializer.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from apps.blog.models.tag import Tag + +class TagSerializer(serializers.ModelSerializer): + class Meta: + model = Tag + fields = '__all__' diff --git a/apps/blog/signals.py b/apps/blog/signals.py new file mode 100644 index 0000000..dd186bb --- /dev/null +++ b/apps/blog/signals.py @@ -0,0 +1,10 @@ +from django.db.models.signals import pre_delete +from django.dispatch import receiver +from apps.blog.models.image import Image +import os + +@receiver(pre_delete, sender=Image) +def delete_image_file(sender, instance, **kwargs): + if instance.image_file: + if os.path.isfile(instance.image_file.path): + os.remove(instance.image_file.path) diff --git a/apps/blog/tasks.py b/apps/blog/tasks.py new file mode 100644 index 0000000..4f3bdbe --- /dev/null +++ b/apps/blog/tasks.py @@ -0,0 +1,7 @@ +from celery import shared_task +from apps.blog.models.image import Image + +@shared_task +def handle_image_upload(image_id): + image = Image.objects.get(id=image_id) + diff --git a/apps/blog/test/__init__.py b/apps/blog/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/blog/test/test_tag_model.py b/apps/blog/test/test_tag_model.py new file mode 100644 index 0000000..ebc982d --- /dev/null +++ b/apps/blog/test/test_tag_model.py @@ -0,0 +1,16 @@ + +from django.test import TestCase +from apps.blog.models.tag import Tag +from apps.blog.models.category import Category +from apps.blog.models.author import Author +from apps.blog.models.blog import Blog +from apps.blog.models.image import Image + +class TagModelTest(TestCase): + def setUp(self): + Tag.objects.create(name="Test Tag") + + def test_tag_creation(self): + tag = Tag.objects.get(name="Test Tag") + self.assertEqual(tag.name, "Test Tag") + diff --git a/apps/blog/views/__init__.py b/apps/blog/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/blog/views/author.py b/apps/blog/views/author.py new file mode 100644 index 0000000..70c7c82 --- /dev/null +++ b/apps/blog/views/author.py @@ -0,0 +1,7 @@ +from rest_framework import generics +from apps.blog.models.author import Author +from apps.blog.serializers.author_serializer import AuthorSerializer + +class AuthorListView(generics.ListCreateAPIView): + queryset = Author.objects.all() + serializer_class = AuthorSerializer diff --git a/apps/blog/views/blog.py b/apps/blog/views/blog.py new file mode 100644 index 0000000..461e24e --- /dev/null +++ b/apps/blog/views/blog.py @@ -0,0 +1,17 @@ +from rest_framework import generics, permissions +from apps.blog.models.blog import Blog +from apps.blog.serializers.blog_serializer import BlogSerializer, BlogCreateUpdateSerializer + + +class BlogListCreateView(generics.ListCreateAPIView): + queryset = Blog.objects.all() + serializer_class = BlogCreateUpdateSerializer + + def get_permissions(self): + if self.request.method == 'POST': + self.permission_classes = [permissions.IsAuthenticated] + return super().get_permissions() + +class BlogDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Blog.objects.all() + serializer_class = BlogSerializer diff --git a/apps/blog/views/category.py b/apps/blog/views/category.py new file mode 100644 index 0000000..2c22d00 --- /dev/null +++ b/apps/blog/views/category.py @@ -0,0 +1,7 @@ +from rest_framework import generics +from apps.blog.models.category import Category +from apps.blog.serializers.category_serializer import CategorySerializer + +class CategoryListView(generics.ListCreateAPIView): + queryset = Category.objects.all() + serializer_class = CategorySerializer diff --git a/apps/blog/views/image.py b/apps/blog/views/image.py new file mode 100644 index 0000000..fafccb5 --- /dev/null +++ b/apps/blog/views/image.py @@ -0,0 +1,17 @@ +from rest_framework import generics +from apps.blog.models.image import Image +from apps.blog.serializers.image_serializer import ImageSerializer +from apps.blog.tasks import handle_image_upload + + +class ImageListView(generics.ListCreateAPIView): + queryset = Image.objects.all() + serializer_class = ImageSerializer + +class ImageCreateView(generics.CreateAPIView): + queryset = Image.objects.all() + serializer_class = ImageSerializer + + def perform_create(self, serializer): + instance = serializer.save() + handle_image_upload.delay(instance.id) diff --git a/apps/blog/views/index.py b/apps/blog/views/index.py new file mode 100644 index 0000000..bf607f4 --- /dev/null +++ b/apps/blog/views/index.py @@ -0,0 +1,5 @@ +from django.http import HttpResponse + + +def index(request): + return HttpResponse("Hello, world! This is the django cameroon page.") diff --git a/apps/blog/views/post.py b/apps/blog/views/post.py new file mode 100644 index 0000000..1608631 --- /dev/null +++ b/apps/blog/views/post.py @@ -0,0 +1,64 @@ +from rest_framework import generics, permissions +from apps.blog.models.blog import Blog +from apps.blog.serializers.blog_serializer import BlogSerializer, BlogCreateUpdateSerializer +from drf_yasg.utils import swagger_auto_schema +from drf_yasg import openapi + + +class PostList(generics.ListCreateAPIView): + queryset = Blog.objects.all() + + def get_serializer_class(self): + if self.request.method == 'POST': + return BlogCreateUpdateSerializer + return BlogSerializer + + def get_permissions(self): + if self.request.method == 'POST': + self.permission_classes = [permissions.IsAuthenticated] + return super().get_permissions() + + @swagger_auto_schema( + operation_description="Retrieve a list of blog posts", + responses={200: BlogSerializer(many=True)} + ) + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + + @swagger_auto_schema( + operation_description="Create a new blog post", + request_body=BlogCreateUpdateSerializer, + responses={201: BlogSerializer} + ) + def post(self, request, *args, **kwargs): + return super().post(request, *args, **kwargs) + +class PostDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Blog.objects.all() + + def get_serializer_class(self): + if self.request.method in ['PUT', 'PATCH']: + return BlogCreateUpdateSerializer + return BlogSerializer + + @swagger_auto_schema( + operation_description="Retrieve a blog post", + responses={200: BlogSerializer} + ) + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + + @swagger_auto_schema( + operation_description="Update a blog post", + request_body=BlogCreateUpdateSerializer, + responses={200: BlogSerializer} + ) + def put(self, request, *args, **kwargs): + return super().put(request, *args, **kwargs) + + @swagger_auto_schema( + operation_description="Delete a blog post", + responses={204: openapi.Response(description="No Content")} + ) + def delete(self, request, *args, **kwargs): + return super().delete(request, *args, **kwargs) diff --git a/apps/blog/views/tag.py b/apps/blog/views/tag.py new file mode 100644 index 0000000..94f2bbc --- /dev/null +++ b/apps/blog/views/tag.py @@ -0,0 +1,7 @@ +from rest_framework import generics +from apps.blog.models.tag import Tag +from apps.blog.serializers.tag_serializer import TagSerializer + +class TagListView(generics.ListCreateAPIView): + queryset = Tag.objects.all() + serializer_class = TagSerializer \ No newline at end of file diff --git a/apps/events/test/__init__.py b/apps/events/test/__init__.py index 971b955..46b134b 100644 --- a/apps/events/test/__init__.py +++ b/apps/events/test/__init__.py @@ -1,2 +1 @@ -ÿþ - +ÿþ \ No newline at end of file diff --git a/apps/users/test/__init__.py b/apps/users/test/__init__.py index 971b955..46b134b 100644 --- a/apps/users/test/__init__.py +++ b/apps/users/test/__init__.py @@ -1,2 +1 @@ -ÿþ - +ÿþ \ No newline at end of file diff --git a/documentation/main.md b/documentation/main.md index 568ec97..118efdb 100644 --- a/documentation/main.md +++ b/documentation/main.md @@ -1 +1 @@ -The Django Cameroon main website API. \ No newline at end of file +The Django Cameroon main website API. diff --git a/website_api/routes/main.py b/website_api/routes/main.py index 37b0c94..ac06922 100644 --- a/website_api/routes/main.py +++ b/website_api/routes/main.py @@ -8,16 +8,17 @@ BASE_API_URL = "api/v1" + urlpatterns = ( - [ - path("admin/", admin.site.urls), + [ path("admin/", admin.site.urls), path('__debug__/', include(debug_toolbar.urls)), path(f"{BASE_API_URL}/", include("apps.users.routes.api")), path(f"{BASE_API_URL}/", include("apps.events.routes.api")), path(f"{BASE_API_URL}/", include("apps.events.routes.extra")), - ] + swagger_urlpatterns - + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) - + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + path(f"{BASE_API_URL}/", include("apps.blog.routes.api")), + ] + swagger_urlpatterns + + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ) handler404 = "apps.users.views.index.page_not_found_view" diff --git a/website_api/settings/apps.py b/website_api/settings/apps.py index ba09a04..ac2bb77 100644 --- a/website_api/settings/apps.py +++ b/website_api/settings/apps.py @@ -10,6 +10,7 @@ CUSTOM_APPS = [ "apps.users", "apps.events", + "apps.blog", ] # ---------------------- some extra stuff ------------------------------------- # diff --git a/website_api/settings/base.py b/website_api/settings/base.py index cc594aa..7f2c562 100644 --- a/website_api/settings/base.py +++ b/website_api/settings/base.py @@ -21,6 +21,8 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + 'rest_framework.authtoken', + ] INSTALLED_APPS += THIRD_PARTY_APPS @@ -140,3 +142,8 @@ # Custom user model AUTH_USER_MODEL = "users.User" + + +CELERY_BROKER_URL = 'redis://localhost:6379/0' +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' + diff --git a/website_api/settings/blog.py b/website_api/settings/blog.py new file mode 100644 index 0000000..e69de29 diff --git a/website_api/settings/celery.py b/website_api/settings/celery.py new file mode 100644 index 0000000..b39095f --- /dev/null +++ b/website_api/settings/celery.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import, unicode_literals +import os +from celery import Celery + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'website_api.settings') + +app = Celery('website_api') + +app.config_from_object('django.conf:settings', namespace='CELERY') + +app.autodiscover_tasks()