221005_DJango ORM

Csw·2022년 10월 5일
0

Django

목록 보기
8/14

이 게시글은 김형종 강사님Django 웹개발 강의를 듣고 작성하였습니다.

🍻 DJango ORM

Django QuerySet API reference 사이트 참고


🍸 ORM과 QuerySets


🍹 ORM

  • ORM이란 Object-Relational Mapping의 약자로 객체(Object)와 관계형 데이터베이스(Relational Database)의 데이터를 매핑(Mapping)해주는 것을 의미.
    • 객체 간의 관계를 바탕으로 SQL을 자동 생성하여 sql쿼리문 없이도 데이터베이스의 데이터를 다룰 수 있게 해줌.
    • 데이터베이스의 테이블을 객체지향 프로그래밍에서 흔히 사용하는 객체(Class)처럼 사용할 수 있도록 해주는 기술.
    • 기존 쿼리문을 작성하여 데이터베이스를 조작하는 것을 넘어서서 더 효율적이고 가독성 및 유지 보수에 적합한 코드를 만들기 위해 나온 기술.

🍹 QuerySets

  • QuerySet 이란 데이터베이스에서 전달 받은 객체의 목록이다.
    • Django ORM에서 발생한 자료형
    • 리스트와 구조는 같지만 파이썬 기본 자료구조가 아니기 때문에 파이썬에서 읽고 쓰기 위해 자료형 변환(Casting)

🍸 ORM 실습


🍹 실습 사전 준비

  1. 현재 프로젝트 폴더 내에 orm 앱 설치 후 기본 셋팅
  2. model 설정 및 db 테이블 추가

models.py 코드


from django.db import models


class Member(models.Model):
    name = models.CharField(verbose_name="이름", max_length=10)
    birth = models.DateField(verbose_name="생년월일", null=True)
    email = models.EmailField(verbose_name="이메일")
    phone = models.CharField(verbose_name="전화번호", max_length=11)

    created_at = models.DateTimeField(verbose_name="생성일", auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name="갱신일", auto_now=True)

    def __str__(self):
        return f"{self.name} ({self.email})"

    class Meta:
        verbose_name = "회원"
        verbose_name_plural = "회원 목록"


class Product(models.Model):
    NEW = "new"
    OLD = "old"
    STATUS_TYPE = (
        (NEW, "신상품"),
        (OLD, "재고상품"),
    )

    code = models.CharField(verbose_name="상품코드", max_length=8)
    status = models.CharField(verbose_name="상품구분", max_length=3, choices=STATUS_TYPE)
    name = models.CharField(verbose_name="상품명", max_length=30)
    price = models.PositiveSmallIntegerField(verbose_name="가격(원)", default=0)

    created_at = models.DateTimeField(verbose_name="생성일", auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name="갱신일", auto_now=True)

    def __str__(self):
        return f"{self.name} ({self.code})"

    class Meta:
        verbose_name = "상품"
        verbose_name_plural = "상품 목록"


class PaymentOrderRecord(models.Model):
    NOT_YET = 0
    PAID = 1
    REFUND = 2
    STATUS_TYPE = (
        (NOT_YET, "결제전"),
        (PAID, "결제완료"),
        (REFUND, "환불완료"),
    )

    code = models.CharField(verbose_name="결제코드", max_length=15)
    status = models.IntegerField(verbose_name="결제구분", default=0, choices=STATUS_TYPE)
    member = models.ForeignKey(
        Member,
        verbose_name="회원",
        on_delete=models.CASCADE,
        null=True,
        related_name="member_paymentorderrecord",
    )
    product = models.ManyToManyField(
        Product, verbose_name="상품", related_name="product_paymentorderrecord"
    )

    created_at = models.DateTimeField(verbose_name="생성일", auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name="갱신일", auto_now=True)

    def __str__(self):
        return f"{self.code} ({self.product.count()}개)"

    class Meta:
        verbose_name = "결제 주문서"
        verbose_name_plural = "결제 주문서 목록"
  1. orm 실습을 위한 db 생성
  • 실행 파일 준비
    • 아래와 같이 management폴더 구조 만들기
      • management 폴더와 commands 폴더 내에 아무 내용도 들어 있지 않은 __init__.py 파일 필수 생성
      • commands 폴더 내에 실행 파일 생성
        • push_orm_data.py : test db 생성 시 사용
        • control_orm_data.py : orm 실습 시 사용


push_orm_data.py 코드

  • 아래 코드를 해당 파일에 붙여넣고 저장
  • terminal 창에서 명령어 실행
    • poetry run python manage.py push_orm_data
import datetime

from django.core.management.base import BaseCommand

from orm.models import Member, PaymentOrderRecord, Product

# TODO: 커맨드 활용
# https://docs.djangoproject.com/en/3.2/howto/custom-management-commands/#testing
    class Command(BaseCommand):
        help = "PUSH TEST DB"

        def handle(self, *args, **options):
            member_list = [
                ("이병헌", "lbh@test.com", "01017472748", datetime.date(1970, 7, 12)),
                ("박해수", "phs@test.com", "01039584378", datetime.date(1981, 11, 21)),
                ("황정민", "hjm@test.com", "01012344624", datetime.date(1970, 9, 1)),
                ("강동원", "kdw@dummy.com", "01048572738", datetime.date(1981, 1, 18)),
                ("장동건", "jdk@dummy.com", "01020302949", datetime.date(1972, 3, 7)),
                ("송강호", "skh@movie.com", "01020584737", datetime.date(1967, 1, 17)),
            ]

            product_list = [
                ("po3nuw01", "new", "휴지", 1500),
                ("92oif0rj", "new", "화장품", 30000),
                ("9oiuh67y", "old", "네스프레소", 300000),
                ("8ai2k8ww", "new", "수저", 1000),
                ("9oi8ujhy", "new", "젓가락", 500),
                ("8uhyjtd6", "old", "압력밥솥", 250000),
                ("9if56yhg", "new", "머그컵", 5000),
                ("23tyfuen", "old", "스마트폰", 1000000),
            ]

            payment_list = [
                ("20220101-9oi8u7", 0, "이병헌", 2),
                ("20220101-8ujhbg", 0, "이병헌", 3),
                ("20220101-5tgvfr", 1, "박해수", 4),
                ("20220101-4rfcdE", 2, "박해수", 1),
                ("20220101-3edcxs", 1, "송강호", 2),
                ("20220101-2wsxdd", 0, "강동원", 1),
                ("20220101-9iuj78", 1, "강동원", 3),
                ("20220101-7ujhy6", 2, "장동건", 3),
            ]

            for item in member_list:
                member, is_created = Member.objects.get_or_create(
                    name=item[0], email=item[1], phone=item[2]
                )
                member.birth = item[3]
                member.save()

            for item in product_list:
                Product.objects.get_or_create(code=item[0], status=item[1], name=item[2], price=item[3])

            for item in product_list:
                Product.objects.get_or_create(code=item[0], status=item[1], name=item[2], price=item[3])

            for item in payment_list:
                payment_order_record, is_created = PaymentOrderRecord.objects.get_or_create(
                    code=item[0]
                )

                payment_order_record.status = item[1]
                payment_order_record.member = Member.objects.get(name=item[2])

                for product in Product.objects.order_by("?")[: item[3]]:
                    payment_order_record.product.add(product)

                payment_order_record.save()

control_orm_data.py 기초 코드 작성

import datetime

from django.core.management.base import BaseCommand

from orm.models import Member, PaymentOrderRecord, Product

# TODO: 커맨드 활용
# https://docs.djangoproject.com/en/3.2/howto/custom-management-commands/#testing
class Command(BaseCommand):
    help = "CONTROL TEST DB"

    def handle(self, *args, **options):

이제 이 다음부터 아래의 실습을 이어가게 될 것이며,
코드 작성 후, terminal 창에서 파일 실행 명령을 하면 됨.

  • 실행 명령어 : poetry run python manage.py control_orm_data
  • 아래의 코드들은 control_orm_data.py 파일에서 handle 함수 부분의 코드에 해당.

🍹 전체 데이터 조회 : all

control_orm_data.py

	def handle(self, *args, **options):
        all_member = Member.objects.all()
        all_product = Product.objects.all()
        all_payment = PaymentOrderRecord.objects.all()
		
        # Member 전체 쿼리 조회
        print(all_member)
        # Product 전체 쿼리 조회
        print(all_product)
        # Payment 전체 쿼리 조회
        print(all_payment)

terminal 실행 결과

	# Member
	<QuerySet [<Member: 이병헌 (lbh@test.com)>, <Member: 박해수 (phs@test.com)>, <Member: 황정민 (hjm@test.com)>, <Member: 강동원 (kdw@dummy.com)>, <Member: 장동건 (jdk@dummy.com)>, <Member: 송강호 (skh@movie.com)>]>
	# Product
	<QuerySet [<Product: 휴지 (po3nuw01)>, <Product: 화장품 (92oif0rj)>, <Product: 네스프레소 (9oiuh67y)>, <Product: 수저 (8ai2k8ww)>, <Product: 젓가락 (9oi8ujhy)>, <Product: 압력밥솥 (8uhyjtd6)>, <Product: 머그컵 (9if56yhg)>, <Product: 스마트폰 (23tyfuen)>]>   
	# Payment
	<QuerySet [<PaymentOrderRecord: 20220101-9oi8u7 (2)>, <PaymentOrderRecord: 20220101-8ujhbg (3)>, <PaymentOrderRecord: 20220101-5tgvfr (4)>, <PaymentOrderRecord: 20220101-4rfcdE (1)>, <PaymentOrderRecord: 20220101-3edcxs (2)>, <PaymentOrderRecord: 20220101-2wsxdd (1)>, <PaymentOrderRecord: 20220101-9iuj78 (3)>, <PaymentOrderRecord: 20220101-7ujhy6 (3)>]>

🍹 데이터 검색 (filter) : 데이터 포함 'contains' 외


🍾 Case 1. 전체 일치

control_orm_data.py

	def handle(self, *args, **options):
		# Filter : 
        filter_member = Member.objects.filter(name="강동원")
        print(filter_member)

terminal 실행 결과

	<QuerySet [<Member: 강동원 (kdw@dummy.com)>]>

🍾 Case 2. 일부 포함 : '__contains='

control_orm_data.py

	def handle(self, *args, **options):
		filter_member = Member.objects.filter(name__contains="강")
        print(filter_member)

		# 대소문자 구분
        filter_payment = PaymentOrderRecord.objects.filter(code__contains="E")
        print(filter_payment)
        filter_payment = PaymentOrderRecord.objects.filter(code__contains="e")
        print(filter_payment)

terminal 실행 결과

    <QuerySet [<Member: 강동원 (kdw@dummy.com)>, <Member: 송강호 (skh@movie.com)>]>

	# 대문자와 소문자를 다르게 결과가 같다?!!
    <QuerySet [<PaymentOrderRecord: 20220101-4rfcdE (1)>, <PaymentOrderRecord: 20220101-3edcxs (2)>]>
	<QuerySet [<PaymentOrderRecord: 20220101-4rfcdE (1)>, <PaymentOrderRecord: 20220101-3edcxs (2)>]>

🍗 contains vs icontains 차이

  • contains : 대문자소문자구분
  • icontains : 대문자소문자를 동일한 데이터로 취급

🍗 ES (Elastic Search) : 추후 학습 시 내용 보충 예정

  • 형태소 단위로 나눠서 db화 시켜서 search를 진행

🍾 Case 3. 시작 부분에 포함 : 'startswith`

추후 __startswith=, __endswith= 내용 보충 예정
자매품 __istartswith=, __iendswith=도 있음.


🍹 데이터 검색 (filter) : 크기 비교 '__lt=' 외

  • __gt= : greater than
  • __gte= : greater than or equal to
  • __lt= : less than
  • __lte= : less than or equal to

control_orm_data.py

	def handle(self, *args, **options):
    	filter_product = Product.objects.filter(price__lte=1000)

        print(filter_product)

        for item in filter_product:
            print(item.price)

terminal 실행 결과

	<QuerySet [<Product: 수저 (8ai2k8ww)>, <Product: 젓가락 (9oi8ujhy)>]>

    1000
    500

🍹 데이터 검색 (filter) : ChoiceField 관련 조회

  • ChoiceField 데이터 관련 filter의 경우에는 status값에 모델명.상태값으로 지정해야 함.

models.py 에서 Product 모델 클래스 내용

	class Product(models.Model):
        NEW = "new"
        OLD = "old"
        STATUS_TYPE = (
            (NEW, "신상품"),
            (OLD, "재고상품"),
        )

        code = models.CharField(verbose_name="상품코드", max_length=8)
        status = models.CharField(verbose_name="상품구분", max_length=3, choices=STATUS_TYPE)
        name = models.CharField(verbose_name="상품명", max_length=30)
        price = models.PositiveSmallIntegerField(verbose_name="가격(원)", default=0)

        created_at = models.DateTimeField(verbose_name="생성일", auto_now_add=True)
        updated_at = models.DateTimeField(verbose_name="갱신일", auto_now=True)

        def __str__(self):
            return f"{self.name} ({self.code})"

        class Meta:
            verbose_name = "상품"
            verbose_name_plural = "상품 목록"

control_orm_data.py

	def handle(self, *args, **options):
    	filter_product = Product.objects.filter(price__lte=1000, status=Product.NEW)
        print(filter_product)

terminal 실행 결과

	<QuerySet [<Product: 수저 (8ai2k8ww)>]>

🍓 filter2개 이상 적용 가능


🍹 데이터 개수 세기 (Count)

control_orm_data.py

	def handle(self, *args, **options):
		all_member_count = len(all_member)
        print(all_member_count)

        all_member_count = all_member.count()
        print(all_member_count)

terminal 실행 결과

	6
	6

🍓 단, len보다 count를 사용할 것!!

  • len도 결국 QuerySets 내부에 메서드로 구현이 되어 있으나
  • len 함수는 결국 내부에서 count 메서드를 다시 호출하는 구조로 되어있기 때문에 미세하지만 속도가 조금 더 느림.

🍹 데이터 연산 (aggregate) - Sum, Avg / Max, Min

control_orm_data.py

# 아래 코드에서 'data'는 변수명이며, 임의로 지정 가능
from django.db.models import Sum, Avg, Max, Min

	def handle(self, *args, **options):
    	# Sum
        all_product_total = all_product.aggregate(data=Sum("price"))
        print(all_product_total)
        # Avg
        all_product_average = all_product.aggregate(data=Avg("price"))
        print(all_product_average)
        # Max
        all_product_max = all_product.aggregate(data=Max("price"))
        print(all_product_max)
        # Min
        all_product_min = all_product.aggregate(data=Min("price"))
        print(all_product_min)

terminal 실행 결과

	# Sum
	{'data': 1588000}
	# Avg
    {'data': 198500.0}
	# Max
    {'data': 1000000}
	# Min
    {'data': 500}

🍹 원하는 필드(컬럼)만 추출 (values)

control_orm_data.py

	def handle(self, *args, **options):
    	all_product_status = all_product.values("status")
        print(all_product_status)
        print(type(all_product_status))
      
        all_product_status_list = all_product.values_list("status")
        print(all_product_status_list)
        print(type(all_product_status_list))
       
        all_product_status_list = all_product.values_list("status", flat=True)
        print(all_product_status_list)
        print(type(all_product_status_list))

        all_product_status_real_list = list(all_product_status_list)
        print(all_product_status_real_list)
        all_product_status_distinct_list = list(set(lall_product_status_list))
        print(all_product_status_distinct_list)

terminal 실행 결과

	<QuerySet [{'status': 'new'}, {'status': 'new'}, {'status': 'old'}, {'status': 'new'}, {'status': 'old'}, {'status': 'old'}, {'status': 'new'}, {'status': 'old'}]>
	<class 'django.db.models.query.QuerySet'>

	<QuerySet [('new',), ('new',), ('old',), ('new',), ('old',), ('old',), ('new',), ('old',)]>
	<class 'django.db.models.query.QuerySet'>

	<QuerySet ['new', 'new', 'old', 'new', 'old', 'old', 'new', 'old']>
	<class 'django.db.models.query.QuerySet'>

	['new', 'new', 'old', 'new', 'old', 'old', 'new', 'old']
	['new', 'old']

🍹 데이터 정렬 (order by)

control_orm_data.py

    def handle(self, *args, **options):
    	# 오름차순
		all_product_asc = all_product.order_by("price")
        print(all_product_asc)
        # 내림차순
        all_product_dsc = all_product.order_by("-price")
        print(all_product_dsc)

        # 내림차순 정렬 후 가격만 확인
		for item in all_product_dsc:
            print(item.price)

		# random으로 정렬
        all_product_random = all_product.order_by("?")
        print(all_product_random)
		# 렌덤으로 하나의 결과만 추출
        #   방법 1
        choice_product_random = all_product.order_by("?")[0]
        print(choice_product_random)    
        #   방법 2
        index = randint(0, Product.objects.count() - 1)
        product = Product.objects.all()[index]
        print(product)

terminal 실행 결과

		# 오름차순
		<QuerySet [<Product: 젓가락 (9oi8ujhy)>, <Product: 수저 (8ai2k8ww)>, <Product: 휴지 (po3nuw01)>, <Product: 머그컵 (9if56yhg)>, <Product: 화장품 (92oif0rj)>, <Product: 압력밥솥 (8uhyjtd6)>, <Product: 네스프레소 (9oiuh67y)>, <Product: 스마트폰 (23tyfuen)>]>
        # 내림차순
		<QuerySet [<Product: 스마트폰 (23tyfuen)>, <Product: 네스프레소 (9oiuh67y)>, <Product: 압력밥솥 (8uhyjtd6)>, <Product: 화장품 (92oif0rj)>, <Product: 머그컵 (9if56yhg)>, <Product: 휴지 (po3nuw01)>, <Product: 수저 (8ai2k8ww)>, <Product: 젓가락 (9oi8ujhy)>]> 

		# 내림차순 정렬 후 가격만 확인
		1000000
        300000
        250000
        30000
        5000
        1500
        1000
        500

        # random으로 정렬
        <QuerySet [<Product: 휴지 (po3nuw01)>, <Product: 네스프레소 (9oiuh67y)>, <Product: 머그컵 (9if56yhg)>, <Product: 압력밥솥 (8uhyjtd6)>, <Product: 젓가락 (9oi8ujhy)>, <Product: 화장품 (92oif0rj)>, <Product: 수저 (8ai2k8ww)>, <Product: 스마트폰 (23tyfuen)>]>  
        # random 정렬 후 하나만 뽑아오기
        #   방법 1
		화장품 (92oif0rj)
		#   방법 2
        압력밥솥 (8uhyjtd6)

🍹 데이터 검색 (filter) : 외래키(Foreign Key) 참조

🥡 먼저, 각 모델에 대해 살펴보자.

PaymentOrderRecord 모델

  • 아래와 같이 member라는 filed를 가지고 있는데, 이것은 Member라는Field를 외래키로 가지고 있음.
            member = models.ForeignKey(
                    Member,
                    verbose_name="회원",
                    on_delete=models.CASCADE,
                    null=True,
                    related_name="member_paymentorderrecord",
                )

Member 모델

  • 아래와 같이 name이라는 Field를 가지고 있음.
        class Member(models.Model):
            name = models.CharField(verbose_name="이름", max_length=10)
            birth = models.DateField(verbose_name="생년월일", null=True)
            email = models.EmailField(verbose_name="이메일")
            phone = models.CharField(verbose_name="전화번호", max_length=11)
    
            created_at = models.DateTimeField(verbose_name="생성일", auto_now_add=True)
            updated_at = models.DateTimeField(verbose_name="갱신일", auto_now=True)

🥡 이것을 이용한 데이터 조회

control_orm_data.py

    def handle(self, *args, **options):
    	# 송강호라는 사람이 구매한 주문서 출력
    	payment_by_name = all_payment.filter(member__name="송강호")
        print(payment_by_name)
		# 머그컵을 구매한 주문서 출력
        payment_by_product = all_payment.filter(product__name="머그컵")
        print(payment_by_product)

terminal 실행 결과

    <QuerySet [<PaymentOrderRecord: 20220101-3edcxs (2)>]>

    <QuerySet [<PaymentOrderRecord: 20220101-9oi8u7 (2)>]>

🍹 데이터 검색 (filter) : 가상의 필드(annotate) 이용

🥡 PaymentOrderRecord라는 모델에 member라는 필드는 있지만, member_name이라는 필드는 없음.

  • 이럴 때, 해당 필드를 생성해서 붙여주는 작업을 하는 것이 바로 annotate의 역할
    • 주의할 것!!
    • 어떤 필드에 접근하려면 하나의 객체여야만 함.
    • 쿼리셋에 대한 리스트이면 특정 필드에 접근할 수 없음.
    • 그래서 last() 메서드를 붙여서 하나의 객체를 추출한 것임.

control_orm_data.py

    def handle(self, *args, **options):
    	all_payment_annotate = all_payment.annotate(member_name=F("member__name"))

        print(type(all_payment_annotate))
        print(type(all_payment_annotate.last()))

        # print(all_payment_annotate.member_name)
        print(all_payment_annotate.last().member_name)

        for item in all_payment_annotate:
            print(item.member_name)

terminal 실행 결과

	<class 'django.db.models.query.QuerySet'>  # 쿼리셋
	<class 'orm.models.PaymentOrderRecord'>    # 하나의 row

    장동건

    이병헌
    이병헌
    박해수
    박해수
    송강호
    강동원
    강동원
    장동건

🍹 데이터 검색 (filter) : 특정 범위 내에서 찾기(in)

🥡 내가 찾고싶은 리스트 목록을 미리 지정하고, 내가 가진 데이터 값들 중 해당 리스트 내에 있는 것들만 조회할 때 사용

control_orm_data.py

    def handle(self, *args, **options):
    	price_list = [500, 1000]
        product_in_the_list = all_product.filter(price__in=price_list)

		print(product_in_the_list)

terminal 실행 결과

	<QuerySet [<Product: 수저 (8ai2k8ww)>, <Product: 젓가락 (9oi8ujhy)>]>

🍹 개별 실습

🍾 Q1. 결제 전 상태의 고객의 이름을 출력

control_orm_data.py

	def handle(self, *args, **options):
		find_customer_name = all_payment.filter(status=PaymentOrderRecord.NOT_YET).values_list(
            "member__name", flat=True
        )

        print(find_customer_name)
        print(list(set(find_customer_name)))

terminal 실행 결과

    <QuerySet ['이병헌', '이병헌', '강동원']>
	['이병헌', '강동원']

🍾 Q2. 화장품을 주문하고, 환불한 고객의 이름 출력

control_orm_data.py

	def handle(self, *args, **options):
		find_customer_name = all_payment.filter(product__name="화장품", status=PaymentOrderRecord.REFUND).values_list("member__name", flat=True)

        print(find_customer_name)

terminal 실행 결과

	<QuerySet []>



🏁 ref

Django 공식 문서 > QuerySet API reference
ORM 쿼리 최적화
Django > 반드시 알아야 할 5가지 ORM 쿼리
(엑셀만큼 쉬운) Django Annotation/Aggregation

0개의 댓글