Leta Learns

billboard HOT 100 | 210806 본문

토이 프로젝트/Billboard HOT 100

billboard HOT 100 | 210806

leta 2021. 8. 8. 23:25

 

미니 해커톤 후 팀원 한 명과 토이 프로젝트를 하기로 했다.

개강 전에 미니 해커톤을 한 번 더 진행할 계획이 있다고 들어서 그 전에 조금이라도 더 실력을 키워놓고 싶었다.

 

주제 : billboard HOT 100 🔥

 

 

미니 해커톤때는 사실 CSS에 손도 안 댔어서 이번에는 CSS 열심히 하는 게 제1 목표였다.

두 번째 목표는 git 좀 더 능숙하게 사용해보기.

 

 


 

1. API 통신

 

장고 기본 세팅을 마치고 API 받아오는 작업을 했다.

미니 해커톤 때는 API 받아오는 작업을 팀원이 해서 이번 프로젝트에서는 이 작업을 내가 해보기로 했다. (팀원의 도움 하에.. ㅋㅋ)

 

우선, 모델을 먼저 정의해주었다.

models.py

from django.db import models

# Create your models here.
class Music(models.Model):
    title = models.CharField(max_length=200)
    singer = models.CharField(max_length=100)
    rank = models.CharField(max_length=50)
    weeks_on_chart = models.CharField(max_length=100)
    last_week = models.CharField(max_length=100, null=True)
    peak_date = models.CharField(max_length=100)
    peak_rank = models.CharField(max_length=10)
    debut_date = models.CharField(max_length=100)
    debut_rank = models.CharField(max_length=10)
    artist_url = models.CharField(max_length=500, null=True)
    artist_imgurl = models.CharField(max_length=500, null=True)
    cover_imgurl = models.CharField(max_length=500, null=True)

class Producer(models.Model):
    number = models.ForeignKey(Music, null=True, on_delete=models.CASCADE)
    name = models.CharField(max_length=200)
    
class Writer(models.Model):
    number = models.ForeignKey(Music, null=True, on_delete=models.CASCADE)
    name = models.CharField(max_length=200)

 

 

그 다음, views.py에 init_db 함수를 정의하였다.

views.py

def init_db(request):
    url = "https://billboard2.p.rapidapi.com/hot_100"
    querystring = {"date":"2021-08-05"}
    headers = {
        'x-rapidapi-key': "337bab386fmshf0fcb4095596e1ep1bd2b7jsn5b64864e656b",
        'x-rapidapi-host': "billboard2.p.rapidapi.com"
        }
    res = requests.request("GET", url, headers=headers, params=querystring)
    responses = res.json()
    for response in responses:
        new_music = Music()
        new_music.title = response['title'].replace(''', "'")
        new_music.singer = response['artist_name']
        new_music.rank = response['rank']
        new_music.weeks_on_chart = response['history']['weeks_on_chart']
        new_music.last_week = response['history']['last_week']
        new_music.peak_date = response['history']['peak_date']
        new_music.peak_rank = response['history']['peak_rank']
        new_music.debut_date = response['history']['debut_date']
        new_music.debut_rank = response['history']['debut_rank']
        new_music.artist_url = 'https://www.billboard.com' + response['artist_url']
        try:
            new_music.artist_imgurl = 'https://charts-static.billboard.com' + response['artist_images']['sizes']['x-small-2x']['Name']
        except:
            new_music.artist_imgurl = "https://scontent-ssn1-1.xx.fbcdn.net/v/t1.6435-9/117591858_2956021041345448_8920210974041066255_n.jpg?_nc_cat=104&ccb=1-4&_nc_sid=730e14&_nc_ohc=LGy_GR22QT8AX_zN9mU&_nc_ht=scontent-ssn1-1.xx&oh=9141469a34783edd464bb2765494dbee&oe=61317778"
        try:
            new_music.cover_imgurl = 'https://charts-static.billboard.com' + response['title_images']['sizes']['medium']['Name']
        except:
            new_music.cover_imgurl = "https://scontent-ssn1-1.xx.fbcdn.net/v/t1.6435-9/117591858_2956021041345448_8920210974041066255_n.jpg?_nc_cat=104&ccb=1-4&_nc_sid=730e14&_nc_ohc=LGy_GR22QT8AX_zN9mU&_nc_ht=scontent-ssn1-1.xx&oh=9141469a34783edd464bb2765494dbee&oe=61317778"
        new_music.save()

        for producer in response['credited_producers']:
            new_producer = Producer()
            new_producer.number = new_music #new_music의 id가 들어감
            new_producer.name = producer['producer_short_name']
            new_producer.save()

        for writer in response['credited_writers']:
            new_writer = Writer()
            new_writer.number = new_music
            new_writer.name = writer['writer_short_name']
            new_writer.save()

    return redirect('home')

 

 

init_db 함수를 완성한 다음 urls.py에 db의 path를 설정해준 다음 http://127.0.0.1:8000/chart/db 로 들어가면 빌보드 db를 받아올 수 있다.

 

urls.py

from django.contrib import admin
from django.urls import path
from chart.views import *

app_name = 'chart'

urlpatterns = [
    #path('admin/', admin.site.urls),
    path('<int:id>', detail, name='detail'),
    path('db', init_db, name='init_db'),
]

 

 

예상한대로 API 받아오는 과정이 쉽지 않았다. 

모델에서 지정한 필드들 중 None 값들도 많아서 이 None 값들을 처리해주는 과정을 하나하나 해결해나가느라 시간이 꽤 걸렸다.

이 과정에서 앨범 커버가 None 값인 경우 엄청 귀여운 새끼 골든 리트리버 사진을 넣어주었다 ! 🐾

 

 

API 받아온 후 최초 home.html

 

 

 

 

 

 

2. Home 페이지

home에는 우리가 받아온 billboard hot 100을 paginator를 사용하지 않고 한 페이지에 띄웠다.

 

2일 차에 search 기능도 추가했는데 이에 대한 포스팅은 2일 차 포스팅에 따로 할 것이다.

우선 최종 home.html을 첨부한다.

 

home.html

{% extends 'base.html' %}
    {% block content %}
        <head>
            <style>
                .horizon {
                    display: flex;
                    flex-direction: row;
                    margin-top: 10px;
                    margin-bottom: 10px;
                    width: 100%;
                    align-items: center;
                    font-family: 'GmarketSansBold';
                }
                .rank {
                    width: 120px;
                }
                .cover {
                    margin-right: 20px;
                }
                .title {
                    margin-right: 50px;
                    text-decoration: none;
                    color: black;
                    font-size: 18pt;
                }
                .singer {
                    text-decoration: none;
                    color: black;
                    font-size: 10pt;
                }
                .right{ text-align: right;
                    flex: auto;
                }
                .date {font-size: 9pt;}
                @font-face {
                    font-family: 'GmarketSansBold';
                    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansBold.woff') format('woff');
                    font-weight: normal;
                    font-style: normal;
                }
            </style>
        </head>
        <h3 style="font-family: 'GmarketSansBold'; margin-top: 40px;margin-bottom: 20px; font-size: 30pt;">billboard 🔥 100</h3>
        <h5 style="font-family: 'GmarketSansBold'; margin-top: 20px;margin-bottom: 40px; font-size: 14pt; color:rgb(68, 83, 83)">2021-08-05</h5>
        {% if length < 100%}
            <p style="font-family: 'GmarketSansBold'; margin-top: 40px;margin-bottom: 40px; font-size: 20pt;">{{length}} found</p>
        {% endif %}
        <hr>
        {% for music in musics %}
            <div class="horizon">
                <hr>
                <h2 class="rank">{{music.rank}}</h2>
                <a href="{% url 'chart:detail' music.id %}"><img class="cover" src="{{ music.cover_imgurl }}" alt="" width="180" height="180"></a>
                <a class="title" href="{% url 'chart:detail' music.id %}"><p>{{ music.title }}</p></a>
                <div class="right">
            
                    <a class="singer" href="{{ music.artist_url }}">{{ music.singer }}</a>
                    <p style="text-align: right;" class="date"> {{music.debut_date}} </p>
                
                </div>
            </div> <hr>
        {% endfor %}
    {% endblock %}

 

 

API 통신 받아온 다음 CSS를 이용하여 대충 꾸며준 후, CSS 마무리는 둘째 날에 진행하였다.

구현 영상은 2일차 포스팅에서 올려야지.

 

 

 

 

 

 

3. Detail 페이지

디테일 페이지에서는 우리가 받아온 db의 정보들을 모두 넣어주려고 노력했다.

장고 실습도 나름 여러 번 했고, 미니 해커톤도 했었기에 정보들을 받아오는 건 그렇게 어렵지 않았다.

덕분에 CSS를 더 신경쓸 수 있었다. 

 

base.html에서 NavBar와 footer를 구분한 다음 이를 상속받아서 detail.html을 만들었다.

NavBar와 footer 수정은 둘째 날에 본격적으로 했기 때문에 2일 차 포스팅에 올릴 예정.

 

 

detail.html

{% extends 'base.html' %}
{% load static %}
    {% block content %}
    <head>
        <style type="text/css">
            #detail {
                display: flex;
                margin-bottom: 20px;
            }
            #cover {
                margin-right:20px;
                margin-top: 10px;
            }
            * {
                text-align: left;
            }

            #information {
                width: 1000px;
                margin-top: 10px;
                font-family: 'ChosunSg';
            }
            .canv {background-color: whitesmoke;}

            @font-face {
                font-family: 'LAB디지털';
                src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-07@1.0/LAB디지털.woff') format('woff');
                font-weight: normal;
                font-style: normal;
            }
            @font-face {
                font-family: 'twaysky';
                src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_tway@1.0/twaysky.woff') format('woff');
                font-weight: normal;
                font-style: normal;
            }
            @font-face {
                font-family: 'ChosunSg';
                src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@1.0/ChosunSg.woff') format('woff');
                font-weight: normal;
                font-style: normal;
            }
        </style>
    </head>

      <span style="text-shadow: 2px 2px 0px #999; font-size:55px;"><b>{{ music.rank }}</b></span>
      <span style="font-size:40px; margin-left: 10px;"><b>{{ music.title }}</b></span><br>
        <div id='detail'>
        <img id='cover' src="{{ music.cover_imgurl }}" alt="" width="300" height="300">
        <div id='information'>
            <h5><strong>{{ music.title}}</strong></h5>
            <span>{{ music.singer }}</span><br><br>
            <span style="margin-right: 20px;"><strong>발매일</strong> </span>
            <span>{{ music.debut_date }}</span><br>
            <span style="margin-right: 20px;"><strong>작곡가</strong> </span>
            {% for producer in producers %}
                <span style="margin-right: 7px;">{{ producer.name }}</span>
            {% endfor %}
            <br>
            <span style="margin-right: 20px;"><strong>작사가</strong> </span>
            {% for writer in writers %}
                <span style="margin-right: 7px;">{{ writer.name }}</span>
            {% endfor %}
            <br>
        </div>
        </div>
        <br>
        <canvas class="canv" width="1000px" height="400px">

        </canvas>
  
        <script>
            const canv = document.querySelector(".canv");
            const context = canv.getContext('2d');
    
            context.font = "12pt LAB디지털";
    
            const debut_date = "{{music.debut_date}}";
            const last_date = "2021-07-29";
            const current_date = "2021-08-05"
            context.fillText(debut_date,30,370);
            context.fillText(last_date,315,370);
            context.fillText(current_date,595,370);
    
            context.moveTo(30,340);
            context.lineTo(680,340);
            context.stroke();
    
            const debut_rank = "{{music.debut_rank}}";
            const last_rank = "{{music.last_week}}";
            const current_rank = "{{music.rank}}";
    
            const debut_pos = 35 + (3*debut_rank);
            const last_pos = 35 + (3*last_rank);
            const current_pos = 35 + (3*current_rank);
    
            context.font = "12pt twaysky";
    
            context.fillText(debut_rank,60,debut_pos-20);
            context.fillText(last_rank,360,last_pos-20);
            context.fillText(current_rank,660,current_pos-20);
    
            context.moveTo(60,debut_pos);
            context.lineTo(360,last_pos);
            context.lineTo(660,current_pos);
            context.stroke();
    
            context.strokeStyle = 'lightgray';
    
            context.beginPath();
            context.moveTo(60,debut_pos);
            context.lineTo(60,340);
            context.stroke();
    
            context.moveTo(360,last_pos);
            context.lineTo(360,340);
            context.stroke();
    
            context.moveTo(660,current_pos);
            context.lineTo(660,340);
            context.stroke();
    
            const weeks_on_chart = '{{music.weeks_on_chart}}';
            const peak_date = '{{music.peak_date}}';
            const peak_rank = '{{music.peak_rank}}';
    
            context.font = "50pt twaysky";
            context.fillText(weeks_on_chart, 720, 130);
            context.font = "12pt twaysky";
            context.fillText('weeks on chart', 735, 155);
    
            context.font = "50pt twaysky";
            context.fillText(peak_rank, 720, 230);
            context.font = "12pt twaysky";
            context.fillText('peaked at '+peak_date, 735, 255);
    
          </script>

    <hr>
      <h3 style="font-family: 'ChosunSg';"><strong>Artist</strong></h3>
        <div id='detail'>
        <img id='cover' src="{{ music.artist_imgurl }}" alt="" width="150" height="150">
        <div id='information'>
            <span>{{music.singer}}</span><br>
            <a href="{{ music.artist_url }}">more</a>
        </div>
        </div>
    <br><br>
    {% endblock %}

 

<발매일 음원 순위, 지난 주 음원 순위, 현재 음원 순위, 차트인 했던 주 수, 제일 높았던 순위>

이 다섯 가지를 한 눈에 보여주는 그래프를 만들었다.

내가 한 작업은 아니고 팀원이 도맡아서 했는데 폰트도 예쁘게 입혀져서 디테일 페이지가 좀 더 고퀄리티가 된 계기라고 생각한다.

 

디테일은 중간 스크린샷이 없어서 우선 최종본으로 올린다.

디테일 페이지 최종 구현 영상

 

 

 


 

 

 

API 통신이 어려웠던 것만 빼면 전반적으로 미니 해커톤 때보다 훨씬 진행 속도가 빨랐다.

미니 해커톤때 사용한 방식과 크게 다르지 않아서 익숙한 만큼 수월했던 것이라고 생각한다.

첫 날에는 백 작업을 위주로 했는데, 순조롭게 끝나서 둘째날 CSS에 집중할 수 있었다.

git은 사실 아직 잘 모르겠다... 팀원한테 물어봐가면서 같이 했는데 쉽지 않아서 나중에 깃만 따로 공부를 해야할 것 같다.

빌보드 프로젝트 첫 날 리뷰 끝 !

 

 

 

'토이 프로젝트 > Billboard HOT 100' 카테고리의 다른 글

billboard HOT 100 | 210807  (0) 2021.08.09
Comments