# 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
β 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.
π» 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')
π» 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
π» 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
πΉ 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.
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"),
]
python manage.py migrate
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!
πΉ 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.
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
from django.urls import include
urlpatterns = [
# ...
path('__debug__/', include('debug_toolbar.urls')),
]
MIDDLEWARE = [
"debug_toolbar.middleware.DebugToolbarMiddleware",
# ...
]
INTERNAL_IPS = [
"127.0.0.1",
]
π» 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
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
path('auth/', include('user.urls'))
β Create "api" folder under "blog" App. π Then create "urls.py", "serializers.py" and "views.py" files under "api" folder π
from django.urls import path, include
urlpatterns = [
path('auth/', include('dj_rest_auth.urls')),
]
python manage.py migrate
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)
python manage.py migrate
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)
from django.contrib import admin
from .models import User
admin.site.register(User)
AUTH_USER_MODEL = 'user.User'
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.
πΉ 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.
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)
def ready(self):
import user.signals
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)
from django.urls import path, include
from .views import RegisterView
urlpatterns = [
path('', include('dj_rest_auth.urls')),
path('register/', RegisterView.as_view())
]
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
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)
from users.api.views import RegisterView
path('register/', RegisterView.as_view()),
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",
)
class UpdateUserView(generics.RetrieveUpdateAPIView):
#! We used RetrieveUpdateAPIView so that the user can only update. π
queryset = User.objects.all()
serializer_class = UpdateUserSerializer
path('update-profile/<int:pk>', UpdateUserView.as_view()),
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)
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)
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())
import uuid
#! π user uniqe id => Slug field must be uniqe
def get_random_code():
code = str(uuid.uuid4())[:11].replace("-","")
return code
from django.apps import AppConfig
class BlogConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "blog"
def ready(self):
import blog.api.signals
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()
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)
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
from rest_framework.pagination import LimitOffsetPagination
#! For 6 posts to appear on each page π
class CustomLimitOffsetPagination(LimitOffsetPagination):
default_limit = 6
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
queryset = BlogPost.objects.filter(status="p")
path("all-posts/", UserAllPosts.as_view()),
π’ Do not forget to check the endpoints you wrote in Postman.
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
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)
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()
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)
π 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.
pip install django-cors-headers
MIDDLEWARE = [
...,
"corsheaders.middleware.CorsMiddleware",
...,
]
CORS_ALLOW_ALL_ORIGINS=True
CORS_ALLOW_METHODS = [
"DELETE",
"GET",
"OPTIONS",
"PATCH",
"POST",
"PUT",
]
python manage.py runserver
yarn start