Skip to content

Latest commit

Β 

History

History
1174 lines (897 loc) Β· 33.2 KB

README.md

File metadata and controls

1174 lines (897 loc) Β· 33.2 KB

✈ FULLSTACK-PROJECT-BLOG-APP ✈

πŸ’» BACKEND πŸ’»

πŸš€ INITIAL SETUP

# CREATING VIRTUAL ENVIRONMENT
# windows πŸ‘‡
python -m venv env
# linux / Mac OS πŸ‘‡
vitualenv env

# ACTIVATING ENVIRONMENT
# windows πŸ‘‡
source env/Scripts/activate
# linux / Mac OS πŸ‘‡
source env/bin/activate

# PACKAGE INSTALLATION
# if pip does not work try pip3 in linux/Mac OS
pip install djangorestframework
pip freeze > requirements.txt
django-admin startproject main .
# alternatively python -m pip install django
pip install python-decouple
django-admin --version
# πŸ’¨ If you already have a requirement.txt file, you can install the packages in the file
# πŸ’¨ by entering the following commands respectively in the terminal πŸ‘‡
1-python -m venv env
2-source env/Scripts/activate
3-pip install -r requirements.txt πŸš€
4-python.exe -m pip install --upgrade pip
5-python manage.py migrate
6-python manage.py createsuperuser
7-python manage.py runserver

πŸ›‘ Secure your project

🚩 .gitignore

βœ” Add a ".gitignore" file at same level as env folder, and check that it includes ".env" and /env lines.

πŸ”Ή Do that before adding your files to staging area, else you will need extra work to unstage files to be able to ignore them.

πŸ”Ή On this page you can create "gitignore files" for your projects.

🚩 Python Decouple

πŸ’» To use python decouple in this project, first install it πŸ‘‡

pip install python-decouple

πŸ’» Go to terminal to update "requirements.txt" πŸ‘‡

pip freeze > requirements.txt

βœ” Create a new file and name as ".env" at same level as env folder

βœ” Copy your SECRET_KEY from settings.py into this .env file. Don't forget to remove quotation marks and blanks from SECRET_KEY

SECRET_KEY=-)=b-%-w+0_^slb(exmy*mfiaj&wz6_fb4m&s=az-zs!1^ui7j

βœ” Go to "settings.py", make amendments below πŸ‘‡

from decouple import config

SECRET_KEY = config('SECRET_KEY')

πŸ’» INSTALLING DJANGO REST

πŸ’» Go to terminal πŸ‘‡

python manage.py makemigrations
python manage.py migrate
pip install djangorestframework

βœ” Go to "settings.py" and add 'rest_framework' app to INSTALLED_APPS

πŸ’» PostgreSQL Setup

πŸ’» To get Python working with Postgres, you will need to install the β€œpsycopg2” moduleπŸ‘‡

pip install psycopg2

πŸ’» Go to terminal to update requirements.txt πŸ‘‡

pip freeze > requirements.txt

βœ” Go to settings.py and add '' app to INSTALLED_APPS

🚩 Install Swagger

πŸ”Ή Explain a sample API reference documentation

πŸ”Ή Swagger is an open source project launched by a startup in 2010. The goal is to implement a framework that will allow developers to document and design APIs, while maintaining synchronization with the code.

πŸ”Ή Developing an API requires orderly and understandable documentation.

πŸ”Ή To document and design APIs with Django rest framework we will use drf-yasg which generate real Swagger/Open-API 2.0 specifications from a Django Rest Framework API.

πŸ“œ You can find the documentation here.

πŸ’» Go to terminal for installation πŸ‘‡

pip install drf-yasg

πŸ’» Go to terminal to update requirements.txt πŸ‘‡

pip freeze > requirements.txt

βœ” Go to "settings.py" and add 'drf_yasg' app to INSTALLED_APPS

βœ” Here is the updated "urls.py" file for swagger. In swagger documentation, those patterns are not up-to-date πŸ‘‡

from django.contrib import admin
from django.urls import path
# Three modules for swagger:
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
    openapi.Info(
        title="Flight Reservation API",
        default_version="v1",
        description="Flight Reservation API project provides flight and reservation info",
        terms_of_service="#",
        contact=openapi.Contact(
            email="rafe@clarusway.com"),  # Change e-mail on this line!
        license=openapi.License(name="BSD License"),
    ),
    public=True,
    permission_classes=[permissions.AllowAny],
)
urlpatterns = [
    path("admin/", admin.site.urls),
    # Url paths for swagger:
    path("swagger(<format>\.json|\.yaml)",
         schema_view.without_ui(cache_timeout=0), name="schema-json"),
    path("swagger/", schema_view.with_ui("swagger", cache_timeout=0),
         name="schema-swagger-ui"),
    path("redoc/", schema_view.with_ui("redoc",
         cache_timeout=0), name="schemaredoc"),
]

πŸ’» MIGRATE πŸ‘‡

python manage.py migrate

πŸš€ RUNSERVER πŸ‘‡

python manage.py runserver

πŸ”΄ If you have this problem πŸ‘‰ ( return Database.Cursor.execute(self, query, params) sqlite3.OperationalError:) when you create "superuser" you should write this command πŸ‘‡

python manage.py migrate --run-syncdb

βœ” After running the server, go to swagger page and redoc page of your project!

🚩 INSTALL DEBUG TOOLBAR πŸ‘‡

πŸ”Ή The Django Debug Toolbar is a configurable set of panels that display various debug information about the current request/response and when clicked, display more details about the panel’s content.

πŸ“œ See the Django Debug Toolbar documentation page.

πŸ’» For Installation go to terminal πŸ‘‡

pip install django-debug-toolbar

πŸ’» Go to terminal to update "requirements.txt" πŸ‘‡

pip freeze > requirements.txt

βœ” Go to "settings.py" and add 'debug_toolbar' app to INSTALLED_APPS

🚩 Add django-debug-toolbar’s URLs to your project’s URLconf πŸ‘‡

from django.urls import include
urlpatterns = [
# ...
path('__debug__/', include('debug_toolbar.urls')),
]

🚩 Add the middleware to the top πŸ‘‡

MIDDLEWARE = [
"debug_toolbar.middleware.DebugToolbarMiddleware",
# ...
]

🚩 Add configuration of internal IPs to "settings.py" πŸ‘‡

INTERNAL_IPS = [
    "127.0.0.1",
]

🚩 ADDING AN APP

πŸ’» Go to terminal πŸ‘‡

python manage.py startapp blog

βœ” Go to "settings.py" and add 'blog' app to "INSTALLED_APPS"

πŸ’» INSTALL DJ-REST-AUTH

pip install dj-rest-auth

πŸ’» Go to terminal to update "requirements.txt" πŸ‘‡

pip freeze > requirements.txt

🚩 Add "dj_rest_auth" app to "INSTALLED_APPS" in your django "base.py" πŸ‘‡

    'rest_framework',
    'rest_framework.authtoken',
    'dj_rest_auth',

🚩 Go to "main/urls.py" and add the path πŸ‘‡

path('auth/', include('user.urls'))

βœ” Create "api" folder under "blog" App. πŸ‘‰ Then create "urls.py", "serializers.py" and "views.py" files under "api" folder πŸ‘‡

🚩 Go to "users/urls.py" and add πŸ‘‡

from django.urls import path, include

urlpatterns = [
    path('auth/', include('dj_rest_auth.urls')),
]

πŸ’» Migrate your database

python manage.py migrate

🚩 Start Models in "Blog" app πŸ‘‡

from django.db import models
from django.conf import settings

User = settings.AUTH_USER_MODEL

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class BlogPost(models.Model):
    STATUS = (
        ("d", "DRAFT"),
        ("p", "PUBLISHED"),
    )
    title = models.CharField(max_length=100)
    author = models.ForeignKey(User, related_name="post_user", on_delete=models.CASCADE)
    category = models.ForeignKey(Category, related_name="post_category", on_delete=models.CASCADE)
    content = models.TextField()
    image = models.URLField(max_length=200, blank=True, default="https://robohash.org/9c681a48b0ef374675df3ca8d6b014a5?set=set4&bgset=&size=400x400")
    published_date = models.DateTimeField(auto_now_add=True, blank=True)
    last_updated_date = models.DateTimeField(auto_now=False, blank=True)
    status = models.CharField(max_length=50, choises=STATUS)
    #! We use slug for the fields we want to appear instead of ID πŸ‘‡
    slug = models.SlugField()

    def __str__(self):
        return self.title

class Like(models.Model):
    user = models.ForeignKey(User, related_name="like_user", on_delete=models.CASCADE)
    post = models.ForeignKey(BlogPost, related_name="like_post", on_delete=models.CASCADE)

    def __str__(self):
        return self.user.username

class Comment(models.Model):
    content = models.TextField()
    time_stamp = models.DateTimeField(auto_now_add=True, blank=True)
    user = models.ForeignKey(User, related_name="comment_user", on_delete=models.CASCADE)
    post = models.ForeignKey(BlogPost, related_name="comment_post", on_delete=models.CASCADE)

    def __str__(self):
        return self.user.username

class Post_view(models.Model):
    user = models.ForeignKey(User, related_name="post_viewed_user", on_delete=models.CASCADE)
    post = models.ForeignKey(BlogPost, related_name="viewed_post", on_delete=models.CASCADE)
    viewed_date_time = models.DateTimeField(auto_now_add=True, blank=True)

πŸ’» Migrate your database πŸ‘‡

python manage.py migrate

🚩 Create "user" app and add "INSTALLED_APP"

🚩 Go to "models.py" in "user" app and add πŸ‘‡

from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    image = models.URLField(max_length=200, blank=True)
    bio = models.TextField(blank=True)

🚩 Register the model in "admin.py" πŸ‘‡

from django.contrib import admin
from .models import User

admin.site.register(User)

🚩 Go "main/settings.py" and add πŸ‘‡

AUTH_USER_MODEL = 'user.User'

🚩 Create "serializers.py" file under "user" app and add πŸ‘‡

from rest_framework import serializers, validators
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from dj_rest_auth.serializers import TokenSerializer

User = get_user_model()
class RegisterSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(
        required=True,
        validators=[validators.UniqueValidator(queryset=User.objects.all())]
    )
    password = serializers.CharField(
        write_only=True,
        required=True,
        validators=[validate_password],
        style={"input_type": "password"}
    )
    password1 = serializers.CharField(
        write_only=True,
        required=True,
        validators=[validate_password],
        style={"input_type": "password"}
    )
    class Meta:
        model = User
        fields = (
            'username',
            'email',
            'first_name',
            'last_name',
            'password',
            'password1',
            'image',
            'bio'
        )
    def validate(self, data):
        if data['password'] != data['password1']:
            raise serializers.ValidationError(
                {"password": "Password didn't match..... "}
            )
        return data
    def create(self, validated_data):
        password = validated_data.pop("password")
        validated_data.pop('password1')
        user = User.objects.create(**validated_data)
        user.set_password(password)
        user.save()
        return user
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = (
            'username',
            'email'
        )
class CustomTokenSerializer(TokenSerializer):
    user = UserSerializer(read_only=True)

    class Meta(TokenSerializer.Meta):
        fields = (
            'key',
            'user'
        )

πŸ”΄ SIGNALS πŸ‘‡

πŸ”Ή Django include a β€œsignal dispatcher” which helps decoupled applications get notified when actions occur elsewhere in the framework.

πŸ”Ή In nutshell, signals allow certain senders to notify a set of receivers that some action has taken place.

πŸ”Ή They’re especially useful when many pieces of code may be interested in the same events.

πŸ”΄ Listening to signals πŸ‘‰ Parameters:

πŸ”Ή receiver: The callback function which will be connected to this signal. See Receiver functions for more information.

πŸ”Ή sender: Specifies a particular sender to receive signals from. See Connecting to signals sent by specific senders for more information.

πŸ”Ή weak: Django stores signal handlers as weak references by default. Thus, if your receiver is a local function, it may be garbage collected. To prevent this, pass weak=False when you call the signal’s connect() method.

πŸ”Ή dispatch_uid: A unique identifier for a signal receiver in cases where duplicate signals may be sent. See Preventing duplicate signals for more information.

🚩 Create "signals.py" file under "user" app and add πŸ‘‡

from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model

User = get_user_model()

@receiver(post_save, sender=User)
def create_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

🚩 Go to "apps.py" and add this under UsersConfig() πŸ‘‡

def ready(self):
    import user.signals

🚩 Go to "views.py" and create RegisterView() πŸ‘‡

from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from .serializers import RegisterSerializer
from django.contrib.auth import get_user_model

User = get_user_model()

class RegisterView(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = RegisterSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        data = serializer.data
        if Token.objects.filter(user=user).exists():
            token = Token.objects.get(user=user)
            data['token'] = token.key
        else:
            data['error'] = 'User dont have token. Please login'
        headers = self.get_success_headers(serializer.data)
        return Response(data, status=status.HTTP_201_CREATED, headers=headers)

🚩 Create "urls.py" file under "user" app and add πŸ‘‡

from django.urls import path, include
from .views import RegisterView

urlpatterns = [
    path('', include('dj_rest_auth.urls')),
    path('register/', RegisterView.as_view())
]

βœ” Create "serializers.py" file under "User" app and add πŸ‘‡

from rest_framework import serializers, validators
from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password

class RegisterSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(
        required = True,
        validators=[validators.UniqueValidator(queryset=User.objects.all())]
    )
    password = serializers.CharField(
        required = True,
        write_only = True,
        validators = [validate_password],
        style = {"input_type":"password"}
    )
    password1 = serializers.CharField(
        required = True,
        write_only = True,
        validators = [validate_password],
        style = {"input_type":"password"}
    )
    class Meta:
        model = User
        fields = (
            "username",
            "email",
            "first_name",
            "last_name",
            "password",
            "password1",
        )
    def validate(self, data):
        if data["password"] != data["password1"]:
            raise serializers.ValidationError(
                {"password": "Password must be same with above !..."}
            )
        return data

    def create(self, validated_data):
        password = validated_data.pop("password")
        validated_data.pop("password1")
        user = User.objects.create(**validated_data)
        user.set_password(password)
        user.save()
        return user

🚩 Go to "user/api/views.py" and create RegisterView() πŸ‘‡

from rest_framework import generics
from django.contrib.auth.models import User
from .serializers import RegisterSerializer

class RegisterView(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = RegisterSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        data = serializer.data
        if Token.objects.filter(user=user):
            token = Token.objects.get(user=user)
            data['token'] = token.key
        else:
            data['error'] = 'User does not have token . Try again ...'
        headers = self.get_success_headers(serializer.data)
        return Response(data, status=status.HTTP_201_CREATED, headers=headers)

🚩 Go to "urls.py" and add the path πŸ‘‡

from users.api.views import RegisterView

path('register/', RegisterView.as_view()),

🚩 Go to "user/api/serializers.py" and add UpdateUserSerializerπŸ‘‡

class UpdateUserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(
        required=True,
        validators=[validators.UniqueValidator(queryset=User.objects.all())]
    )

    class Meta:
        model = User
        fields = (
            "username",
            "id",
            "email",
            "first_name",
            "last_name",
            "profile_pic",
            "biography",
        )

🚩 Go to user/api/views.py and add "UpdateUserView" πŸ‘‡

class UpdateUserView(generics.RetrieveUpdateAPIView):
    #! We used RetrieveUpdateAPIView so that the user can only update. πŸ‘†
    queryset = User.objects.all()
    serializer_class = UpdateUserSerializer

πŸ‘‡ Go to "user/api/urls.py" and add the path πŸ‘‡

path('update-profile/<int:pk>', UpdateUserView.as_view()),

✏ Create "api" folder under "blog" app and add the "views, serializers, urls" files.

🚩 Create models in "models.py" under "blog" app πŸ‘‡

from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.db import models
from django.conf import settings
from django.template.defaultfilters import slugify
from blog.api.utils import get_random_code

User = settings.AUTH_USER_MODEL

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class BlogPost(models.Model):
    STATUS = (
        ("d", "DRAFT"),
        ("p", "PUBLISHED"),
    )
    title = models.CharField(max_length=100)
    author = models.ForeignKey(
        User, related_name="post_user", on_delete=models.PROTECT, default='Anonymous User')
    category = models.ForeignKey(
        Category, related_name="post_category", on_delete=models.CASCADE)
    content = models.TextField()
    # image = models.ImageField(upload_to=None, height_field=None, width_field=None, max_length=None)
    image = models.URLField(max_length=200, blank=True,
                            default="https://gravatar.com/avatar/2074b7945e3c6c493b0b2b94b24c35c2?s=400&d=robohash&r=x")
    published_date = models.DateTimeField(auto_now_add=True, blank=True)
    last_updated_date = models.DateTimeField(auto_now=True, blank=True)
    status = models.CharField(max_length=2, choices=STATUS)
    slug = models.SlugField(blank=True, null=True)

    def __str__(self):
        return self.title

class Like(models.Model):
    user = models.ForeignKey(
        User, related_name="like_user", on_delete=models.PROTECT)
    post = models.ForeignKey(
        BlogPost, related_name="like_post", on_delete=models.CASCADE)

    def __str__(self):
        return self.user

class Comment(models.Model):
    content = models.TextField()
    time_stamp = models.DateTimeField(auto_now_add=True, blank=True)
    user = models.ForeignKey(User, related_name="comment_user",
                             on_delete=models.PROTECT, default='Anonymous User')
    post = models.ForeignKey(
        BlogPost, related_name="comment_post", on_delete=models.CASCADE)

    def __str__(self):
        return self.user

class Post_view(models.Model):
    user = models.ForeignKey(
        User, related_name="post_viewed_user", on_delete=models.PROTECT)
    post = models.ForeignKey(
        BlogPost, related_name="viewed_post", on_delete=models.CASCADE)
    viewed_date_time = models.DateTimeField(auto_now_add=True, blank=True)

🚩 Register the models in "admin.py" πŸ‘‡

from django.contrib import admin

from blog.models import BlogPost, Category, Comment, Like, Post_view

admin.site.register(Category)
admin.site.register(Like)
admin.site.register(BlogPost)
admin.site.register(Comment)
admin.site.register(Post_view)

🚩 Create "signals.py" file under "blog/api" folder and add πŸ‘‡

from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.template.defaultfilters import slugify
from blog.models import BlogPost
from .utils import get_random_code

@receiver(pre_save, sender=BlogPost)
def pre_save_create_slug(sender, instance,**kwargs):
    if not instance.slug:
        instance.slug = slugify(instance.title + " " + get_random_code())

🚩 Create "utils.py" file under "blog/api" folder and add πŸ‘‡

import uuid
#! πŸ‘† user uniqe id => Slug field must be uniqe

def get_random_code():
    code = str(uuid.uuid4())[:11].replace("-","")
    return code

🚩 Customize the BlogConfig() in "apps.py" in "blog"app πŸ‘‡

from django.apps import AppConfig

class BlogConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "blog"

    def ready(self):
        import blog.api.signals

🚩 Go to "api/serializers.py" under "blog" app πŸ‘‡

from rest_framework import serializers
from blog.models import BlogPost, Category, Comment, Like, Post_view
from users.api.serializers import UserSerializer
from django.contrib.auth import get_user_model
# User = settings.AUTH_USER_MODEL
User = get_user_model()

class CategorySerializer(serializers.ModelSerializer):

    class Meta:
        model = Category
        fields = (
            'id',
            'name'
        )


class CommentSerializer(serializers.ModelSerializer):

#! added to indicate who commented
    user = serializers.StringRelatedField(read_only=True)
    class Meta:
        model = Comment
        fields = (
            "id",
            "content",
            "time_stamp",
            "user",
        )

class LikeSerializer(serializers.ModelSerializer):
    # like_user = AllUserSerializer(many=True, read_only=True)
    user = serializers.StringRelatedField()
    user_id = serializers.IntegerField()

    class Meta:
        model = Like
        fields = (
            "id",
            "user",
            "user_id",
            "post",
            # "like_user"
        )


class BlogPostSerializer(serializers.ModelSerializer):
    comment_post = CommentSerializer(many=True, read_only=True)
    like_post = LikeSerializer(many=True, read_only=True)
    category = serializers.StringRelatedField(read_only=True)
    category_id = serializers.IntegerField()
    author = serializers.StringRelatedField(read_only=True)
    author_id = serializers.IntegerField(read_only=True)
    like_count = serializers.SerializerMethodField()
    comment_count = serializers.SerializerMethodField()
    post_view_count = serializers.SerializerMethodField()

    class Meta:
        model = BlogPost
        fields = (
            "id",
            "title",
            "author",
            "author_id",
            "category_id",
            "category",
            "content",
            "image",
            "published_date",
            "last_updated_date",
            "status",
            "slug",
            "like_count",
            "comment_count",
            "post_view_count",
            "comment_post",
            "like_post",
        )
        read_only_fields = (
            "published_date",
            "updated_date",
            "slug",
        )

    def get_like_count(self, obj):
        return Like.objects.filter(post=obj.id).count()

    def get_comment_count(self, obj):
        return Comment.objects.filter(post=obj.id).count()

    def get_post_view_count(self, obj):
        return Post_view.objects.filter(post=obj.id).count()

🚩 Go to "api/views.py" under blog app πŸ‘‡

from rest_framework import permissions
from rest_framework.exceptions import ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework import  generics, status
from blog.api.pagination import CustomLimitOffsetPagination
from blog.api.permissions import IsAdminUserOrReadOnly, IsPostOwnerOrReadOnly
from blog.api.serializers import BlogPostSerializer, CategorySerializer, CommentSerializer,LikeSerializer
from rest_framework.response import Response
from blog.models import BlogPost, Category, Post_view, Comment, Like


class CategoryView(generics.ListCreateAPIView):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    permission_classes = [IsAdminUserOrReadOnly]


class BlogPostView(generics.ListCreateAPIView):
    queryset = BlogPost.objects.all()
    serializer_class = BlogPostSerializer
    pagination_class = CustomLimitOffsetPagination
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

class BlogPostDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = BlogPost.objects.all()
    serializer_class = BlogPostSerializer
    lookup_field = "slug"
    permission_classes = [IsPostOwnerOrReadOnly]

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        # Post_view.objects.get_or_create(user=request.user, post=instance)
        Post_view.objects.create(user=request.user, post=instance)
        return Response(serializer.data)

class CommentView(generics.CreateAPIView):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        print(self.kwargs)
        slug = self.kwargs.get('slug')
        blog = get_object_or_404(BlogPost, slug=slug)
        user = self.request.user
        comments = Comment.objects.filter(post=blog, user=user)
        if comments.exists():
            raise ValidationError(
                "You can not add another comment, for this Post !")
        serializer.save(post=blog, user=user)


class LikeView(generics.ListCreateAPIView):
    queryset = Like.objects.all()
    serializer_class = LikeSerializer

    def create(self, request, *args, **kwargs):
        user = request.data.get('user_id')
        post = request.data.get('post')
        serializer = self.get_serializer(data=request.data)
        exists_like = Like.objects.filter(user_id=user, post=post)
        serializer.is_valid(raise_exception=True)
        if exists_like:
            exists_like.delete()
        else:
            self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

🚩 Create "permissions.py" file under "api" folder in the "blog" app and add πŸ‘‡

from rest_framework import permissions

class IsPostOwnerOrReadOnly(permissions.BasePermission):
    #! If the request.user is the same as the author, it can update/delete. Otherwise can view πŸ‘‡
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user == obj.author

class IsAdminUserOrReadOnly(permissions.IsAdminUser):
    #! "Admin" can do any action. If not it can only view πŸ‘‡
    def has_permission(self, request, view):
        is_admin = super().has_permission(request, view)
        return request.method in permissions.SAFE_METHODS or is_admin

🚩 Create "pagination.py" file under "api" folder in the "blog" app and add πŸ‘‡

from rest_framework.pagination import LimitOffsetPagination

#! For 6 posts to appear on each page πŸ‘‡
class CustomLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 6

🚩 Go to "blog/api/urls.py" and add the path πŸ‘‡

from .views import (
    CategoryView,
    BlogPostView,
    BlogPostDetailView,
    CommentView,
    LikeView
)
from django.urls import path
from rest_framework import routers

urlpatterns = [
    path("category/", CategoryView.as_view()),
    path("posts/", BlogPostView.as_view()),
    path("like/", LikeView.as_view()),
    path("posts/<str:slug>/", BlogPostDetailView.as_view()),
    path("posts/<str:slug>/add_comment/", CommentView.as_view()),
]

🚩 Create "userAllPost" so that a user can see their own created posts in one place in "blog/api/views.py" πŸ‘‡

class UserAllPosts(generics.ListAPIView):
    queryset = BlogPost.objects.all()
    serializer_class = BlogPostSerializer
    permission_classes = [IsPostOwnerOrReadOnly]

    def get_queryset(self):
        author = self.request.user
        queryset = BlogPost.objects.filter(author=author)
        return queryset

🚩 Update the BlogPostView()'s "queryset" πŸ‘‡

    queryset = BlogPost.objects.filter(status="p")

🚩 Add "all-posts" path to "urls.py" in "blog/api/" πŸ‘‡

    path("all-posts/", UserAllPosts.as_view()),

πŸ“’ Do not forget to check the endpoints you wrote in Postman.

πŸ₯³ END OF THE BACKEND πŸ₯³


πŸ“’ FOR DJANGO DEPLOYMENT YOU CAN USE "PYTHON ANY WHERE"

πŸ’» Commands for setup πŸ‘‡

    git clone https://github.com/githubUserName/projectName.git
    cd projectName
    python -m venv env
    source env/bin/activate
    pip install --upgrade pip
    pip install -r requirements.txt
    echo SECRET_KEY=write_random_chars_to_here > .env
    python manage.py migrate
    $ python manage.py createsuperuser # optional

πŸ’» Command for learn to current path πŸ‘‡

    pwd
  • "Add New Web App" with ManualConfigration with Python_LastVersion

  • Set "Source Code" with "Main Path" (example: /home/anyWhereUserName/ProjectName)

  • Set "Working Directory" with "Main Path" (example: /home/anyWhereUserName/ProjectName)

  • Set "VirtualEnv" with "Env Path" (example: /home/anyWhereUserName/ProjectName/env)

🚩 pythonanywhere/Web -> WSGI Configuration File(pythonanywhere_com_wsgi.py) πŸ‘‡

    import os
    import sys

    # Set: Project Main Path:
    path = '/home/anyWhereUserName/ProjectName'

    if path not in sys.path:
        sys.path.append(path)

    # Set: Where is settings.py:
    os.environ['DJANGO_SETTINGS_MODULE'] = 'projectFolderName.settings'

    from django.core.wsgi import get_wsgi_application
    application = get_wsgi_application()

πŸ‘ Finished 😎

β€Ό Don't forget πŸ‘‰ click to 'Reload' button before publish.

if error, checking:

    settting.py:

        ALLOWED_HOSTS = ['*']

        # folder -> static-files-path:
        STATIC_URL = 'static/'
        # root -> static-files-path:
        STATIC_ROOT = BASE_DIR / STATIC_URL
        # Alternates:
        # if in base folder -> STATIC_ROOT = BASE_DIR / 'static/'
        # if in app folder -> STATIC_ROOT = BASE_DIR / 'appFolderName/static/'

    urls.py:

        from django.conf import settings
        from django.conf.urls.static import static
        # url -> static-files-path:
        urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

FOR REACT CONFIGURATION

πŸ’» Install "cors-headers" for connecting with Frontend πŸ‘‡

βœ” DJANGO-CORS-HEADERS βœ”


πŸ”‘ A Django App that adds Cross-Origin Resource Sharing (CORS) headers to responses.

πŸ”‘ This allows in-browser requests to your Django application from other origins.

πŸ”‘ Adding CORS headers allows your resources to be accessed on other domains.

πŸ”‘ It's important you understand the implications before adding the headers, since you could be unintentionally opening up your site's private data to others.

πŸ’» To install cors-headers πŸ‘‡

pip install django-cors-headers

βœ” Add 'corsheaders' to "INSTALLED_APPS" in "settings.py" file.

🚩 You will also need to add a middleware class to listen in on responses πŸ‘‡

MIDDLEWARE = [
    ...,
    "corsheaders.middleware.CorsMiddleware",
    ...,
]

🚩 To allow all origins; add πŸ‘‡

CORS_ALLOW_ALL_ORIGINS=True

🚩 Add a list of HTTP verbs that are allowed for the actual request πŸ‘‡

CORS_ALLOW_METHODS = [
    "DELETE",
    "GET",
    "OPTIONS",
    "PATCH",
    "POST",
    "PUT",
]

πŸ’» Runserver πŸ‘‡

python manage.py runserver

πŸ’» Open the React Project and start it πŸ‘‡

yarn start