2월 12, 2022

[Django]ORM QuerySet method

 Model method(또는 QuerySet method)를 통하여 Django에 내장된 데이터 관리 기능을 사용할 수 있습니다. 


우선, models.py의 코드는 다음과 같습니다. 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from tkinter import CASCADE
from unicodedata import category
from django.db import models

# Create your models here.
class Menu(models.Model):
    name = models.CharField(max_length=45)
    class Meta: #모델에 대한 다양한 사항 정의
        db_table = 'menu' # 테이블을 식별하는데 사용하는 이름 설정(db상 이름 menus)

class Categories(models.Model):
    name = models.CharField(max_length=45)
    menu = models.ForeignKey('Menu', on_delete=models.CASCADE) 
    # 1:N에서 N쪽이 관계(FK)선언
    # CASCADE: 1쪽이 삭제됐을때 모든 N쪽 데이터 삭제
    class Meta:
        db_table = 'categories'

class Images(models.Model):
    image_url = models.CharField(max_length=300)
    drink = models.ForeignKey('Drinks',on_delete=models.CASCADE)
    class Meta:
        db_table = 'images'

class Drinks(models.Model):
    korean_name = models.CharField(max_length=45)
    english_name = models.CharField(max_length=45)
    description = models.TextField(max_length=100)
    category = models.ForeignKey('Categories', on_delete=models.CASCADE)
    class Meta:
        db_table = 'drinks'

class Allergy_drink(models.Model):     #drinks와 allergy 의 중간테이블 
    allergy = models.ForeignKey('Allergy', on_delete=models.CASCADE)
    drink = models.ForeignKey('Drinks', on_delete=models.CASCADE)
    class Meta:
        db_table = 'allergy_drink'


class Allergy(models.Model):
    name = models.CharField(max_length=45)
    drinks = models.ManyToManyField(Drinks, through='Allergy_drink') #through에 중간테이블명 넣어줌
    class Meta:
        db_table = 'allergy'
   

class Nutritions(models.Model):  
    one_serving_kcal = models.DecimalField(max_digits=10, decimal_places=5)
    sodium_mg = models.DecimalField(max_digits=10, decimal_places=5)
    saturated_fat_g = models.DecimalField(max_digits=10, decimal_places=5)
    sugars_g = models.DecimalField(max_digits=10, decimal_places=5)
    protein_g = models.DecimalField(max_digits=10, decimal_places=5)
    drink = models.ForeignKey('Drinks', on_delete=models.CASCADE )
    size = models.ForeignKey('Sizes', on_delete=models.CASCADE)
    class Meta:
        db_table = 'nutritions'

class Sizes(models.Model):
    name = models.CharField(max_length=45)
    size_mi = models.CharField(max_length=45)
    size_fluid_ounce = models.CharField(max_length=45)
    class Meta:
        db_table = 'sizes'

# # categories(다) - (일)menu :  one-to-many : Menu 메뉴에 음료/푸드/상품 등 카테고리
# # drinks(다) - (일)categories : one-to-many
# # images (다) - drinks(일) : one-to-many
# # nutritions(다) - drinks(일) : one-to-many
# # nutritions(다) - sizes(일) : one-to-many
# drinks(다) - (다) allergy : many-to-many / allergy_drink 중간테이블
# NULL은 포인터가 가져올 값이 없는 상태
# 중간테이블 직접 정의 through model


이때, 서로 참조하고 있는 테이블들을 Read하는 방식에 대해 자세히 알아보려 합니다.

데이터는 임의로 넣어주었습니다.


먼저 다대일 (Categories - Menu)의 경우를 확인해보겠습니다.

카테고리가 메뉴를 정참조하고, 메뉴가 카테고리를 역참조하는 구조입니다.

menu변수에 객체 담아줍니다

menu = Menu.objects.get(id=2) menu <Menu: Menu object (2)>

_set 을 이용해 categories를 나타내는 매니저를 설정하고, get으로 역참조 객체(categories) 가져옵니다.

  • menu.categories_set.get(id=4)       # 역참조시 _set 사용 
  • <Categories: Categories object (4)>
  • menu.categories_set.get(id=4).name '브레드'

 

filter로 역참조 객체를 가져옵니다.

  • menu.categories_set.filter(id=4) 
  • <QuerySet [<Categories: Categories object (4)>]>
  • menu.categories_set.filter(id=4).name 
  • Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'QuerySet' object has no attribute 'name'

filter는 객체가 아닌 Queryset을 반환하기에 .name을 했을 시 값이 나오지 않는 모습입니다


따라서 [0]을 통해서 Queryset안의 객체를 꺼내주면 .name으로 카테고리의 이름을 확인할 수 있습니다.

  • menu.categories_set.filter(id=4)[0].name 
  • '브레드'


이번에는 category 변수에 객체를 담아주고, 정참조하는 Menu를 가져와봅니다. 

  • category = Categories.objects.get(id=1) 
  • category.menu 
  • <Menu: Menu object (1)> 
  • category.menu.name 
  • '음료'


이번에는 다대다 Drinks(다) - Allergy_drink(중간테이블) - Allergy(다) 의 경우를 확인해보겠습니다.

drink에 get으로 객체를 담고 .allergy를 찍으니 Drinks에 allergy란 속성이 없다고 합니다.

  • drink = Drinks.objects.get(id=4) 
  • drink.allergy 
  • Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'Drinks' object has no attribute 'allergy'


위의 역참조의 경우처럼 _set을 사용해야 drink와 연결된 값들을 확인할 수 있습니다

  • drink.allergy_set.all() 
  • <QuerySet [<Allergy: Allergy object (2)>, <Allergy: Allergy object (6)>]> drink.allergy_set.get(id=2).name 
  • '우유'


이번에는 이렇게 drink와 연결된 특정 알러지 값이 아닌,  allergy의 name 전체가 꺼내고 싶습니다. for 문을 사용해줍니다.

  • for i in drink.allergy_set.all(): print(i.name)
  • ... 
  • 우유 
  • 토마토

다대다 관계이기에 반대로 알러지를 담은 변수에서 연결된 Drinks 값을 꺼내는 것도 가능하겠죠?

  • allergy = Allergy.objects.get(id=2) 
  • allergy.drinks_set.get(id=1) 
  • Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'Allergy' object has no attribute 'drinks_set'

어라... drinks_set이란 어트리뷰트는 없다고 나옵니다.

_set을 사용하지 않고 정참조때와 같은 방식으로 drinks를 꺼내보았습니다.

allergy.drinks <django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x7ffab86eafd0>

.drink가 many_to_manager로 동작하고 있음을 볼 수 있습니다

앞서 drink 변수의 경우는 _set을 붙여줘야만 했는데.. 뭐가 다른걸까요??

drink.allergy_set <django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x7ffab86eacd0>


코드를 자세히 보니, 제가 Allergy 클래스에 ManyToManyField를 정의해주었습니다.
one-to-many에서 many인 쪽에 FK를 정의하고, many쪽의 클래스로 인스턴스를 만들어 정참조하는 테이블을 호출할 때, _set을 사용하지 않는 것과 같은 메커니즘인 듯 보입니다.
관계 키를 정의해준 쪽에서는 _set을 사용하지 않고 호출할 수 있는 것으로 이해했습니다.


추가

1. exclude(), values(), values_list()를 활용해 DB로부터 READ를 해보았습니다

value_list는 값만 있는 리스트가 반환되는 반면, values는 {'name':'값"} 의 형식으로 딕셔너리가 반환됩니다.

  •  Allergy.objects.exclude(id=1).values_list('name')
  • <QuerySet [('우유',), ('난류',), ('밀',), ('아황산류',), ('토마토',)]> 
  • Allergy.objects.exclude(id=1).values('name') 
  • <QuerySet [{'name': '우유'}, {'name': '난류'}, {'name': '밀'}, {'name': '아황산류'}, {'name': '토마토'}]>


2. filter메소드에 연관된 테이블의 id가 아닌 값을 사용하여 필터링을 할때 menu__name="음료" 이런식으로 _를 두번 사용해야됩니다( __ )

  • Categories.objects.filter(menu__name="음료") 

  • <QuerySet [<Categories: Categories object (1)>, <Categories: Categories object (2)>]>


2월 10, 2022

Django Tutorial ORM

  1. 클라이언트인 사용자가 요청을하면 Nginx 또는 Apache인 웹서버가 요청을 맞이함(개발시에는 경량 웹서버 사용하고, 상용으로 올릴때 Nginx또는 Apache사용)
  2. wsgi는 웹서버와 django 프레임워크를 연결하기 위해서 사용
  3. 요청이 넘어감
  4. 사용자가 요청한 특정 주소를 잘게 나누어줌 (parsing)
  5. 잘게 나눠진 주소들은 각각에 맞게 view로 이동함
  6. view에 따라 데이터베이스작업 등의 맞는 작업을 거침
  7. 디자인 담당인 템플릿으로 이동 된 후
  8. 사용자는 화면을 볼 수 있게 됨

Django의 전체적인 사이클입니다. 
이중에서도 6번 부분에 대해 코드를 보며 자세히 알아보겠습니다.

빨간 박스로 표시된 부분은 모델과 데이터베이스간의 소통을 보여줍니다. 이를 ORM(Object-Relation-Mapping)이라 합니다. 무슨 뜻이냐면, 데이터베이스의 테이블을 객체(object)와 연결하여서, 테이블에 CRUD를 할 때, SQL쿼리를 사용하지 않고도 CRUD를 가능하게 하는 것을 말합니다.
즉, 장고의 각 앱 안에 있는 models.py에 생성된 각 모델은 데이터베이스 테이블과 mapping(연결) 돼 있다는 것입니다. 
장고 튜토리얼의 mysite/polls/models.py 의 코드를 먼저 보시겠습니다
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import datetime
from django.db import models
from django.utils import timezone

# Create your models here.
class Question(models.Model): 
    id = models.AutoField(primary_key=True) 
    question_text = models.CharField(max_length=200) #질문내용
    pub_date = models.DateTimeField('date published') #생성날짜
    def __str__(self): #object.all을 했을때 구분가능하게 하기 위해  __str__ 메소드 사용
        return self.question_text
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1) #커스텀메소드 추가
        #현재로부터 하루 차감한 값(어제) 이후(>=) 에 발행된 데이터가 return 
        #timedelta(days=1) means "1일전"
        # ex) datetime.now() - timedelta(hours=3) means 현재로부터 세시간 전

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE) #선택지에 해당하는 질문,외래키(question참조)
    #Question 클래스가 삭제되면, 바로 위의 이 question도 삭제가 되겠다
    choice_text = models.CharField(max_length=200) 
    votes = models.IntegerField(default=0) #투표수 
    def __str__(self):
        return self.choice_text
이 모델을 사용하여 데이터베이스에 CRUD를 해보겠습니다.

python manage.py shell 로 우선 쉘을 실행해줍니다.


데이터 조회(Read)

우선 모델에서 Choice와 Question 모델을 import한 뒤, Question의 내용을 Read합니다.


그런 다음,  데이터를 조회하여(read) reaad라는 변수에 저장합니다.


데이터 생성(Create)


위의 코드를 실행하여 Question 모델에 새로운 데이터를 create해봅니다


다시 한번 Question 모델을 조회하여 데이터가 잘 추가된 것을 확인할 수 있습니다


이런식으로 T라는 변수에 Question클래스의 객체를 저장하고, 이를 Queryset에 save해주는 방식으로도 데이터를 생성할 수 있습니다

데이터 업데이트(Update)


위의 코드로 데이터를 조회하고, 업데이트 해봅니다.
변수 O는 데이터 셋 내부의 값이 아닌, Question모델(클래스) 값을 받은 변수입니다. 따라서 O의 question_text 를 변경해주었다고 해도, O.save()를 통해 변경사항을 Question 모델에 직접 적용해주지 않는다면 QuerySet의 내용은 변화하지 않을것입니다. 


아래의 코드로 업데이트 된 내용이 적용됐음을 확인할 수 있습니다


Question 클래스의 함수인 was_published_recently()도 잘 작동합니다. 객체를 만들고 얼마 안가 바로 함수를 작동했으니 True가 나오는 것이 맞습니다.

데이터 삭제(Delete)



위의 코드로 만들어놓은 데이터를 Delete하고 결과를 확인해봅니다. 

끝으로 exit()을 통해 shell을 종료하고 빠져나옵니다.