Django 910掲示板CRUD


> End Point
-会員入力:POST/user/signup
-ログイン:POST/users/signin
-投稿リスト:GET/bullet-board/post
-投稿の作成:POST/bullet-board/post
-投稿の変更:PATCH/bulletin-board/post/int:post id
-投稿の削除:DELETE/bulletin-board/post/int:post id
-投稿詳細:GET/bullet-board/post-detail/int:post id
🥕 github:
https://github.com/chrisYang256/wnated-wecode-FreeOnboarding
> users App
▶特別事項
会員登録とログインのユーザーのみが投稿の作成、変更、削除を許可します.
登録およびログオンレコーダによる認証機能が追加されました.
▶︎ code
- models.py
from django.db               import models

class User(models.Model):
    name       = models.CharField(max_length = 20)
    password   = models.CharField(max_length = 200)
    email      = models.EmailField(max_length = 50, unique = True)
    created_at = models.DateTimeField(auto_now_add = True)
    updated_at = models.DateTimeField(auto_now = True)
        
    class Meta:
        db_table = 'users'
- views.py
import json
import re
import bcrypt
import jwt

from django.http  import JsonResponse
from django.views import View

from my_settings import SECRET_KEY, ALGORITHMS
from users.models import User

class SignUp(View):
    def post(self, request):
        data          = json.loads(request.body)
        REGX_EMAIL    = '^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
        REGX_PASSWORD = '^(?=.*\d)(?=.*[a-zA-Z])[0-9a-zA-Z!@#$%^&*]{8,20}$'

        try:
            if User.objects.filter(email=data['email']).exists():
                return JsonResponse({'message': 'EXIST_EMAIL'}, status=400)

            if not re.match(REGX_EMAIL, data['email']):
                return JsonResponse({'message': 'INVALID_EMAIL_FORM'}, status=400)

            if not re.match(REGX_PASSWORD, data['password']):
                return JsonResponse({'message': 'INVALID_PASSWORD_FORM'}, status=400)

            password = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
            
            User.objects.create(
                name     = data['name'],
                email    = data['email'],
                password = password,
            )

            return JsonResponse({'message': 'SUCCESS'}, status=201)
        
        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)

class SignIn(View):
    def post(self, request):
        try:
            data = json.loads(request.body)
            user = User.objects.get(email = data['email'])

            if not (user and bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8'))):
                return JsonResponse({"message" : "INVALID_USER"}, status=401)

        except KeyError:
            return JsonResponse({"message" : "KEY_ERROR"}, status=400)

        access_token = jwt.encode({'user_id' : user.id}, SECRET_KEY, ALGORITHMS)

        return JsonResponse({'access_token' : access_token}, status=201)
- utils.py
import jwt

from django.http            import JsonResponse

from my_settings  import SECRET_KEY, ALGORITHMS
from users.models import User

def login_decorator(func):
    def wrapper(self, request, *args, **kwargs):
        if 'Authorization' not in request.headers: 
            return JsonResponse ({'message' : 'UNAUTHORIZED'}, status=401)

        access_token = request.headers.get('Authorization')
        
        try:
            payload      = jwt.decode(access_token, SECRET_KEY, algorithms=ALGORITHMS)
            user         = User.objects.get(id=payload['user_id'])
            request.user = user

        except jwt.exceptions.DecodeError:
            return JsonResponse({'MESSAGE': 'INVALID_TOKEN'}, status=401)

        except User.DoesNotExist:
            return JsonResponse({'MESSAGE': 'INVALID_USER'}, status=401)

        return func(self, request,  *args, **kwargs)

    return wrapper
- urls.py
from django.urls import path

from users.views import SignIn, SignUp

urlpatterns = [
    path('/signup', SignUp.as_view()),
    path('/signin', SignIn.as_view()),
]
▶︎ Divide & Conquer
- SignUp API
pythonのre.match()を使用して、正規表現と入力値を照合します.
異常処理を行いました.
- utils.py
login decorator関数をデザイナとして貼り付けたAPIは、タグチェック後にタグを復号します.
user idを要求します.認証とHTTP無状態をuserにダンプする
解決しました.
> bulletin_board App
▶特別事項
RESTful APIに準拠.
投稿を表示するほか、「すべて承認」に対して例外処理を行いました.
▶︎ code
- models.py
from django.db               import models

class BulletinBoard(models.Model):
    author      = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='bulletin_board')
    title       = models.CharField(max_length=100)
    description = models.CharField(max_length=5000)
    created_at  = models.DateTimeField(auto_now_add=True)
    updated_at  = models.DateTimeField(auto_now=True)
        
    class Meta:
        db_table = 'bulletin_boards'
- views.py
import json
import time
import datetime

from django.http  import JsonResponse
from django.views import View

from users.utils  import login_decorator
from .models      import BulletinBoard
from users.models import User
    
class Post(View):
    def get(self, request):
        limit  = int(request.GET.get('limit', 5))
        offset = int(request.GET.get('offset', 0))

        limit  = limit + offset

        post_list = BulletinBoard.objects.all()[offset:limit]

        results = [
            {
            'author'     : post.author.name if post.author else "사라진 회원입니다.",
            'title'      : post.title,
            'created_at' : post.created_at
        } for post in post_list]

        return JsonResponse({'results' : results}, status=200)

    @login_decorator
    def post(self, request):
        try:
            data = json.loads(request.body)

            if not User.objects.filter(id=request.user.id):
                return JsonResponse({'message' : 'INVALID_USER'}, status=404)

            BulletinBoard.objects.create(
                    author_id   = request.user.id,
                    title       = data['title'],
                    description = data['description'],
                    created_at  = data['created_at']
            )

            time.sleep(1)

            return JsonResponse({'message' : 'SUCCESS'}, status=201)

        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)

    @login_decorator
    def patch(self, request, post_id):
        try:
            data = json.loads(request.body)
            post = BulletinBoard.objects.get(id=post_id)

            if not post.author_id == request.user.id:
                return JsonResponse({'message' : 'INVALID_USER'}, status=404)

            post.title       = data['title']
            post.description = data['description']
            post.updated_at  = datetime.datetime.now()
            post.save()

            return JsonResponse({'message' : 'SUCCESS'}, status=200)

        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400) 

        except BulletinBoard.DoesNotExist:
                return JsonResponse({'message' : 'INVALID_POST'}, status=404)  

    @login_decorator
    def delete(self, request, post_id):
        try:
            post = BulletinBoard.objects.get(id = post_id)

            if not post.author_id == request.user.id:
                return JsonResponse({'message' : 'INVALID_USER'}, status=404)

            BulletinBoard.objects.get(id=post_id).delete()

            return JsonResponse({'message' : 'SUCCESS'}, status=200)

        except BulletinBoard.DoesNotExist:
                return JsonResponse({'message' : 'INVALID_POST'}, status=404)
            
class PostDetail(View):
    def get(self, request, post_id):
        try:
            post = BulletinBoard.objects.get(id=post_id)

            results = {
                'author'      : post.author.name if post.author else "사라진 회원입니다.",
                'title'       : post.title,
                'description' : post.description,
                'created_at'  : post.created_at,
                'updated_at'  : post.updated_at
            }

            return JsonResponse({'results' : results}, status=200)

        except BulletinBoard.DoesNotExist:
                return JsonResponse({'message' : 'INVALID_POST'}, status=404)
- urls.py
from django.urls import path

from .views import Post, PostDetail

urlpatterns = [
    path('/post', Post.as_view()),
    path('/post/<int:post_id>', Post.as_view()),
    path('/post-detail/<int:post_id>', PostDetail.as_view()),
]
▶︎ Divide & Conquer
- models.pyのBulletinBoarテーブルon_delete=models.SET_NULLをauthor columのオプションに設定することで、
作成したメンバーを削除しても、記事は削除されないように設計されています.
-POST APIでのget
BDで削除したメンバーに対して、3番目の演算子を使用して投稿情報を返す場合
「消えた会員」名前のテキストが表示されます.
-POST APIのpost
pythonのtime.sleep()を使用して、投稿の登録時間をランダムに遅延します.
これは非常に簡単な概念で、DDos攻撃を阻止することができます.
-POST APIのpatch
PUTメソッドではなくPATCHメソッドを使用して、投稿の一部のデータを変更します.
🌈 小さな回顧🤔
久しぶりに書いた小さな回顧です
少し前、私は文章を平語から敬語に変えました.
字数を減らすほうが効率的に見えると思うので、平語の概念を決めました.
傲慢に見えるのでちょっと気分が悪いので気分を変えて穏やかになりました
Pythonと張高を学び、初めて完全なCRUDを一つのプロジェクトに統合した.
実施されたようです.
正確には初めてコードを学びました
RESTful APIも正しく適用されるのは初めてです.
URIはPOST/post-writeにそれぞれ設定され、すべてのAPIが分離される.
これは本当にRESTの黒い歴史を破壊して、同時に忘れられない学習です.
全体的に未熟ですが、コード転送を守るために何度かチェックしました.
少し時間をかけて、簡潔なコードを見て、幸せだったのを覚えています.
小さいながらも大きな一歩を踏み出した喜びをもう一度感じてほしい…!❤️‍🔥