Django Study (2) 외부 api, 검색 기능 구현, DB

다율·2024년 7월 24일
0

Django Study

목록 보기
2/4
post-thumbnail

외부 api에서 값 받아오기

mydjango.py

def index(request):
    json_url = "https://raw.githubusercontent.com/pyhub-kr/dump-data/main/melon/melon-20230906.json"

    response = requests.get(json_url)
    # response.raise_for_status()

    if response.ok:
        song_list = response.json()
    else:
        song_list = []
        
    # index.html을 렌더
    return render(request, "index.html", {"song_list": song_list})

index.html

<!--    autocomplete는 자동완성 기능 끄는 것-->
    <form action="" method="get" autocomplete="off">
        <input type="text" name="query" placeholder="검색어를 입력해주세요 : " autofocus
               value="{{ query }}">
<!--            django의 템플릿 문법-->

검색 기능 (filter)

def index(request):
    query = request.GET.get("query", "").strip()     
    # (검색어) 장고에서는 쿼리스트링을 가공한 값을 이렇게 가져올 수 있음
    # 검색어가 없더라도 디폴트 값 "" 반환

    json_url = "https://raw.githubusercontent.com/pyhub-kr/dump-data/main/melon/melon-20230906.json"

    response = requests.get(json_url)
    # response.raise_for_status()

    if response.ok:
        song_list = response.json()
    else:
        song_list = []

    if query:
        song_list = filter(
            lambda song: query in song["가수"],
            song_list,
        )

    # index.html을 렌더
    return render(request, "index.html", {"song_list": song_list, "query": query})

데이터 베이스

데이터 베이스 연동

  • python은 sqlite3를 기본 라이브러리로 갖고 있음
# python의 기본 라이브러리
import sqlite3

connection = sqlite3.connect("melon-20230906.sqlite3")
cursor = connection.cursor()

cursor.execute("SELECT * FROM songs")

column_names = [desc[0] for desc in cursor.description]
print(column_names)

songs_list = cursor.fetchall()

# tuple 타입으로 조회됨
for song_tuple in songs_list :
    song_dict = dict(zip(column_names, song_tuple))
    print(song_dict["곡명"], song_dict["가수"])

connection.close()

데이터 베이스 데이터 검색하기

# python의 기본 라이브러리
import sqlite3

query = "악뮤"
print("검색어 : ", query)

connection = sqlite3.connect("melon-20230906.sqlite3")
cursor = connection.cursor()
# 쿼리문 확인
connection.set_trace_callback(print)

param = '%' + query + '%'
sql = f"SELECT * FROM songs WHERE 가수 LIKE '{param}' OR 곡명 LIKE '{param}'"
# 커서를 통해 쿼리를 수행할 수 있음
cursor.execute(sql)

# 조회한 테이블의 컬럼명을 리스트로 변환
column_names = [desc[0] for desc in cursor.description]
print(column_names)

songs_list = cursor.fetchall()

print("list size", len(songs_list))

# tuple 타입으로 조회됨, 딕셔너리 타입으로 변환 (가독성)
for song_tuple in songs_list :
    song_dict = dict(zip(column_names, song_tuple))
    # print(song_dict["곡명"], song_dict["가수"])
    print("{곡명} {가수}".format(**song_dict))

connection.close()

SQL Injection 공격 방어하기

param = '%' + query + '%'
# sql = f"SELECT * FROM songs WHERE 가수 LIKE '{param}' OR 곡명 LIKE '{param}'"
sql = "SELECT * FROM songs WHERE 가수 LIKE ? OR 곡명 LIKE ?"

# 커서를 통해 쿼리를 수행할 수 있음
cursor.execute(sql, [param,param])

함수로 만들기

  • 빈 쿼리일 경우도 처리함
def index(request):
    query = request.GET.get("query", "").strip()     # (검색어) 장고에서는 쿼리스트링을 가공한 값을 이렇게 가져올 수 있음
    # 검색어가 없더라도 디폴트 값 "" 반환

    song_list = get_song_list(query)

    # query = "정국"  # 검색어
    #
    # song_list = [
    #     {"곡명": "Seven (feat. Latto) - Clean Ver.", "가수": "정국"},
    #     {"곡명": "Love Lee", "가수": "AKMU (악뮤)"},
    #     {"곡명": "Super Shy", "가수": "NewJeans"},
    #     {"곡명": "후라이의 꿈", "가수": "AKMU (악뮤)"},
    #     {"곡명": "어떻게 이별까지 사랑하겠어, 널 사랑하는 거지", "가수": "AKMU (악뮤)"},
    # ]
    # # 파이썬 빌트인 함수 filter를 활용해서, 곡명에 검색어가 포함된 노래만 필터링
    # song_list = filter(lambda song: query in song["가수"], song_list)

    # index.html을 렌더
    return render(request, "index.html", {"song_list": song_list, "query": query})

def get_song_list(query: str):
    connection = sqlite3.connect("melon-20230906.sqlite3")
    cursor = connection.cursor()
    connection.set_trace_callback(print)
    
    # query가 비었을 겨우 처리
    if query:
        param = '%' + query + '%'
        sql = "SELECT * FROM songs WHERE 가수 LIKE ? OR 곡명 LIKE ?"
    
        cursor.execute(sql, [param, param])
    else:
        cursor.execute("SELECT * FROM songs")

    column_names = [desc[0] for desc in cursor.description]
    print(column_names)

    songs_list = []
    # for song_tuple in cursor.fetchall():
        # song_dict = dict(zip(column_names, song_tuple))
        # songs_list.append(song_dict)

    # list comperhensior
    songs_list = [dict(zip(column_names, song_tuple))
                  for song_tuple in cursor.fetchall()]

    connection.close()
    return songs_list

장고 DB 추상화 계층 통해서 connection 얻기

  • 하나의 장고 프로젝트에서 여러 DB 연결이 가능함
  • 이제 이 코드는 DB종류에 상관없이 실행됨
settings.configure(
    ROOT_URLCONF=__name__,
    DEBUG=True,
    SECRET_KEY="secret",
    DATABASES={
        "default": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": "melon-20230906.sqlite3",       # 파일 경로
        }
    },
    TEMPLATES=[
        {
            "BACKEND": "django.template.backends.django.DjangoTemplates",
            "DIRS": ["templates"],
        }
    ],
)
  • connect에 alt + enter 해서 django가 지원하는 연결로 변경
def get_song_list(query: str):
    # connection = sqlite3.connect("melon-20230906.sqlite3")
    cursor = connection.cursor()
    # connection.set_trace_callback(print)

    # query가 비었을 겨우 처리
    if query:
        param = '%' + query + '%'
        sql = "SELECT * FROM songs WHERE 가수 LIKE %s OR 곡명 LIKE %s"

        cursor.execute(sql, [param, param])
    else:
        cursor.execute("SELECT * FROM songs")

    column_names = [desc[0] for desc in cursor.description]
    print(column_names)

    songs_list = []
    # for song_tuple in cursor.fetchall():
        # song_dict = dict(zip(column_names, song_tuple))
        # songs_list.append(song_dict)

    # list comperhensior
    songs_list = [dict(zip(column_names, song_tuple))
                  for song_tuple in cursor.fetchall()]

    # connection.close()
    return songs_list

장고 ORM인 모델 활용

  • DB의 종류와 상관없이 장고가 관리해주는 connection을 가지고 모든 DB에서 동일하게 동작하는 코드
# pip install "django~=4.2.0"
# import sqlite3
# mydjango.py

import sys
import django
import requests
from django.db import models
from django.conf import settings
from django.core.management import execute_from_command_line
from django.db import connection
from django.db.models import Q
# from django.http import HttpResponse
from django.shortcuts import render
from django.urls import path


settings.configure(
    ROOT_URLCONF=__name__,
    DEBUG=True,
    SECRET_KEY="secret",
    DATABASES={
        "default": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": "melon-20230906.sqlite3",       # 파일 경로
        }
    },
    TEMPLATES=[
        {
            "BACKEND": "django.template.backends.django.DjangoTemplates",
            "DIRS": ["templates"],
        }
    ],
)
django.setup()

class Song(models.Model):
    id = models.AutoField(primary_key=True)
    가수 = models.CharField(max_length=100)
    곡명 = models.CharField(max_length=200)
    곡일련번호 = models.IntegerField()
    순위 = models.IntegerField()
    앨범 = models.CharField(max_length=200)
    좋아요 = models.IntegerField()
    커버이미지_주소 = models.URLField()

    class Meta:
        db_table = "songs"
        app_label = "mydjango"


# View 함수 : HTTP 요청이 올 때마다 호출 되어, 요청을 처리하여 응답을 생성/반환하는 함수

def index(request):
    query = request.GET.get("query", "").strip()     # (검색어) 장고에서는 쿼리스트링을 가공한 값을 이렇게 가져올 수 있음
    # 검색어가 없더라도 디폴트 값 "" 반환

    song_list = Song.objects.all()      #QuerySet type
    if query:
        song_list = song_list.filter(
            Q(곡명__icontains=query) | Q(가수__icontains=query)
        )

    # index.html을 렌더
    return render(request, "index.html", {"song_list": song_list, "query": query})


urlpatterns = [
    path("", index),        # http://localhost:8000 요청에 매핑
]

execute_from_command_line(sys.argv)

React 맛보기

{# 장고 템플릿 엔진 주석 문법 : templates/index.html 경로의 파일 #}

<!doctype html>
<html lang="ko">
<head>
    <meta charset="UTF-8"/>
    <title>Melon List</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.22.16/babel.min.js"></script>
</head>
<body>

<div class="container mx-auto">
    <h1 class="font-bold text-2xl">Melon List</h1>
    <!--    autocomplete는 자동완성 기능 끄는 것-->
    <form action="" method="get" autocomplete="off" class="my-4">
        <input type="text" name="query" placeholder="검색어를 입력해주세요 : " autofocus
               value="{{ query }}" class="w-full p-2 border rounded focus:outline-none focus:ring">
        <!--            django의 템플릿 문법-->
    </form>

    <table class="min-w-full bg-white border border-gray-300 divide-y divide-gray-300"
           id="song-list-table">
        <thead>
        <tr class="text-center">
            <th class="py-2 px-4 border-b">곡명</th>
            <th class="py-2 px-4 border-b">가수</th>
        </tr>
        </thead>
        <tbody></tbody>
    </table>

    <script type="text/babel">
        function SongList() {
            // 상태
            const [songList, setSongList] = React.useState([]);

            React.useEffect(() => {
                fetch("api/song-list.json")
                    .then(response => response.json())
                    .then(_songList => {
                        setSongList(_songList);
                    });
            }, []);
            return (
                <>
                    {songList.map(song => {
                        return (
                            <tr key={song.id}>
                                <td>{song.곡명}</td>
                                <td>{song.가수}</td>
                            </tr>
                        )
                    })}
                </>
            )
        }

        ReactDOM.render(
            <SongList/>,
            document.querySelector("#song-list-table tbody")
        )
    </script>
</div>
</body>
</html>
profile
새싹 개발자 🌱

0개의 댓글