비둘기 둥지

[짭하하 / 침하하 개발기] 1. 메인 페이지를 만들어보자 본문

토이 프로젝트/Django - 침하하

[짭하하 / 침하하 개발기] 1. 메인 페이지를 만들어보자

KimDove 2022. 6. 21. 22:10
728x90

0. 들어가기 전

  • 보고 계신 게시물은 현재 서비스 되고 있는 침하하 페이지의 개발기가 아닌,
    김둘기의 개인 프로젝트를 위한 침하하임을 알립니다.

1. 본격적인 개발을 시작해 보자

1- 1. main 앱 생성

  • 프로젝트에서 기능을 구현할 app (main)을 하나 생성한다.
  • 이 앱은 침하하 홈페이지 맨 처음 들어갈 때 표시되는 페이지가 보이도록 구성한다.
  • 터미널에 django-admin startapp main을 입력하여 app을 생성한다.

[그림 6] main app 생성

  • chimhaha/config/urls.py 파일에 main/ url이 요청되면 chimhaha/main/urls.py 호출
  • chimhaha/main 폴더에 urls.py를 생성하여 url 매핑을 추가한다.
    e.g) main/urls.py 파일 안에 ' '은 아래 표와 같이 동작한다.
config/urls.py main/urls.py 최종 url
'main/' ' ' 'main/'
'main/' 'videos/' 'main/videos'
  • dove-web.duckdns.org/main 으로 접속하면, main/view.py 파일 안 index 함수가 실행된다.
## chimhaha/config/urls.py

from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('main/', include('main.urls'))
]

## chimhaha/main/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index)
]
  • main/view.py 에서 templates/index.html 페이지를 호출하도록 하는 함수를 생성한다.
from django.shortcuts import render
# Create your views here.

def index(request):
    return render(request, 'index.html', {})

1- 2. 템플릿 적용하기

  • main 앱에서 index.html을 호출하도록 생성하였으므로, 이제 index.html에 탬플릿을 적용할 차례이다.
  • html에 템플릿을 적용하기 위해 몇가지 추가해야 할 내용이 있다.
    (1) 템플릿을 적용하고자 하는 html 파일의 맨 윗부분에 {% load static %}을 입력한다.
    (2) 사용하고자하는 이미지 파일, javascript 파일 앞을 사용하는 태그 안에
          {% static '소스 경로' %}를 추가한다. (이 부분은 말로 설명하기 어려우니 코드를 보자)
  • 위 과정을 반복하고 나서 웹 페이지에 접속해보면 템플릿이 적용된 모습을 볼 수 있다.
## templates/index.html
## 추가한 내용

{% extends 'main.html' %}
{% load static %}

(... 중략 ...)

<div class="header_resize">
    <div class="logo">
      <h1><a href="index.html">침하하<sup>alpha</sup></a></h1>
    </div>
    <div class="menu_nav">
      <ul>
        <li class="active"><a href="#"><span>메인 페이지</span></a></li>
        <li><a href="#"><span>폭소 게시판</span></a></li>
        <li><a href="#"><span>침착맨 일기</span></a></li>
        <li><a href="#"><span>침투부 채널</span></a></li>
      </ul>
      <div class="clr"></div>
    </div>
    <div class="clr"></div>
    <!-- 이 부분이 (2)번에 해당하는 내용이다. -->
    <div class="header_img"><img src={% static "images/banner.png" %} alt="" width="970" height="320" /></div>
</div>

[그림 1] 템플릿을 적용한 홈페이지 화면

1- 3. 유튜브 api로 침투부에 있는 영상들 가져오기 

  • 침투부에 영상이 올라올 때 침하하에도 자동으로 업데이트될 수 있도록
    youtube api를 이용해 침투부에 있는 데이터들을 가져오도록 한다.
  • youtube api키를 받는 방법은 아래 블로그에서 참고하였다
 

[youtube api] 유튜브 데이터 가져오기

유튜브의 영상데이터를 가지고 와서 브라우저에 보여주는 방법을 알아보자. 만약 react나 nodejs를 통해 구축환경을 고려하고 있다면, 아래의 url을 참고하자. [Web/nodejs] - nodejs 기초 총 정리 기본적

han-py.tistory.com

 

  • youtube api를 이용하여 영상정보를 가져오는 파이썬 코드는 아래와 같이 작성하였다.
## 필요한 패키지 로드
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from oauth2client.tools import argparser
from itertools import chain
from urllib import request
import pandas as pd
import numpy as np
## 채널의 플레이리스트에 있는 영상 정보 가져오는 함수
def search_videos(youtube, id, max_result = 200):
    ## playlistId에 해당하는 영상 찾기
    ## max_result의 갯수 만큼 영상을 찾아 가져옴.
    result = youtube.playlistItems().list(
                playlistId = id,
                part       = 'snippet',
                maxResults = max_result
            ).execute()
    titles, ids, thumbnails, dates = [], [], [],[]
    
    for video in result['items']:
        snippet = video['snippet']
        
        ## 비공개 동영상인 경우에 그냥 넘기기 
        if 'private' in snippet['title'].lower(): continue
        
        titles.append(snippet['title'])
        ids.append(snippet['resourceId']['videoId'])
        dates.append(snippet['publishedAt'])
        
        ## 썸네일에 medium 이미지가 없는 경우 default 이미지로 가져오기
        try: thumbnails.append(snippet['thumbnails']['medium']['url'])
        except: thumbnails.append(snippet['thumbnails']['default']['url'])
        
    return (dates, titles, ids, thumbnails)
##채널 내 플레이리스트의 id 가져오는 함수
def search_playlists(youtube, id, max_result = 100):
    ## channelId에 해당하는 플레이리스트 찾기
    ## max_result의 갯수 만큼의 플레이리스트를 찾아 가져옴.
    result = youtube.playlists().list(
                channelId  = id,
                part       = 'snippet',
                maxResults = max_result
            ).execute()
    
    ## 플레이리스트 id 저장 
    ids = []
    for playlist in result['items']: ids.append(playlist['id'])
    return ids
    
    ## 채널부터 시작해 영상 목록까지 가져오는 함수
def search_datas(query, max_result = 30):
    youtube = use_api()
    
    ## query에 해당하는 채널 이름 찾기
    ## max_result의 갯수 만큼 채널을 찾아 가져옴.
    result  = youtube.search().list(
                q          = query,
                part       = 'snippet',
                maxResults = max_result
            ).execute()
    
    ## youtube api에서 가져온 데이터가 json 데이터에 좀 복잡한 구조로 되어있는데,
    ## 채널 id는 channel_id 변수처럼 가져올 수 있다.
    channel_id = result['items'][0]['id']['channelId']
    
    ## 채널 내 플레이리스트 id 목록 가져옴.
    playlists  = search_playlists(youtube, channel_id)
    
    ## 영상 업로드 날짜, 제목, id, 썸네일 링크를 저장
    dates, titles, ids, thumbnails = [], [], [], []
    for playlist in playlists: 
        (d, t, i, th) = search_videos(youtube, playlist)
        dates      = list(chain(dates    , d))
        titles     = list(chain(titles   , t))
        ids        = list(chain(ids      , i))
        thumbnails = list(chain(thumbnails, th))
    
    ## 저장한 영상정보 데이터를 DataFrame으로 변환
    df         = pd.DataFrame([dates, titles, ids, thumbnails]).T
    df.columns = ['dates', 'titles', 'ids', 'thumbnails']
    return df
## 채널 id로는 침착맨으로만 가져오기 위해 max_result = 1로 지정하였다.
df = search_datas('침착맨', max_result = 1)

## 날짜 내림차 순으로 정렬해주는 부분
df.sort_values('dates', ascending = False)

## 침투부에서 가져온 영상 정보를 csv 파일로 저장하는 부분
df.to_csv('chim_videos.csv', index = False)

[그림 2] 침투부 영상 정보를 저장한 csv파일

  • 코드를 작성하며 발견하게된 내용인데, youtube api로 가져올 수 있는 영상 정보는
    재생목록이 지정되어 있는 영상들에 한에서 가져올 수 있다는 것이다. 
    < ! > 채널 내 모든 영상을 가져오는 것은 좀 더 찾아봐야 할 것 같다.

1- 4. 영상 정보를 DB에 저장시키기

  • 침투부 영상을 추천해주는 페이지를 개발하기 위해 chim_channel이라는 새로운 앱을 생성하였다.
  • youtube api에서 받아온 영상 데이터를 DB에 저장하기 위해 chim_channel/models.py를 작성하였다.
## chim_channel/models.py

from django.db import models

# Create your models here.
class Videos(models.Model):

    ## published_date          | 날짜형 데이터를 가진 필드
    ## titles, ids, thumbnails | 길이에 제한이 없는 문자열을 가진 필드
    published_date = models.DateTimeField()
    titles         = models.TextField()
    ids            = models.TextField()
    thumbnails     = models.TextField()
    
    def __str__(self):
        return f'published date : {self.published_date} | title : {self.titles}'
  • 1-3.에서 수집한 영상 데이터를 DB에 입력하기 위해 터미널에서 python manage.py shell
    Django Shellscript로 진입한다.
  • 쉘 스크립트에서 아래 코드를 입력하여 데이터를 추가한다.

[그림 3] 장고 쉘 스크립트 모드
[그림 4] csv 파일에 저장되어 있는 데이터를 DB에 입력하는 코드

  • 이제 마지막으로 침투부에서의 최신 영상을 가져와보자.
  • 메인 페이지에서 침투부의 최신 영상을 표시할 것이기 때문에, main/views.py
    templates/index.html
    을 수정하였다.
## main/views.py

from django.shortcuts import render
from chim_channel.models import *
from datetime import datetime
# Create your views here.

def index(request):

    ## DB에서 가장 최신 영상 가져오는 코드
    video_db = Videos.objects.order_by('-published_date')[0]

    ## iso8061 시간대 형식을 변환해주는 코드 
    published_date = str(video_db.published_date)
    published_date = datetime.fromisoformat(published_date)

    ## UTC 시간대에서 한국 시간대로 맞춰주는 코드 
    published_date = published_date.timestamp() + 32400
    published_date = datetime.fromtimestamp(published_date).strftime('%Y/%m/%d - %H:%M:%S')
	
    context = {'videos' : video_db, 'published_date' : published_date}
    return render(request, 'index.html', context = context)
  • Django에서 전달받은 데이터를 웹 페이지에 표시하기 위해 템플릿 언어라는 것을 사용한다.
  • 파이썬의 함수를 웹 페이지에서도 사용할 수 있도록 템플릿 태그라는 것도 사용하였다.
  • main 폴더 안에 templatetags라는 폴더를 생성하고, 그 안에 main_filters.py를 생성하여 
    add_urlpath 함수를 작성하였다.
    < ! > python 파일 이름은 아무거나 사용해도 상관 없지만, 폴더 이름은 templatetags로 고정해야한다.
## main/templatetags/main_filters.py

from django import template
register = template.Library()

## 썸네일 이미지를 클릭하면 바로 유튜브로 넘어가도록 하게해주는 함수
## e.g) url : QsuhIGOXxfs&t=1704s => 반환값 : https://www.youtube.com/watch?v=QsuhIGOXxfs&t=1704s
@register.filter
def add_urlpath(url):
    return f'https://www.youtube.com/watch?v={url}'
## templates/index.html

{% extends 'main.html' %}
{% load main_filters %}
{% load static %}

{% load main_filters %}

(... 중략 ...)
<div class="mainbar">
          <div class="article">
            <h2><span><b>오늘의 침투부 영상</b></span></h2>
            <hr>
            <div class="clr"></div>
            <div class="row">
              <div>
              <!-- videos.ids 변수를 add_urlpath 함수에 입력하여 반환된 결과를 url로 정의 -->
              <!-- add_urlpath는 파이썬으로 정의된 함수이다. -->
              <!-- 그냥 videos.ids는 QsuhIGOXxfs&t=1704s처럼 되어 있어, 영상으로 넘어가도록 url 링크를 수정해줌.-->
                {% with videos.ids|add_urlpath as url%}
                 <!-- 변수를 사용할 때는 이중 중괄호가 변수명을 감싸는 형태를 띈다.-->
                  <a href={{ url }}>
                  	<!-- 영상 썸네일이 입력되는 부분 -->
                    <img src={{ videos.thumbnails }} width="320" height="179" alt="" />
                  </a>
                {% endwith %}
              </div>
              <div>
              	<!-- 영상 제목과 올라온 날짜가 입력된 부분-->
                <span style="font-size:21px;"><b>■ {{ videos.titles }}</b></span>
                <p>posted on | {{ published_date }}</p>
              </div>
            </div>
            <div class="clr"></div>
            <hr>
          </div>
        </div>
(... 생략 ...)
  • 위 코드를 모두 입력하고 나면 홈페이지는 다음과 같이 변한다.

[그림 5] views.py와 index.html 파일 수정한 결과

  • 지금까지 메인 앱 생성, 템플릿 적용, youtube api를 이용한 영상 정보 가져오기 까지 적용하였다.

 

  • 개발되고 있는 코드들은 아래 링크에 저장되어 있습니다.
 

GitHub - AhMuGeoNa/ChimHaha: 웃음의 알렉산드리아 도서관 침하하 페이지를 만들어보자

웃음의 알렉산드리아 도서관 침하하 페이지를 만들어보자. Contribute to AhMuGeoNa/ChimHaha development by creating an account on GitHub.

github.com

 

999. 참고 자료

999 - 1. Django 코드

 

점프 투 장고

**점프 투 장고 오프라인 책 출간 !! (2020.12)** * [책 구입 안내](https://wikidocs.net/105844) 점프 투 장고는 파이 ...

wikidocs.net

999 - 2. youtube api 코드

 

[youtube api] 유튜브 데이터 가져오기

유튜브의 영상데이터를 가지고 와서 브라우저에 보여주는 방법을 알아보자. 만약 react나 nodejs를 통해 구축환경을 고려하고 있다면, 아래의 url을 참고하자. [Web/nodejs] - nodejs 기초 총 정리 기본적

han-py.tistory.com

 

728x90
Comments