- 대상
- 팀 프로젝트 투입 전 팀장, 팀원
- 기본적인 Git 명렁어는 알고 있는 사람 대상의 강의입니다.
- 내용
- PR을 통한 협업
- 필수적인 것만 반복 설명
- 필수적인 것을 제외한 것, 템플릿 설정 등은 설명하지 않습니다.
- 상세 영상
- (팀장 + 팀원) 팀 편성 후 프로세스
- 커뮤니케이션 도구 선택(디스코드, 슬랙, 지라, 노션, 피그마, 피그잼 등) 및 룰 만들기
- 오거나이제이션 만들기(수업은 개인 repo에서 진행)
- GitHub 위키(코드 컨벤션 세팅)
- GitHub 프로젝트(GitHub Project 생성 및 공개 설정)
- GitHub 이슈 생성
- (팀원) 이슈 생성(업무 할당) 후 프로세스
- 이슈 생성
- 브랜치 생성
- 작업 진행(commmit은 여러번 할 수 있습니다.)
- PR 생성
- Merge
- 브랜치 삭제
- 반복
- 마지막에는 Django 프로젝트를 만들어서 진행합니다. 꼭 Django를 모르셔도 눈으로 따라오시면 됩니다.
- 강의자료 짧은 링크: https://weniv.link/O1f8Hd
- 강의자료 긴 링크: https://paullabworkspace.notion.site/GitHub-GitHub-PR-bceef81b948944049a95de5297a78195?pvs=4
############################
# 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