############################

# GitHub repo 2개 생성합니다.
# 2개를 생성하는 이유는 일반적으로 규모있는 회사 프로젝트는 여러 repo로 구성되기 때문입니다.
# 협업에 있어서도 분야가 다른 소스코드를 1개의 레포에서 통합관리하지 않도록 해주세요.

fe-weniv_blog
be-weniv_blog

#############################

# git clone 해서 로컬에 저장소를 가져옵니다.
# 최상위 폴더는 project입니다.

fe-weniv_blog > git clone <https://github.com/paullabkorea/fe-weniv_blog.git> .
be-weniv_blog > git clone <https://github.com/paullabkorea/be-weniv_blog.git> .

#############################

GitHub Wiki > create the first page 클릭

# 프롬프트
'''
GitHub Wiki에 Django mini 프로젝트 컨벤션 가이드를 줄려고 해. 아래와 같이 간단하게 작성해줘.

1. VSC 익스텐션 가이드
2. Python 코딩 스타일 가이드
3. 깃 관리, 깃모지 및 커밋 가이드
'''

# 답변
'''
# Django Mini Project Convention Guide

이 가이드는 Django mini 프로젝트를 진행할 때 팀원들이 지켜야 할 컨벤션에 대해 설명합니다.

## 1. VSC 익스텐션 가이드

프로젝트를 진행할 때 다음과 같은 VSC 익스텐션을 사용하는 것을 권장합니다.

- Python
- Django
- SQLite
- Git History
- GitLens
- Prettier
- Auto Rename Tag
- Live Server

## 2. Python 코딩 스타일 가이드

Python 코드를 작성할 때는 PEP 8 스타일 가이드를 따릅니다.

- 들여쓰기는 스페이스 4칸을 사용합니다.
- 최대 줄 길이는 79자입니다.
- 함수와 클래스 사이에는 빈 줄을 두 줄 추가합니다.
- 모듈 임포트는 알파벳 순서로 정렬합니다.
- 변수와 함수 이름은 snake_case를 사용합니다.
- 클래스 이름은 PascalCase를 사용합니다.

## 3. 깃 관리, 깃모지 및 커밋 가이드

깃 관리 시 다음 규칙을 따릅니다.

- 브랜치 이름은 `feature/기능명` 형식을 사용합니다.
- 커밋 메시지는 영어로 작성하며, 제목은 50자 이내로 제한합니다.
- 커밋 메시지 제목 끝에 마침표를 사용하지 않습니다.
- 커밋 메시지 본문은 한 줄당 72자 이내로 제한합니다.

깃모지는 다음과 같이 사용합니다.

- ✨ (sparkles): 새로운 기능 추가
- 🐛 (bug): 버그 수정
- 📝 (memo): 문서 수정
- 🎨 (art): 코드 스타일 개선
- ⚡️ (zap): 성능 개선
- 🔧 (wrench): 설정 파일 수정
- 🚚 (truck): 파일 혹은 경로 이동/이름 변경
- 🔥 (fire): 코드 혹은 파일 제거
- ✅ (white_check_mark): 테스트 추가/수정
'''

#############################

GitHub BE-Team repo에서 Project 클릭 > New Project > ProjectName Project 생성
Project settings(오른쪽 상단 ...)에서 Manage access 클릭 > private 변경 > Public으로 변경

#############################

# 1cicle 시작(다만 dev 브랜치는 단 1번만 만들면 됩니다.)

#############################

GitHub BE-Team repo에서 Issues 클릭 > New Issue > IssueName Issue 생성
* Add a title: 기능 이름 생성
* Add a description: 기능 내용 생성
* Assignees: 팀원 선택
* Labels: bug, duplicate, enhancement, good first issue, help wanted, invalid, question, wontfix
* Projects: Project 선택

submit new issue 클릭

Project에서 Todo, In Progress, Done 생성
Development에서 Create a branch 클릭 > BranchName 생성 > Create branch 클릭

#############################

복사하라고 뜹니다. 복사해주세요. 아직 커멘드 라인에 붙여넣진 않겠습니다.
git fetch origin
git checkout 1-one

#############################

전략은 GitHub Flow + develop branch 전략을 사용합니다.
전략상 필요한 dev branch를 만들어줍니다.

git branch
git branch dev
git push --set-upstream origin dev

settings > General > Default branch > dev 선택
// 이제부터 merge는 develop branch로 합니다.

#############################

git branch # 현재 브랜치 확인
git fetch origin # 원격 저장소의 브랜치 정보를 가져옴
git checkout 1-one # 브랜치 생성 및 이동
# 작업 진행
git add . # 변경사항을 스테이징
git commit -m "feat: test one" # 커밋
git push # 원격 저장소에 푸시

#############################

GitHub BE-Team repo에서 Compare&Pull requests 클릭 또는 Pull requests 클릭 > New pull request 클릭

add a title: feat: test one
add a description: feat: test one(내용은 좀 더 상세하게 작성해주세요. table과 같은 것을 넣어주셔도 좋습니다.)

Create pull request 클릭

#############################

Merge pull request 클릭
Confirm merge 클릭
Delete branch 클릭

#############################

// local에서 해야 하는 것
git checkout dev
git pull
git branch -d 1-one

# VSC에 Source control에 가서 view git graph(git log)를 클릭
# 원하는 브랜치에 네모 박스에서 마우스 오른쪽 클릭하고 delete branch 클릭해서
# GUI로도 브랜치 삭제가 가능합니다.

#############################

# 1cicle 끝, 이렇게 5번 반복합니다.

#############################

New issue 생성
add a title: init django settings
add a description:
장고 초기 세팅입니다. 아래와 같은 내용을 포함합니다.
    - requirements.txt
    - .gitignore
    - settings.py
    - urls.py
    - README.md

#############################

Project에서 Todo, In Progress, Done 생성
Development에서 Create a branch 클릭 > BranchName 생성 > Create branch 클릭

#############################

복사하라고 뜹니다. 복사해주세요.

git fetch origin
git checkout 3-init-django-settings
git branch

#############################

powershell로 작업

###################################

파이썬 설치
VSC를 이 폴더 기준으로 열었습니다.
# 3.12버전으로 최신버전 확인해야 합니다.

python --version
mkdir viewset
cd viewset
python -m venv venv
.\\\\venv\\\\Scripts\\\\activate
# source ./venv/bin/activate

pip install django
pip install pillow
pip install djangorestframework
pip install django-cors-headers

django-admin startproject config .

# freeze로 requirements.txt 생성
pip freeze > requirements.txt

# .gitignore 생성(powershell이어서 echo 명령어 사용)
# <https://www.toptal.com/developers/gitignore>
echo "venv/`n__pycache__/`n*.pyc`ndb.sqlite3`n.DS_Store`n*/migrations/" > .gitignore

###################################

git add . # 변경사항을 스테이징
git commit -m "feat: test one" # 커밋
git push # 원격 저장소에 푸시

#############################

BE-Team repo에서 Compare&Pull requests 클릭 또는 Pull requests 클릭 > New pull request 클릭
Merge까지 진행

#############################

// local에서 해야 하는 것
git checkout dev
git pull
git branch -d 3-init-django-settings

#############################

New issue 생성
add a title: init django app
add a description:
장고 앱을 생성합니다. 아래와 같은 내용을 포함합니다.
    - app 생성
    - model 생성
    - serializer 생성
    - viewset 생성
    - url 연결

#############################

Project에서 Todo, In Progress, Done 생성

Development에서 Create a branch 클릭 > BranchName 생성 > Create branch 클릭

#############################

복사하라고 뜹니다. 복사해주세요.

git fetch origin
git checkout 4-init-django-app
git branch

#############################

python manage.py startapp posts

# => 혹시 setuptools관련 error 발생하면 입력하세요.
# pip install setuptools

#############################

git add .
git commit -m 'posts 앱 생성'

###################################

ALLOWED_HOSTS = ['*']

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # custom app
    'posts',
    # install app
    'rest_framework',
    'corsheaders',
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "corsheaders.middleware.CorsMiddleware", # 추가
]

##

LANGUAGE_CODE = 'ko-kr'

TIME_ZONE = 'Asia/Seoul'

##

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

###################################

git add .
git commit -m 'settings.py 설정 수정'

###################################
# config > urls.py

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path("posts/", include("posts.urls")),
    path("admin/", admin.site.urls),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

###################################

git add .
git commit -m 'urls.py 설정 수정'

###################################

posts app 작성(models.py, urls.py, views.py, serializers.py, settings.py)

###################################
# posts > models.py

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

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
    caption = models.TextField()
    image = models.ImageField(upload_to="post_images/", blank=True) # 다른 점: blank=True
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.author} - {self.caption[:10]}"

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="comments")
    text = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.author.username} - {self.text[:10]}"

class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="likes")
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="likes")
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ("post", "user")

    def __str__(self):
        return f"{self.user.username} likes {self.post.caption}"

###################################

git add .
git commit -m 'models.py 작성'

###################################
# posts > urls.py (생성 후 저장)

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet, CommentViewSet, LikeView

router = DefaultRouter()
router.register("posts", PostViewSet)
router.register("posts/(?P<post_pk>[^/.]+)/comments", CommentViewSet)

urlpatterns = [
    path("", include(router.urls)),
    path("posts/<int:post_id>/like/", LikeView.as_view(), name="post-like"),
]

###################################

git add .
git commit -m 'posts > urls.py 작성'

###################################
# posts > views.py

from rest_framework import viewsets
from .serializers import PostSerializer, CommentSerializer
from .models import Comment, Post, Like
from rest_framework import permissions, views, response, status
from django.shortcuts import get_object_or_404

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticated]

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

class CommentViewSet(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
    permission_classes = [permissions.IsAuthenticated]

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

    def get_queryset(self):
        return self.queryset.filter(post_id=self.kwargs["post_pk"])

class LikeView(views.APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, post_id):
        post = get_object_or_404(Post, id=post_id)
        like, created = Like.objects.get_or_create(post=post, user=request.user)

        if not created:
            return response.Response(status=status.HTTP_409_CONFLICT)

        return response.Response(status=status.HTTP_201_CREATED)

    def delete(self, request, post_id):
        post = get_object_or_404(Post, id=post_id)
        like = get_object_or_404(Like, post=post, user=request.user)
        like.delete()
        return response.Response(status=status.HTTP_204_NO_CONTENT)

###################################

git add .
git commit -m 'posts > views.py 작성'

###################################
# posts > serializers.py

from rest_framework import serializers
from .models import Comment, Post, Like

class CommentSerializer(serializers.ModelSerializer):
    author_username = serializers.ReadOnlyField(source="author.username")

    class Meta:
        model = Comment
        fields = ["id", "post", "author", "author_username", "text", "created_at"]
        read_only_fields = ["author", "author_username"]

class PostSerializer(serializers.ModelSerializer):
    author_username = serializers.ReadOnlyField(source="author.username")
    comments = CommentSerializer(many=True, read_only=True)
    likes_count = serializers.IntegerField(source="likes.count", read_only=True)
    is_liked = serializers.SerializerMethodField()

    class Meta:
        model = Post
        fields = [
            "id",
            "author",
            "author_username",
            "caption",
            "image",
            "created_at",
            "comments",
            "likes_count",
            "is_liked",
        ]
        read_only_fields = ["author", "author_username", "likes_count", "is_liked"]

    def get_is_liked(self, obj):
        user = self.context["request"].user
        if user.is_authenticated:
            return Like.objects.filter(post=obj, user=user).exists()
        return False

###################################

git add .
git commit -m 'posts > serializers.py 작성 및 posts 앱 설정 끝'
git push

###################################

BE-Team repo에서 Compare&Pull requests 클릭 또는 Pull requests 클릭 > New pull request 클릭
Merge까지 진행

###################################

# local에서 해야 하는 것
git checkout dev
git pull
git branch -d 4-init-django-app

###################################

# Test를 진행하기 위해 별도 브랜치 생성
git branch test
git checkout test

###################################

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

python manage.py createsuperuser
leehojun
dlghwns1234!

###################################

요즘은 api가 되는지 안되는지 ChatGPT나 Caulde3에서 테스트도 가능합니다.

###################################

API Test - VSC Extension - Thunder Client

###################################
# URL
<http://127.0.0.1:8000/posts/posts/>

# Method
POST

# Auth
leehojun
dlghwns1234!

# Body
{
  "caption":"hello"
}

###################################
# URL
<http://127.0.0.1:8000/posts/posts/>

# Method
GET

###################################
# URL
<http://127.0.0.1:8000/posts/posts/1/>

# Method
PATCH

# Body
{
  "caption":"hello patch test"
}

###################################
# 테스트가 끝났으니 다시 dev 브랜치로 돌아갑니다.
# test 브랜치는 삭제합니다.

git add .
git commit -m 'test end'
git checkout dev
git branch -d test

###################################
# Router에 URL 추가

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet, CommentViewSet, LikeView

router = DefaultRouter()
router.register("posts", PostViewSet)
router.register("posts/(?P<post_pk>[^/.]+)/comments", CommentViewSet)

urlpatterns = [
    path("", include(router.urls)),
    path("posts/<int:post_id>/like/", LikeView.as_view(), name="post-like"),
]

###################################
# views.py
from rest_framework import viewsets
from rest_framework.decorators import action
# 생략

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticated]

    @action(detail=True, methods=["get"])
    def sampleone(self, request, pk=None):
        """
        detail=True: 특정 게시물에 대한 작업
        detail=False: 모든 게시물에 대한 작업
        /posts/{pk}/sampleone/: 개별 게시물의 제목과 내용을 반환하는 엔드포인트 (detail=True)
        """
        data = {"title": "hello", "content": "world"}
        return response.Response(data)

    @action(detail=False, methods=["get"])
    def sampletwo(self, request):
        """
        /posts/sampletwo/: 모든 게시물의 제목과 작성자 이름을 반환하는 엔드포인트 (detail=False)
        """
        data = [{"title": "hello 2", "author": "world 2"}]
        return response.Response(data)

###################################
# URL
<http://127.0.0.1:8000/posts/posts/1/sampleone/>

# Method
GET

###################################
# URL
<http://127.0.0.1:8000/posts/posts/sampletwo/>

# Method
GET

###################################
# views.py
# 아래와 같이 재정의 할 수 있습니다.

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticated]

    def list(self, request, *args, **kwargs):
        return response.Response("Hello, World!")

###################################
# 매서드별 권한 재정의

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def list(self, request, *args, **kwargs):
        # self.permission_classes = [permissions.AllowAny]
        # self.permission_classes = [permissions.IsAuthenticated]
        return super().list(request, *args, **kwargs)

###################################
# 다른 repo에 와서 똑같은 작업을 반복합니다.(repo가 여러개여도 마찬가지입니다.)

# 이슈 등록
# Project 등록(기존에 등록했던 것으로 관리가 가능합니다.)
# Branch 생성
# 작업 진행
# PR 생성
# Merge