개요: 여행가면서 듣기 좋은 음악을 추천하는 SNS 서비스 Ca(raudio) Mu(sic) (였는데..?)
프로젝트 정보
- 프로젝트: SNS서비스 CaMu (Django 기초 팀 프로젝트)
- 개발기간: 2023.04.10 - 2023.04.13 ( 4일 )
- 역할
- 메인페이지
- 전체 레이아웃
- css
- 사용 언어: Python
- 사용 라이브러리:
- Python
- Django (풀스택 프레임워크)
- Pillow (이미지 업로드)
- ytmusicapi (유튜브 api)
- Python
프로젝트 구성
★ 파일구조
관리 앱 Django_Team_Project, 댓글 앱 comment, 메인 컨텐츠 앱 tweet, 유저 관리 앱 user으로 구성
CaMu comment
┣ templates
┃ ┗ comment_test.html
┣ admin.py
┣ apps.py
┣ models.py
┣ tests.py
┣ urls.py
┣ views.py
┗ __init__.py
Django_Team_Project
┣ asgi.py
┣ settings.py
┣ urls.py
┣ wsgi.py
┗ __init__.py
templates
┗ base.html (모든 페이지에 공통적으로 들어가는 상단 nav바)
tweet
┣ migrations
┃ ┗ __init__.py
┣ static
┃ ┗ tweet
┃ ┣ css
┃ ┃ ┗ styles.css
┃ ┗ image
┃ ┣ ad-1.png (이스터에그 광고)
┃ ┣ edit_btn.svg
┃ ┣ logo-camu.png (CaMu 로고)
┃ ┗ user_image.png (유저 기본 이미지)
┣ templates
┃ ┗ tweet
┃ ┣ create_post.html
┃ ┣ home.html
┃ ┣ my_page.html
┃ ┣ post_detail.html
┃ ┣ set_post.html
┃ ┗ set_profile.html
┣ admin.py
┣ apps.py
┣ models.py
┣ oauth.json
┣ tests.py
┣ urls.py
┣ views.py
┗ __init__.py
user
┣ migrations
┃ ┗ __init__.py
┣ templates
┃ ┗ user
┃ ┣ signin.html
┃ ┗ signup.html
┣ admin.py
┣ apps.py
┣ models.py
┣ tests.py
┣ urls.py
┣ views.py
┗ __init__.py
manage.py
requirements.txt
db.sqlite3
★ 화면구성 및 코드
1. 상단 로고 및 상단 nav바
모름지기 보기 좋은 화면이 여기저기 들어가보고 싶은 법.
포토샵을 이용해 간단하게 로고를 만들었다
단 두개의 텍스트 레이어만 가지고 만들었다
CaMu와 ..
u위에 ..을 배치해 웃는 표정을 만들어보고 싶었다
이제 로고를 어떻게 배치했는지 상단nav바 html을 보자
<!-- templates/base.html -->
<div class="nav nav-wrap">
<div class="navbar-container">
<!-- 상단 로고 -->
<div>
<a class="logo" href="/"></a>
</div>
(..skip..)
아주 심플하다
가장 위의 div에는 nav, nav-wrap 클래스를 주고 그 안의 실제 컨텐츠가 들어 있는 div에는 navbar-container를 줬다
로고는 배경으로 줬기 때문에 html파일에서는 클릭시 최상단 페이지로 이동하는 href='/' 속성만 붙어 있다
그렇다면 css는 어떨까
/* tweet/static/tweet/css/styles.css */
.logo {
display: flex;
margin: 0 auto;
width: 200px;
height: 70px;
background-image: url("../image/logo-camu.png");
background-repeat: no-repeat;
background-size: 100% auto;
background-position: center;
filter: invert(100%);
}
background-image로 이미지 파일을 불러오고 크기에 맞게 각종 옵션을 줬다
2. 사용자 드롭다운 메뉴
사용자 로그인 후에 상단 nav바에 나타나는 드롭다운 메뉴다
사용자 프로필과 함께 글쓰기, 프로필 보기, 로그아웃 세 가지 핵심기능으로 이동하는 기능을 가지고 있다
만약 로그아웃을 한다면 위와 같이 로그인/회원가입으로 바뀌도록 했다
이 부분의 django template을 보자
<div class="menu">
{% if user.is_authenticated %}
<li class="nav-item dropdown">
<div class="box" href="#" style="background: #BDBDBD; float:left; margin: 0 4px 10px;">
{% if user.image %}
<img class="profile" src={{ user.image }}>
{% elif not user.image %}
<img class="profile" src={% static 'tweet/image/user_image.png' %}>
{% endif %}
</div>
<a class="nav-link dropdown-toggle" href="#" role="button" style="color: white;"
data-bs-toggle="dropdown" aria-expanded="false">
{{ user.username }}
</a>
<ul class="dropdown-menu dropdown-menu" style="padding: 5px;">
<li><a class="dropdown-item" href={% url 'create-post' %}>글쓰기</a></li>
<li><a class="dropdown-item" href={% url 'my-page' user.pk %}>프로필 보기</a></li>
<li><a class="dropdown-item" href={% url 'logout' %}>로그아웃</a></li>
</ul>
</li>
{% else %}
<div>
<p style="line-height: 45px; color: white;">
<a href={% url 'sign-in' %} class="sign-text">로그인하기</a> / <a href={% url 'sign-up' %} class="sign-text">회원가입 </a>
</p>
</div>
{% endif %}
</div>
menu라는 클래스를 가진 div는 컨텐츠를 nav바 우측 상단에 고정하도록 해준다
이제 내부 컨텐츠에서는 두개의 템플릿 if 태그로 유저 인증여부와 프로필 사진 설정 여부를 판단해 각각 화면을 보여준다
user.is_authenticated, 즉 유저가 로그인 되어 있다면 로그인된 화면을 보여주고 로그인 되어 있지 않다면 로그인하기 화면을 보여준다
그리고 로그인된 유저 중에 개별 프로필 사진을 설정한 유저가 있는 지 안의 if문으로 판단하고 여부에 따라 유저 프로필 사진 혹은 기본 이미지를 보여준다
프로필 사진은 회원가입 시에 업로드하도록 만들어 줬다
프로필 사진을 업로드했다면 프로젝트 안의 media폴더에 저장하고 그 url을 user db에 저장해놨다가 불러올 수 있도록 했다
회원가입 기능 중에 프로필 사진 업로드 기능의 view를 보자
# user/views.py
from .models import UserModel
from django.shortcuts import render, redirect
from django.core.files.storage import FileSystemStorage
import random
# 회원가입 기능
def sign_up_view(request):
if request.method == 'GET':
[..skip..]
elif request.method == 'POST': # POST request일 경우
[..skip..]
# profile-image라는 name을 가진 데이터가 있다면 profile_image변수에 넣어줌
try:
profile_image = request.FILES['profile-image']
# 그렇지 않으면 None을 넣어줌
except:
profile_image = None
[..입력 데이터 판별 코드 skip..]
# 판별이 끝나고 문제가 없다면
else:
# UserModel클래스에 새로운 유저를 만들어 주고 그 유저의 정보를 new_user에 저장
new_user = UserModel.objects.create_user(
username=username, password=password)
# 만약 프로필 사진 데이터가 있다면
if profile_image:
# 프로필사진 파일 이름을 랜덤하게 변경! user2_19232.png 형태
profile_image.name = 'user' + str(new_user.user_id) + '_' + str(
random.randint(10000, 100000)) + '.' + str(profile_image.name.split('.')[-1])
# 파일 저장
file_system_storage = FileSystemStorage()
fs = file_system_storage.save(
profile_image.name, profile_image)
# 저장한 파일 url 따기
uploaded_file_url = file_system_storage.url(fs)
# 신규 회원의 id를 다시 검색해 이미지 url 업데이트
check = UserModel.objects.filter(user_id=new_user.user_id)
check.update(image=uploaded_file_url)
return redirect('/sign-in') # 회원가입이 완료되었으므로 로그인 페이지로 이동
input태그의 type이 file일 경우 로컬에 있는 파일 이름이 그대로 저장됐다.
나는 파일 이름이 랜덤하길 원했기 때문에 중간의 코드와 같이 user[유저 id]_[10000~100000사이 랜덤값].[확장자]꼴로 변경시켜주었다
FileSystemStorage()등의 파일 업로드 클래스를 사용하기 위해선 setting.py와 urls.py도 변경시켜 주어야 한다
setting.py를 보자
# Django_Team_Project/setting.py
import os
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
세팅 파일에 media가 어디 저장될 것인지 위치를 알려준다
장고에서 media파일은 프로젝트 단위로 저장하므로 프로젝트 최상단 위치로 지정해주었다.
MEDIA_URL은 최상단 폴더 바로 아래에 media폴더로 지정해 주었고, MEDIA_ROOT는 os.path.join으로 운영체제에 맞게 최상단 폴더의 media 경로를 가리키게끔 했다.
다음으로 urls.py를 보자
# user/urls.py
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns 리스트에 static함수를 추가해준다
이는 MEDIA_URL로 들어오는 요청에 대해 MEDIA_ROOT 경로를 탐색시켜준다
3-5. 메인 컨텐츠와 페이지네이션, 광고
글 작성 시 유튜브 주소를 입력 받는데 저장 시 db에 유튜브 썸네일 url을 함께 저장하도록 했다
그래서 위 컨텐츠에 유튜브 썸네일을 보여주도록 했다.
아래 별점은 댓글과 함께 작성되는데 그 별점을 소숫점 첫째자리까지 반올림해 출력하도록 했다
맨 아래에는 nav바에 사용한 프로필 사진을 재활용해 작성자의 프로필이 노출되고 클릭 시 작성자 프로필 화면으로 이동하도록 했다
이를 보여주는 home.html를 보자
<!-- tweet/home.html -->
<div class="row row-cols-1 row-cols-md-4 g-4">
<!-- 카드 반복 출력 -->
{% for post in page_obj %}
<div class="col">
<div class="card h-100">
<a href={% url 'post-detail' post.pk %} style="text-decoration-line: none;">
<img src={{ post.youtube_thumbnail }} width=100% height=100% class="card-img-top" alt="...">
<div class="card-body">
<div class="like-wrap">
<div style="">
<p class="like">{{ post.avg_star }}</p>⭐
</div>
</div>
<h5 class="card-title main-text" style="font-size: 1.5rem;">{{ post.title }}</h5>
<p class="card-text main-text" style="font-size: 1rem;">{{ post.comment }}</p>
</a>
<div>
<div class="post-author">
<a href={% url 'my-page' post.owner.pk %} role="button" style="color: black; text-decoration-line: none;">
<div class="box" style="background: #BDBDBD; float:left; margin: 0 4px 10px;">
{% if post.owner.image %}
<img class="profile" src={{ post.owner.image }}>
{% else %}
<img class="profile" src={% static 'tweet/image/user_image.png' %}>
{% endif %}
</div>
{{ post.owner }}
</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
<!-- 카드 반복 출력 end -->
</div>
<!-- 페이지네이션 -->
<div style="margin: 0 auto; display: flex;">
<nav aria-label="Page navigation example" style="margin: 0 auto;">
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{page_obj.previous_page_number}}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% for p in page_obj.paginator.page_range %}
<li class="page-item"><a class="page-link" href="?page={{p}}">{{p}}</a></li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{page_obj.next_page_number}}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</div>
</nav>
</div>
<!-- 페이지네이션 end -->
<!-- 광고 출력 -->
{% if user.is_authenticated %}
<div class="ad-1">
<img src={% static 'tweet/image/ad-1.png' %} width="80%">
</div>
{% endif %}
<!-- 광고 end -->
page_obj는 장고 내장 클래스인 Paginator()를 이용해 컨텐츠를 8개씩 잘라 가져온다
그리고 그걸 템플릿 for 태그로 반복적으로 하나씩 출력시켜준다
그리고 중반부의 if문은 nav바의 메뉴와 마찬가지로 작성자의 이미지가 DB에 있다면 노출시켜주고 아니라면 기본이미지를 출력해 보여주도록 했다
페이지네이션은 장고 내장 클래스를 이용해 만들었다
프로젝트 결과물
https://github.com/Churnobyl/Django_Team_Project
프로젝트 완료 후의 감상
Git을 이용한 첫번째 프로젝트였다
처음엔 이 방식이 익숙하지 않아서 merge할 때 많은 충돌이 있었지만 프로젝트 마무리 시기에는 Git을 자연스럽게 다룰 수 있었다
장고를 이용해 SNS를 만들어 봤는데 Paginator, FileSystemStorage, 템플릿 문법 등 페이지 작성 시 편리한 기능들이 아주 많아서 만드는데 수월했다
더 많은 기능들이 있으니 더 열심히 공부해보아야겠다.
'프로그래밍 > 생각나는대로 프로젝트' 카테고리의 다른 글
[프로젝트] 07. 가계부 SNS - 리치메이커 (0) | 2023.05.31 |
---|---|
[프로젝트] 06. DRF 경매 서비스 - 20세기 박물관 (0) | 2023.05.16 |
[프로젝트] 04. Django 무신사 재고 관리 시스템 (0) | 2023.04.10 |
[프로젝트] 03. Text RPG게임 냥이 키우기 (0) | 2023.04.03 |
[프로젝트] 02. 팀 소개 프로젝트 Brain-8 (0) | 2023.03.19 |