Day 16 Django for KN

김의석 ·2024년 2월 22일

Django

목록 보기
16/39

Django for KN(Koinonia)

  • 해당 문서는 Django 강의가 리뉴얼 되어 이전 과정들을 복습하는 문서로 작성 됨.

데이터베이스 활용

외부 데이터베이스(sqlite3)에 연결하기

왜 데이터베이스를 연결하나?
데이터베이스를 연결하면 매번 외부 url로 데이터를 불러오지 않고 데이터베이스로 데이터를 로드하여 기능을 효율적으로 수행하기 위해서

  • 데이터베이스 sqllite 연결, 디렉토리 내에 있는 db.sqlite3 클릭
  • 외부데이터를 저장할 model class 생성 후 migratrion 한다.
  • 이후 생성한 migraion파일을 sql migrate 한다.
python3 manage.py sqlmigrate hottrack 0001_initial
# 0001_initial : 생성한 migration 파일

커스텀 관리 명령을 통한 데이터베이스 저장

  • load_melon_songs.py 커스텀(소스코드) 명령어 활용
    • hattrack app 내 management/commands/load_melon_songs.py 경로에 저장
    • python3 manage.py --help로 추가된 커스텀 명령어 확인 가능
import json
from urllib.request import urlopen
from django.core.management import BaseCommand
from hottrack.models import Song


class Command(BaseCommand):
    help = "Load songs from melon chart"

    def handle(self, *args, **options):
        melon_chart_url = "https://raw.githubusercontent.com/pyhub-kr/dump-data/main/melon/melon-20230910.json"
        json_string = urlopen(melon_chart_url).read().decode("utf-8")

        # Song 인스턴스들은 아직 데이터베이스에 저장되지 않았습니다.
        song_list = [Song.from_dict(song_dict) for song_dict in json.loads(json_string)]
        print("loaded song_list :", len(song_list))

        # Song 인스턴스들은 한 번에 INSERT 쿼리를 생성하여, INSERT 성능을 높입니다.
        Song.objects.bulk_create(song_list, batch_size=100, ignore_conflicts=True)

        total = Song.objects.all().count()
        print("saved song_list :", total)
  • ong_list: Song 모델의 인스턴스들이 들어있는 리스트. 인스턴스들은 데이터베이스에 저장되지 않은 상태.

  • batch_size=100: bulk_create는 대량의 데이터를 작은 배치로 나누어 처리하는 방식을 사용. 이 인자는 한 번 삽입되는 배치의 크기. 100개씩 작은 배치로 나누어 삽입.

  • ignore_conflicts=True: 중복된 레코드 무시 여부를. True는 설정하중복된 레코드가 있어도 진행.

대량의 데이터를 다룰 때 일반적으로 bulk_create를 사용하여 여러 개의 쿼리를 수행하는 것보다 빠르고 효율적입니다.

  • 여러 개의 쿼리를 각각 실행하는 것이 아닌, bulk_create로 한 번의 쿼리로 여러 개의 레코드를 삽입할 때의 장점

    • 데이터베이스와의 통신 횟수 감소:
      여러 개의 레코드를 각각 추가할 때마다 데이터베이스와의 통신이 발생.
      네트워크 지연을 줄이고 성능을 향상시킬 수 있습니다.

    • 트랜잭션 오버헤드 감소:
      트랜잭션은 여러 쿼리를 하나의 논리적 작업 단위로 묶는데, 이를 줄이면 트랜잭션 관련 오버헤드가 감소하게 됩니다.

    • 인덱스 업데이트 횟수 감소:
      한 번 쿼리로 여러 개의 레코드를 추가할 때, 인덱스의 업데이트가 한 번에 이루어지므로 인덱스 업데이트 횟수가 감소합니다.

view.py에서 데이터 모델 활용

def index(request: HttpRequest) -> HttpResponse:
    query = request.GET.get("query", "").strip()
    song_qs: QuerySet[Song] = Song.objects.all() 
    # 외부 url 요청이 아닌 연결된 데이터베이스 안의 데이터를 가져온다

    if query:
        song_qs = song_qs.filter(
            Q(name__icontains=query)
            | Q(artist_name__icontains=query)
            | Q(album_name__icontains=query)
        )   # 쿼리 기준에 맞는 데이터를 filter()와 Q를 실행하여 song_qs에 담는다.

    return render(
        request=request,
        template_name="hottrack/index.html",
        context={"song_list": song_qs, "query": query},
    )   # song_qs를 "song_list" key에 담아 html과 render한다.

admin 앱을 통한 song 데이터 관리

왜 admin을 사용하나?
개발 공수를 줄이고 유저서비스에 집중할 수 있으며 model에 대한 조회/ 생성/ 삭제/ 수정 UI를 제공하주기 때문에 쉽고 편하게 model에 대한 CRUD가 가능하다.

  • 슈퍼유저계정 생성
python3 manage.py createsuperuser 

admin.py의 model class 내에 필드 추가하기

from django.contrib import admin
from django.utils.html import format_html

from .models import Song


@admin.register(Song)
class SongAdmin(admin.ModelAdmin):
    list_display = [
        "name",
        "artist_name",
        "album_name",
        "genre",
        "like_count",
        "release_date",
        "cover_image",
    ]

    def cover_image(self, song):  # 해당 행에대한 song model instance를 넘겨준다.
        return format_html('<img src="{}" style="width: 50px;" />', song.cover_url)
        # format_html() 장고 내에서 html 태그문자열을 조합하는 안전한 방법.
        # 첫번째 인자는 html 문법(값이 들어갈 영역)
        # 두번째 사용 값
        
# model.py에서 admin 필드값 추가하기

class Song(models.Model):
    melon_uid = models.CharField(max_length=20, unique=True)
    rank = models.PositiveSmallIntegerField()
    album_name = models.CharField(max_length=100)
    name = models.CharField(max_length=100)
    artist_name = models.CharField(max_length=100)
    cover_url = models.URLField()
    lyrics = models.TextField()
    genre = models.CharField(max_length=100)
    release_date = models.DateField()
    like_count = models.PositiveIntegerField()
    # PositiveIntegerField() :  좋아요 필드의 특성상 항상 0이상 이어야한다는 설정
    
    @property
    def cover_image_tag(self):
        return format_html("<img src='{}' style='width:50px;", self.cover_url)
        # 이후 필히 admin list_display에서도 cover_image_tag필드 추가.

search field / list_filter

@admin.register(Song)
class SongAdmin(admin.ModelAdmin):
    search_fields = ["name", "artist_name", "album_name"]
    list_filter = ["genre"]
  • search_fields : 일반적은 검색창, 검색을 수행할 필드를 리스트에 담는다.
  • list_filter : 화면상에 수행할 필드 속성이 리스팅 된 검생기능

좋아요 수, 갱신 액션

검색창에 default delete action 외에 사용자 정의 action을 추가하는 과정

  • 업데이트 된 좋아요 수를 가져오기 위해 utils/melon.py로 경로에 코드 저장 후 아래와 같이 코드 진행
# admin에 사용자 정의 action 옵션 추가 
@admin.register(Song)
class SongAdmin(admin.ModelAdmin):
	actions = ["update_like_count"]
    
######################################################################## 

    # 사용자 정의 action 함수 작성
    def update_like_count(self, request, queryset):
        melon_uid_list = queryset.values_list(
            "melon_uid", flat=True
        )  # queryset = Song.objects.all()(admin을 통해 song 모델에 접속 후 검색창에서 기능 실행함)에서 melon_uid lsit에 접근하여 가져온다.

        likes_dict = get_likes_dict(
            melon_uid_list
        )  # melon.py get_likes_dict 새로운 좋아요 정보를 받아옴

        changed_count = 0  # 좋아요를 업데이트한 곡 메세지로 출력하기
        for song in queryset:
            if (
                song.like_count != likes_dict[song.melon_uid]
            ):  # 기존 좋아요 수와 새로운 좋아요 수가 다르다면
                song.like_count = likes_dict.get(
                    song.melon_uid
                )  # # 새로운 데이터로 like count 값을 없데이트
                changed_count += 1  # 좋아요를 업데이트 한 곡에 추가

        Song.objects.bulk_update(queryset, ["like_count"])
        # queryset은 for문을 통해 업데이트 된 상태
        # 두번째 인자는 업데이트 할 필드

        self.message_user(request, f"{changed_count}곡의 쪼아요 갱신 완료")
        
# 참고
역직렬화 시 객체의 typedict
result = json.loads(urlopen(request).read())
result = {'contsLike': [{'CONTSID': 35667692, 'LIKEYN': 'N', 'SUMMCNT': 23798}, {'CONTSID': 36105548, 'LIK': 'N', 'SUMMCNT': 108942}], 'httpDomain': 'http://www.melon.com', 'httpsDomain': 'https://www.melon.com', 'staticDomain': 'https://static.melon.co.kr'}

# queyset의 type
<QuerySet [<Song: Song object (89)>, <Song: Song object (88)>, <Song: Song object (86)>]>
# 이후 query set을 for에 변수로 담아 object.field 형식으로 value에 접근한다.

# song.save : 모든 모델 필드에 대해서 업데이트를 수행, 필요가 아닌 경우 쿼리 수행이 복잡해진다.
# song.save(udate_fields=["like_count"]) : 지정 필드에 대해서 업데이트 수행
# 필요필드만 업데이트 하는 것을 권장.
profile
널리 이롭게

0개의 댓글