Team Rule
이건 지켜주세요!★★★
┌팀프로젝트에 연관된 것 팀원에게 질문
└기본 개념or방법 관련 튜터님에게 질문
12:00~13:00 점심
17:00~18:00 현황공유 - issue있을 경우!
18:00~19:00 저녁
정기 회의
- 시작회의 - AM 09:00 - 1시간 이내로 | 타임오버X - 하루계획, 현황공유, 역할분담, 건의사항, 개발issue공유
- 현황공유 - PM 17:00 → 다음날 or 저녁시간에 튜터님 질문
- 마감회의 - PM 20:30 - 1시간 이내로 | 타임오버X - 내일계획, 일정 및 현황공유 + 내일계획(간략히만)
소통법 Slack
- Team slack 창을 통해 일정 및 정보 공유!
- ★★★잠수금지!!★★★
- 진행 현황은 솔직하게!
- 식사 시간은 정하되, 팀프로젝트 전에는 자유롭게!
- 소극적이지 마라! 너는 이미! 할 수 있다!
- ★한숨 금지!
- 연락두절 금지! slack 수시 확인!
- ★고민공유! 제발! 속에 담지 마요! 슬렉도 있구요, 따라나와도 있어요.
코드 컨벤션
- 표기법
- 변수/함수 : Snake 표기법 ex) 'python_is_very_good'
- Class : Pascal 표기법 ex) 'PythonIsVeryGood'
- html 클래스명
- Bootstrap 사용 시 오버라이딩 금지! 반드시 추가로 class를 지정해주거나, 태그 인라인으로 기입!
- class명 지정 시 이니셜로 구분하기! ex) box_myo, box_ljm, box_yhm, box_nmh
깃 커밋 컨벤션
깃 커밋 컨벤션
###########################################################################################
#<타입>: <제목> 의 형식으로 제목을 아래 공백줄에 작성
#제목은 50자 이내 / 변경사항이 "무엇인지 명확히 작성/ 끝에 마침표 금지 # 예) :sparkles:Feat: 로그인 기능 추가,
# 바로 아래 공백은 지우지 마세요 (제목과 본문의 분리를 위함)
#본문(구체적인 내용)을 아랫줄에 작성
#여러 줄의 메시지를 작성할 땐 "-"로 구분 (한 줄은 72자 이내)
###########################################################################################
#꼬릿말(footer)을 아랫줄에 작성 (현재 커밋과 관련된 이슈 번호 추가 등) #예) Close #7, related\_to: #7
###########################################################################################
\[스파클 이모티콘\]Feat:
# ✨Feat: 새로운 기능 추가
# 🐛Fix: 버그 수정
# 🚑!HOTFIX: 급한 오류 수정
# 📚Docs: 문서 수정
# ☔Test: 테스트 코드 추가
# 🔨Refact: 코드 리팩토링
# 🎨Style: 코드 의미에 영향을 주지 않는 변경사항
# 💄Design: CSS 등 사용자 UI디자인 변경
# 💬Comment: 필요한 주석 추가 및 변경
# 🏷️Rename: 파일 혹은 폴더명을 수정하거나 옮기는 작업만의 경우
# ❌Remove: 파일을 삭제하는 작업만 수행한 경우
# 📦Package: 빌드 부분 혹은 패키지 매니저 수정사항
# :sparkles:Feat: 새로운 기능 추가
# :bug:Fix: 버그 수정
# :ambulance:HOTFIX: 급한 오류 수정
# :books:Docs: 문서 수정
# :umbrella:Test: 테스트 코드 추가
# :hammer:Refact: 코드 리팩토링
# :art:Style: 코드 의미에 영향을 주지 않는 변경사항
# :lipstick:Design: CSS 등 사용자 UI디자인 변경
# :speech\_balloon:Comment: 필요한 주석 추가 및 변경
# :label:Rename: 파일 혹은 폴더명을 수정하거나 옮기는 작업만의 경우
# :remove:Remove: 파일을 삭제하는 작업만 수행한 경우
# :package:Package: 빌드 부분 혹은 패키지 매니저 수정사항
fetch = 로컬 저장소에서 하는 Synk fork
pull = fetch + merge
origin/develop = 원격저장소의develop
\[PR후 다른 브랜치 생성해서 작업하는 경우\]
fork한 내 repo Synk fork -> git fetch ->
git checkout origin/develop로 fetch한 원격repo 로컬repo 연결 확인
git checkout develop-> git merge origin/develop으로 머지 -> 기존 branch -D 삭제
\-> develop에서 feature/기능으로 git log 반영한 branch생성 -> 작업 시작
\[원본 develop에 바뀐 코드가 필요한 경우 & rejected 발생시\]
기존 작업 중인 내용 git stash로 저장 -> fork한 내 repo Synk fork ->
git fetch -> git checkout origin/develop로 fetch한 원격repo 로컬repo 연결 확인
git checkout develop-> git merge origin/develop으로 머지 -> 기존 branch -D 삭제
\-> develop에서 feature/기능으로 git log 반영한 branch생성 ->
git stash apply stash@{0}로 stash 적용
\-> conflict발생시 해결 -> 작업 시작
\[push 실수 했을 때 조치 방법\]
git log로 commit 내역 확인 -> git reset HEAD^로 최근 commit 취소 -> git push -f로 강제 push
or
git log --oneline으로 commit 내역 확인 -> 되돌릴 지점 해시 확인 -> git reset <해시>
\-> git push -f로 강제 push
\[branch명 잘못 만든 경우\]
이름 바꿀 브랜치에서 git branch -m \[새 브랜치 명\]
\-> 이미 push --set-upstream한 경우 git push --set-upstream origin \[새 브랜치명\]
\-> 잘못 push한 branch 원격 repo에서 삭제
\[가장 최근 commit 메시지 수정\]
git commit --amend -> git log로 수정 확인
\[이전 commit 메시지 수정\]
git rebase -i HEAD~\[head에서 떨어진 수\] -> 수정할 commit의 pick을 reword로 변경 후 저장
\-> 순서대로 commit 변경 메시지 작성 -> git log로 수정 확인
# squash 사용시 commit 합치기 ,reword
- GIT COMMIT MESSAGE 컨벤션 - 풀리퀘스트 시 확인하기!
git commit -m ‘내용~~~ #1’
제목의 맨 뒤에 #을 붙인다.
git commit > i 누르면 작성 시작 > 작성 > esc > :wq
커밋 메시지 예시
✨Feat: 게시글 상세 페이지 추가 #이슈 번호
주석 및 독스트링
- 조건문 반복문 옵션 등 해석이 필요한 부분은 주석으로 남겨두기
- 함수 def 아래 줄에 함수 설명을 독스트링으로 추가하기
Github 브랜치 전략
- 팀장이 레파지토리 형성
- 각 팀원들이 main을 syncfork
- 각자 브랜치 만들어 작업
- 각 작업물을 dev 에 Pull Request
- dev local에서 검증 후 dev를 main에 merge
- GIT - 코드 컨벤션 black (autopep8미사용)pull request : 기술 개발 및 결합 과정의 방법 논의
-
- issue 만들어서 commit -m에 해당 번호 추가하기
- 브랜치 병합 : 기능구현 완료 되었을 때
- 브랜치는 어떻게 나눌 것인지
- 브랜치는 대체로 아래처럼 나눕니다.
- 메인 브랜치(main) - 배포용
- 디벨롭 브랜치(dev)
- 기능 단위 별 브랜치(feature/기능명)
- 병합 순서는 대체로 이렇게 진행해요.
- 기능 단위 별 브랜치 — 병합 → 디벨롭 브랜치 —최종 병합→ 메인 브랜치
- 작업하기 전에 메인 브랜치에서 pull하기!!!(관리자도 해야함),(안하면 나중에 커밋 엇갈려서 오류생김 >> 많이 힘들어집니다)
- 메인 브랜치가 수정되면 팀원들에게 꼭 말해서 pull시키기!!(이유는 1번과 같음)
- 결론 : fetch, merge orgin/main == pull-push-pull request 순서대로 하기
branch 이름 목록
띵곡-BE
- main
- dev
- feature/user | 김광운
회원가입
로그인(+소셜로그인)
- feature/user_page | 문영오
프로필 수정(닉네임, 선호장르)
댓글 좋아요 리스트 조회
게시글 북마크 리스트 조회
- feature/article_crud | 김혁준
- 게시글 작성, 조회, 수정, 삭제
- feature/comment_crud | 이기웅
- 댓글 작성, 조회, 수정, 삭제
- feature/openai | 이기웅
- chat-gpt 연동
- feature/follow | 문영오
유저 간 팔로우, 언팔로우 기능
- feature/like | 구민정
댓글 좋아요 기능
- feature/bookmark | 구민정
게시글 북마크 기능
- feature/testcode | 구민정
띵곡-FE
- main
- dev
- feature/main | 김광운
Header, Footer
- feature/user | 구민정
- 회원가입
- 로그인
- 로그아웃
- feature/user_page | 문영오
프로필 페이지
깃허브 주소
백엔드
프론트엔드
컨셉
음악 커뮤니티 - 띵곡🎵
원하는 음악을 추천하고 추천 받는 커뮤니티 사이트
기능
핵심 목표
- 머신러닝 라이브러리 활용
- 프론트엔드와 백엔드의 분리
- aws를 이용한 서비스의 배포
하고 싶은 기능
민정 : 테스트코드, 페이지네이션
광운 : 소셜 로그인, 프로젝트 완성하기
영오 : 비밀번호 리셋하기, 회원 탈퇴하기, 회원정보수정
혁준 : 백오피스, 클라우드플레어
기웅 : 머신러닝, 하고 싶었지만 하지 못했던 기능. 로봇계정을 하나 더 만들어서 댓글에 로봇계정으로 음악을 추천해주었으면 더 좋았을 것 같다.
구현한 기능
백엔드
- 로그인
- 회원가입
- 로그인(+소셜 로그인 - 카카오)
- 유저
- 프로필 조회
- 프로필 이미지 업로드
- 프로필 이미지 삭제
- 프로필 수정
- 팔로잉
- 게시글
- 작성
- 조회
- 수정
- 삭제
- 이미지 업로드
- 이미지 삭제
- 북마크
- 댓글
- 작성
- 조회
- 수정
- 삭제
- 좋아요
- AI 노래 추천
- 게시글 작성 시 게시글의 내용을 읽고 챗-GPT가 유튜브에서 노래를 추천해서 댓글로 자동 작성됨
프론트엔드
- 헤더, 푸터
- 토글 기능
- 로그인 하지 않은 유저는 글쓰기, 마이페이지 버튼이 없음
- 로그인한 사용자의 프로필 사진 보임
- 닉네임 부분 클릭하면 마이페이지로 이동함
- 토글 기능
- 로그인
- 회원가입
- 로그인(카카오, 네이버, 구글 로그인)
- 로그아웃
- 메인 페이지
- 검색창
- 좋아요 개수 순으로 댓글 9개 정렬
- 최신 순으로 게시글 9개 정렬
- 유저 페이지
- 프로필 수정(프로필 사진, 비밀번호, 닉네임, 성별, 나이 변경 가능)
- 소셜로그인 유저의 경우 비밀번호 박스 막아놓음(어차피 변경해도 변경 안 됨)
- 내가 쓴 글 최신순으로 정렬
- 내가 좋아요 누른 댓글 최신순으로 정렬
- 내가 북마크한 게시글 최신순으로 정렬
- 다른 유저의 페이지 접속해서 팔로잉 클릭 가능
- 존재하지 않는 유저 id로 접속 시 404 페이지 보여줌
- 프로필 수정(프로필 사진, 비밀번호, 닉네임, 성별, 나이 변경 가능)
- 게시글
- 글 목록 조회
- 페이지네이션: 한 페이지에 게시글 10개씩, 페이지 수는 5개씩 보임. 5페이지 넘어가면 다음 페이지로 넘기는 아이콘 생김
- 작성 페이지
- 제목, 내용, 이미지 업로드 가능
- 이미지는 선택사항
- 수정 페이지
- 기존의 제목, 내용이 보임
- 사진은 보이지 않지만 새로 업로드 하지 않을 경우 이미지가 그대로 유지됨
- 이미지를 삭제할 수 있는 삭제 버튼이 있음
- 상세 페이지
- 존재하지 않는 게시글 id로 접속 시 404 페이지 보여줌
- 로그인한 유저와 게시글 작성자가 일치하면 수정, 삭제 버튼 보임
- 로그인한 유저와 게시글 작성자가 일치하지 않으면 북마크하기 버튼 보임
- 작성자의 닉네임을 누르면 해당 유저의 유저페이지로 이동함
- 글 목록 조회
- 댓글
- 게시글 작성 시 작성자의 닉네임으로 AI가 노래를 추천해주는 댓글이 달림
- 자신이 작성한 댓글에는 수정, 삭제 버튼 보임
- 좋아요 버튼 구현
- 자신이 작성한 글 첫 번째 댓글(AI가 노래 추천하는 댓글)은 좋아요 버튼과 삭제 버튼이 보임
- 타인이 작성한 댓글은 좋아요 버튼만 있음
- 좋아요 개수는 일부러 보이지 않게 함(자신의 취향에 따라 소신껏 좋아요를 누르기 위해)
- 근데 인기댓글은 메인페이지에서 확인할 수 있기는 함
- 댓글 작성자의 닉네임 누르면 해당 유저의 유저페이지로 이동함
기술 스택
- Frontend
- HTML
- JavaScript
- CSS
- Backend
- Python 3.11.3
- Django 4.2.1
- DRF 3.14.0
- DRF-simplejwt 5.2.2
와이어프레임
DB설계
ERD(23-05-22)
0522
0528
API 설계(23-05-22)
띵곡맛집
기능분류 | 기능 | Method | URL | Request | Response |
---|---|---|---|---|---|
1.로그인 | 회원가입 | POST | users/signup/ | { ”email” : “email@email.email”, ”nickname” : “nickname”, ”password” : “password”, ”password2” : “password”, ”gender” : “M or W”, ”age” : number, } | { ”HTTP Response” : “201 Created” } |
로그인 | GET | users/login/ | { ”email” : “email@email.email”, ”password” : “password” } | { ”refresh” : “refresh token”, ”access” : “access Token” } | |
카카오 api key | GET | users/kakao/ | - | { ”api_key”: “api_key” } | |
구글 api key | GET | users/google/ | - | { ”api_key”: “api_key” } | |
네이버 api key | GET | users/naver/ | - | { ”api_key”: “api_key” } | |
카카오 소셜 로그인 | POST | users/kakao/ | { ”code” : “code” } | { ”refresh” : “refresh token”, ”access” : “access token” } | |
구글 소셜 로그인 | POST | users/google/ | { ”access_token” : “access_token” } | { ”refresh” : “refresh token”, ”access” : “access token” } | |
네이버 소셜 로그인 | POST | users/naver/ | { ”code” : “code”, ”state” : “state” } | { ”refresh” : “refresh token”, ”access” : “access token” } | |
로그아웃 | 프론트엔드에서 진행 | - | - | ||
2. 프로필페이지 | 마이페이지 | GET | users/profile/<int:user_id>/ | - | { ”HTTP Response” : “200 OK”, { “user_id” : 1, “nickname” : “nickname”, “profile_img” : “image URL”, “gerne” : “prefer gerne” } } |
프로필 수정 | PUT | users/profile/<int:user_id>/ | { ”nickname” : “modify nickname”, ”profile_img” : “image URL”, ”gerne” : “prefer genre” } | { ”HTTP Response” : “200 OK” } | |
7. 팔로잉 | 팔로잉 생성/삭제 | POST | users/follow/<int:user_id>/ | - | { ”HTTP Response” : “200 OK” }, { ”HTTP Response” : “204 NO_CONTENT” } |
내가 쓴 글 모두 보기 | GET | users/profile/<int:user_id>/myarticles/ | - | { ”HTTP Response” : “200 OK”, { “user_id” : 1, “title” : “title”, “content” : “content”, “image” : “image URL” }, … } | |
좋아요 목록 보기 | GET | users/profile/<int:user_id>/likes/ | - | { ”HTTP Response” : “200 OK”, { “comment_id” : 1, “comment” : “comment”, “url” : “url” }, … } | |
북마크 게시글 보기 | GET | users/profile/<int:user_id>/bookmarks/ | - | { ”HTTP Response” : “200 OK”, { “user_id” : 1, “title” : “title”, “content” : “content”, “image” : “image URL” }, … } | |
3.게시글 | 게시글 전체보기 | GET | articles/ | - | { ”HTTP Response” : “200 OK”, { “user_id” : 1, “title” : “title”, “content” : “content”, “image” : “image URL” }, … } |
게시글 생성 | POST | articles/ | { ”title” : “title”, ”content” : “content”, “image” : “image URL” } | { ”HTTP Response” : “201 Created” } | |
게시글 상세보기 | GET | articles/<int:article_id>/ | - | { ”HTTP Response” : “200 OK”, { “user_id” : 1, “title” : “title”, “content” : “content”, “image” : “image URL”, { “comment_id” : 1, “comment” : “comment”, “url” : “url” }, … }, … } | |
게시글 수정하기 | PUT | articles/<int:article_id>/ | { ”title” : “title”, ”content” : “content”, “photo” : “photo URL” } | { ”HTTP Response” : “200 OK” } | |
게시글 삭제하기 | DELETE | articles/<int:article_id>/ | - | { ”HTTP Response” : “204 NO_CONTENT” } | |
게시글 사진올리기 | POST | articles/<int:article_id>/photos/ | {”file” : “article photo url”} | { ”HTTP Response” : “201 Created” } | |
4. 댓글 | 댓글 생성 | POST | articles/<int:article_id>/comment/ | { ”comment” : “comment”, ”url” : “url”, } | { ”HTTP Response” : “201 Created” } |
댓글 조회 | GET | articles/<int:article_id>/comment/ | - | { ”HTTP Response” : “200 OK”, { “comment_id” : 1, “comment” : “comment”, “url” : “url” “like_count” : n }, … } | |
댓글 수정하기 | PUT | comment/<int:comment_id>/ | { ”comment” : “comment”, ”url” : “url”, } | { ”HTTP Response” : “200 OK” } | |
댓글 삭제하기 | DELETE | comment/<int:comment_id>/ | - | { ”HTTP Response” : “204 NO_CONTENT” } | |
5. 좋아요 | 댓글 좋아요 생성/삭제 | POST | articles/like/<int:comment_id>/ | - | { ”HTTP Response” : “201 Created” }, { ”HTTP Response” : “204 NO_CONTENT” } |
좋아요순 댓글 모음 페이지 | GET | articles/likes/ | - | { ”HTTP Response” : “200 OK”, { “comment_id” : 1, “comment” : “comment”, “url” : “url” “like_count” : n }, … } | |
6. 북마크 | 게시글 북마크 생성/삭제 | POST | articles/bookmark/<int:post_id> | - | { ”HTTP Response” : “201 Created” }, { ”HTTP Response” : “204 NO_CONTENT” } |
사진 삭제 | DELETE | medias/photos/ | |||
8. 이미지 | 사진 조회 | GET | medias/photos/get-url/ | ||
유저 프로필 사진 올리기 | POST | users/<int:user_id>/photos/ | { ”file” : “profile photo url” } | { ”HTTP Response” : “201 Created” } | |
9. 검색 | 검색 | GET | search/<str:query>/ | { ”HTTP Response” : “200 OK”, { “user_id” : 1, “title” : “title”, “owner” : “owner”, “created_at” : “2023-05-24 18:30” }, … } |
트러블 슈팅
트러블 슈팅⚽️
Date | error | developer | 해결 여부 | 비고 |
---|---|---|---|---|
[백] 소셜 로그인 데이터 KeyError | 김광운 |
|
||
[프론트] 로드 순서 문제로 태그를 못찾는 오류 | 김광운 |
|
||
[배포] Ubuntu poetry 설치 에러 |
|
|||
[배포] Poetry python 버전 호환 문제 |
|
|||
[배포] mysqlclient 설치 에러 |
|
|||
[배포] gunicorn poetry 경로 에러 |
|
팀원별 마음에 드는 코드
구민정
선정코드 | 이유 : 프론트에서 문자를 백엔드로 넘겨줄 때 URL escape code로 인코딩 된다 그래서 받은 문자열을 그대로 백엔드의 DB와 비교하면 일치하는 값이 없는데 그걸 urllib 라이브러리의 parse_unquote 메서드로 URL escape code를 한글로 바꿔서 DB와 비교하게끔 하는 걸 새로 배웠기 때문에 마음에 든다
articles/views.py - SearchView 클래스
class SearchView(APIView): def get(self, request, query): """검색하기 제목이나 내용에 입력한 검색어가 포함되어 있는 게시글들을 가져옴""" decoded_query = urllib.parse.unquote(query) articles = Article.objects.filter( Q(title__contains=decoded_query) | Q(content__contains=decoded_query) ) serializer = ArticleListSerializer(articles, many=True) if articles: return Response(serializer.data, status=status.HTTP_200_OK) else: return Response("검색 결과가 없습니다", status=status.HTTP_204_NO_CONTENT)
김광운
선정코드 | 이유 : 소셜 로그인 과정에서 중복되는 코드를 따로 함수로 빼와서 가독성이 올라간 것 같아 기부니가 좋았습니다..
users/views.py - SocialLogin()
def SocialLogin(**kwargs): """소셜 로그인, 회원가입""" # 각각 소셜 로그인에서 email, nickname, login_type등을 받아옴!! data = {k: v for k, v in kwargs.items() if v is not None} # none인 값들은 빼줌 email = data.get("email") login_type = data.get("login_type") # 그 중 email이 없으면 회원가입이 불가능하므로 # 프론트에서 메시지를 띄워주고, 다시 로그인 페이지로 이동시키기 if not email: return Response( {"error": "해당 계정에 email정보가 없습니다."}, status=status.HTTP_400_BAD_REQUEST ) try: user = User.objects.get(email=email) # 로그인 타입까지 같으면, 토큰 발행해서 프론트로 보내주기 if login_type == user.login_type: refresh = RefreshToken.for_user(user) access_token = CustomTokenObtainPairSerializer.get_token(user) return Response( {"refresh": str(refresh), "access": str(access_token.access_token)}, status=status.HTTP_200_OK, ) # 유저의 다른 소셜계정으로 로그인한 유저라면, 해당 로그인 타입을 보내줌. # (프론트에서 "{login_type}으로 로그인한 계정이 있습니다!" alert 띄워주기) else: return Response( {"error": f"{user.login_type}으로 이미 가입된 계정이 있습니다!"}, status=status.HTTP_400_BAD_REQUEST, ) # 유저가 존재하지 않는다면 회원가입시키기 except User.DoesNotExist: new_user = User.objects.create(**data) # pw는 사용불가로 지정 new_user.set_unusable_password() new_user.save() # 이후 토큰 발급해서 프론트로 refresh = RefreshToken.for_user(new_user) access_token = CustomTokenObtainPairSerializer.get_token(new_user) return Response( {"refresh": str(refresh), "access": str(access_token.access_token)}, status=status.HTTP_200_OK, )
문영오
선정코드 | 이유 : 특정유저의 정보를 불러오는 과정에서 게시글의 총합 등 연산정보를 프론트가 아닌 백엔드에서 제공함으로 프론트 작업이 편해짐을 경험했습니다.
user/serializers.py - UserProfileSerializer
class UserProfileSerializer(serializers.ModelSerializer): like_comments = serializers.SerializerMethodField() bookmarks = serializers.SerializerMethodField() followings = serializers.PrimaryKeyRelatedField(many=True, read_only=True) followers = serializers.PrimaryKeyRelatedField(many=True, read_only=True) articles = serializers.SerializerMethodField() def get_articles(self, obj): return obj.articles.count() def get_like_comments(self, obj): return list(obj.like_comments.values()) def get_bookmarks(self, obj): return list(obj.bookmarks.values()) class Meta: model = User exclude = ( "user_permissions", "is_superuser", "last_login", "is_active", "is_admin", "password", "groups", ) extra_kwargs = { "password": { "write_only": True, # 작성만 가능하도록 제한! 비밀번호 조회 불가 }, }
김혁준
선정코드 | 이유 : 어드민 페이지를 자유롭게 커스터마이징 할 수 있어서 신기하고 유용하겠다는 생각이 들었습니다.(특히 comments_url_list 함수)
backend - articles/models.py - Article
class Article(models.Model): title = models.CharField(max_length=50, default="title") content = models.TextField() owner = models.ForeignKey( "users.User", on_delete=models.CASCADE, related_name="articles", ) bookmark = models.ManyToManyField( User, blank=True, verbose_name="북마크", related_name="bookmarks" ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return str(self.title) def total_comments(self): return self.comments.count() def total_bookmarks(self): return self.bookmark.count() def comments_url_list(self): comments = self.comments.all() comment_url_content = "" url_regex = r"(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)" reg = re.compile(url_regex) for comment in comments: res = reg.search(comment.comment) if res: indexes = res.span() comment_url_txt = comment.comment[indexes[0] : indexes[1]] article_id = comment.article.pk comment_id = comment.id comment_user = comment.user is_AI = False if comment == comments[0]: is_AI = True comment_url_content += f"게시글 id : {article_id} / 댓글 id : {comment_id} / 댓글 유저 이름 : {comment_user} / 댓글 유저는 ai인가? : {is_AI} / 댓글 내용 URL : {comment_url_txt}\n\n" if comment_url_content == "": return "Url이 포함된 댓글이 없습니다!" else: return comment_url_content
이기웅
선정코드 | 이유 : 사용자가 게시글을 작성할때 게시글 내용을 GPT에게 전달하여 내용을 분석하고, 분석한 내용에 어울릴만한 음악을 추천받는 코드. 직접 구현한거라 뿌듯한 마음이 있어서 선정한 것 같습니다. 아쉬운점이 있다면 GPT의 힘을 받지않고 입력되는 내용들을 계속 학습하면서 조금 더 정교한 추천을 하는 머신너링 모델을 만들었으면 좋았을 텐데, 배움이 깊지못해서 직접 구현하지못하고 GPT의 힘을 빌렸습니다. 모델링도 해볼수있는 지식을 가져보도록 노력하겠습니다!
코드명 openai_utility.py
# GPT API 키 설정 openai.api_key = OPENAI_API_KEY import requests from bs4 import BeautifulSoup def is_artist_first(text): if re.match(r"^[a-zA-Z가-힣\s]+[-]\s*", text): return True else: return False def extract_song_title(text): if is_artist_first(text): pattern = r"^[a-zA-Z가-힣\s]+[-]\s*" else: pattern = r"[-]\s*[a-zA-Z가-힣\s]+" without_artist = re.sub(pattern, "", text) return without_artist.strip() def gpt_music_recommendation(content): gpt_input = ( f"이 내용 분위기에 어울리는 완벽한 팝송 추천 한곡만 부탁드립니다: {content}. 제목만 알려주세요. 가수 이름과 링크는 필요없어요. " ) response = openai.Completion.create( engine="text-davinci-003", prompt=gpt_input, max_tokens=100, n=1, stop=None, temperature=0.3, ) recommendation = response.choices[0].text.strip() song_title = extract_song_title(recommendation) return song_title def get_youtube_music_link(song): youtube = googleapiclient.discovery.build( "youtube", "v3", developerKey=YOUTUBE_API_KEY ) request = youtube.search().list( part="id,snippet", type="video", videoCategoryId="10", # 추가 - 음악 카테고리만 해당 q=song, videoDefinition="high", maxResults=1, fields="items(id(videoId),snippet(publishedAt,channelId,channelTitle,title,description))", ) response = request.execute() if response["items"]: video_url = ( f"https://www.youtube.com/watch?v={response['items'][0]['id']['videoId']}" ) html_doc = requests.get(video_url) soup = BeautifulSoup(html_doc.text, "html.parser") meta_tags = soup.find_all("meta") og_title = None og_url = None for tag in meta_tags: if "property" not in tag.attrs: continue if tag.attrs["property"] == "og:title": og_title = tag.attrs["content"] if tag.attrs["property"] == "og:url": og_url = tag.attrs["content"] if og_title is not None and og_url is not None: break return og_title, og_url else: return "찾을 수 없습니다." def recommend_music_and_link(content): recommendation = gpt_music_recommendation(content) youtube_link = get_youtube_music_link(recommendation) return recommendation, youtube_link
Uploaded by
N2T'프로젝트' 카테고리의 다른 글
[KPT 회고] AI 5기 Django 프로젝트 A-4조 (0) | 2023.05.31 |
---|---|
[KPT 회고] AI 5기 머신러닝 프로젝트 B-3조 (0) | 2023.05.31 |
[팀프로젝트] DRF project - 마셔보장 S.A (0) | 2023.05.16 |
[KPT 회고] AI 5기 장고 프로젝트2 B-5조 (2) | 2023.05.16 |
발표 피드백 (0) | 2023.04.17 |
댓글