One-to-one relationships

  • 기본키를 확장할 때 사용한다.
  • 예를 들면 유저와 유저 프로필 관계와 같다.
  • One-to-One 관계는 한쪽 테이블만 많이 쓰는 경우이다.

Models across files

  • import 해서 사용하면 된다.

Field name restrictions

  • 파이썬 예약어 사용불가
  • underscore(_) 두개 이상 사용 불가
  • sql문은 자동으로 escape 처리하므로 사용 가능

Custom field types

  • 예를 들어 핸드폰 번호 필드를 커스텀해서 사용할 수 있다.
  • 유효성 검사까지 가능하다.
  • 디스플레이 방식도 정의 할 수 있다.

Meta options

  • 클래스에 대한 정보
  • 모델 메타데이터는 “필드를 제외한 모든 것”
from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

Model attributes

objcects

  • 모델의 가장 중요한 속성은 Manager
  • Custom Manager가 정의되어 있지 않는 한, 기본 이름은 objects
  • Manager는 모델 클래스를 통해서만 접근 가능 (모델 인스턴스는 불가)

Model methods

  • Manager 메서드는 “테이블 전체” 영역에 대해 작업을 수행
  • 모델 메서드는 특정 모델 인스턴스에 작동
  • 모델에 자동으로 부여되는 메서드 (오버라이드 가능)
    • __str__() : 모든 객체의 유니코드 “표현”을 반환하는 Python 메서드
    • get_absolute_url()

Overriding predefined model methods

또 다른 모델 메서드이다. 커스텀하려는 데이터베이스 동작을 캡슐화한다.

  • save()
  • delete()

예를 들면 글이 몇 개인지 필드에 미리 저장해두고, 글이 추가되거나 삭제될 때 그 값을 증감한다.

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something
        # Call the "real" save() method
        super(Blog, self).save()
        do_something_else()

super 클래스 메서드를 호출해야 한다. 그래야 데이터베이스에 저장하는 것을 보장한다.

Model inheritance

장고의 모델상속 파이썬의 상속과 유사하다. 장고는 세가지 상속 스타일이 있다.

  1. Abstract base classes : 많이 사용하는 방식
  2. Multi-table inheritance : 편리하지만 느림, 사용하지 않는것을 권장
  3. Proxy models : 특이한 모델로 거의 사용하지 않음

Abstract base classes (aka. abc)

  • 공통된 정보를 여러 다른 모델에 사용하려고 할 때 유용하다.
  • 추상 클래스는 테이블로 생성되지 않는다.
  • 상속받은 클래스에서 그 속성들만 만들어 진다.
# 정보 주는데만 사용
# 테이블이 생성되지 않으므로,
# Shop.objects.* 도 안 됨
class Shop(models.Model):
    name = models.TextField()
    address = models.TextField()

    class Meta:
        abstract = True

# 테이블 생성
class Restaurant(Shop):
	pass #만 해도 naame, address 생성


class PC(Shop):
	pass #만 해도 naame, address 생성

Meta inheritance

메타도 추상화 가능하다

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        # 상송받은 클래스는 자동으로
        # abstract=false로 설정

        # 몇 가지 속성은 Meta에 포함하지 않는 것이 좋음
        # 자식 클래스의 db_table이 모두 같아지기 때문이
        db_table = 'student_info'
  • related_name : 관련된 객체 역참조 할 때, 사용하는 이름
  • related_query_name : 타겟 모델로부터 역방향 필터를 할 때, 사용하는 이름
# Declare the ForeignKey with related_query_name
class Tag(models.Model):
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
        # 기본값은 tag_set
        related_name="tags",
        related_query_name="tag",
    )
    name = models.CharField(max_length=255)

# reverse filter
Article.objects.filter(tag__name="important")
# 다 주석 처리
In [1]: from person.models import Article, Tag

In [2]: Article.objects.filter(tag__name='Tag1')
Out[2]: <QuerySet []>

In [3]: a.tag_set.all()

# related name만 활성화
# related_query_name이 정의되지 않을 경우,
# related name을 기본 값으로 사용
In [2]: Article.objects.filter(tags__name='Tag1')

# related_query_name까지 활성화
In [2]: Article.objects.filter(tag__name='Tag1')

추상 클래스가 related_name과 related_query_name 속성을 가지면, 상속받은 자식 클래스도 같은 이름을 갖게 된다. 그럴때는 아래와 같이 한다.

class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name="%(app_label)s_%(class)s_related",
        related_query_name="%(app_label)s_%(class)ss",
    )

Multi-table inheritance

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

위의 코드를 도식화해보면, 다음과 같다.

Place
 - id
 - name
 - address
Restaruant(Place)
 - p_id (place 상속), One-To-One으로 연결
 - place_name (place 상속)
 - place_address (place 상속)
 - serv_hot_dog (고유 속성)
 - serv_pizza (고유 속성)

실제로 Restaruant 테이블은 Place, Restaruant 필드를 모두 가지고 있다. 하지만 Place는 id, name, address 필드만 있다.

Abstract base classes라면, Restaruant 테이블은 모든 필드를 가지지만, Place는 테이블이 없다. Multi-table은 두 개의 테이블이 분리되어 있고 One-To-One 연결되는 방식이다.

Proxy models

멀티 테이블 상속은 부모와 자식 모두 만들지만, 프록시 모델은 부모 테이블만 만든다. 자식 테이블은 생기지 않으므로, 부모 모델만 정의 할 수 있다.

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

Person으로 접근할 수 있고, MyPerson으로도 접근할 수 있다. 둘다 같은 결과를 반환한다.

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

같은 객체에서 다른 동작을 하고 싶을 경우에 사용한다. (예: 사용자별로 알림을 보낼 때)

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

Person.objects.order_by()하면 그냥 오름차순으로 나오지만, OrderedPerson.objects.order_by()하면 lst_name을 기준으로 오름차순하여 나온다.

Making queries

Creating objects

  • 객체를 생성 후 save()를 호출해야한다. 그래야 데이터베이스에 저장된다.
  • create() 메서드 는 한번에 객체를 생성하고 저장한다.

Saving ForeignKey and ManyToManyField fields

  • ForeignKey 필드는 일반 필드와 동일한 방법으로 저장한다.
  • ManyToManyField는 다르게 동작한다.
    • add() 메서드를 사용해 관계에 레코드를 추가한다.
    • add() 사용하면 save()는 따로 하지 않아도 된다.

In [1]: from blog.models import Blog, Author, Post

# Blog 인스턴스 생성
In [2]: b = Blog.objects.create(name='Beatles Blog', tagline='All the latest Beatles news')

In [3]: author = ['John', 'Paul', 'George', 'Ringo']

# Author 인스턴스 생성
# python dynamic variable name으로 검색
# globals() 전역 변수들을 dictionary로 반환
# globals() dic에 key로 name.lower() 정의
In [7]: for name in author:
   ...:     globals()[name.lower()] = Author.objects.create(name=name)
   ...:

# 확인
In [8]: john
Out[8]: <Author: John>

# Post 인스턴스 생성
In [10]: p = Post.objects.create(blog=b, title='Fisrt Post')

# add() 메서드에 여러개 인자를 포함해 호출하는 법
In [12]: p.authors.add(john, paul, george, ringo)

# 여러개 인자가 저장된 것 확인
In [13]: p.authors.all()
Out[13]: <QuerySet [<Author: John>, <Author: Paul>, <Author: George>, <Author: Ringo>]>

Retrieving objects

  • 데이터베이스에서 객체를 검색하기 위해 QuerySet을 구성한다.
  • QuerySet은 모델 클래스Manager을 통해 구성한다.
    • 각 모델은 objects라고 부르는 적어도 하나의 Manager를 가진다.
  • QuerySet은 SQL문에서 SELECT문과 동급이다.
    • 하나 이상의 많은 filter를 가지고 있다.
    • filter는 SQL문의 WHERE, LIMIT와 같은 제한절과 동급이다.
# 클래스에서만 접근 가능
In [14]: Blog.objects
Out[14]: <django.db.models.manager.Manager at 0x10f33d2b0>

# 인스턴스에서는 불가
In [16]: b.objects
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-16-c375a53b1b40> in <module>()
----> 1 b.objects

/usr/local/var/pyenv/versions/documents/lib/python3.4/site-packages/django/db/models/manager.py in __get__(self, instance, cls)
    184     def __get__(self, instance, cls=None):
    185         if instance is not None:
--> 186             raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
    187
    188         if cls._meta.abstract:

AttributeError: Manager isn't accessible via Blog instances

Retrieving specific objects with filters

  • filter(**kwargs) : 조건과 일치하는 객체를 포함하는 QuerySet을 반환
  • exclude(**kwargs) : 조건이과 일치하지 않는 객체를 포함하는 QuerySet을 반환
# 둘은 같은 결과
>>> Entry.objects.filter(pub_date__year=2006)
>>> Entry.objects.all().filter(pub_date__year=2006)

QuerySets are lazy

QuerySet을 만드는 것이 데이터베이스 활동을 포함하지는 않는다. QuerySet이 평가될 때 까지 아무일도 일어나지 않는다.

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
# 이때 작동
>>> print(q)

위의 예를 보면 데이터베이스에 3번 질의할 것 같지만, 사실은 마지막 라인 print(q)에서 딱 한번 질의한다.

일반적으로 QuerySet의 결과는 데이터베이스로부터 fetch되지 않는다. QuerySet을 “ask”할 때 까지 fecht하지 않는다.

Retrieving a single object with get()

  • 한개를 가져올 때 사용
  • 0도 안되고, 2개도 안되고 무조건 1개만 가져옴

Limiting QuerySets

  • 네거티브는 지원하지 않는다.

Field lookups

  • double-underscore(__)로 접근

대표적인 필드 lookup을 살펴본다.

  • exact
  • iexact : 대소문자 상관없이 검색
  • contains : 대소문자 구분하는 포함
  • icontains
  • startswith : ~로 시작하는
  • istartwith : 대소문자 상관없이 ~로 시작하는
  • endswith : ~로 끝나는
  • iendswith

Lookups that span relationships

  • 장고는 SQL JOIN을 다루는 강력하고 직관적인 검색을 제공한다.
  • 관련된 모델의 필드이름에 __를 사용해 작성한다.
In [24]: Post.objects.filter(blog__name='Beatles Blog')
Out[24]: <QuerySet [<Post: Fisrt Post>]>

In [25]: Post.objects.get(blog__name='Beatles Blog')
Out[25]: <Post: Fisrt Post>

In [26]: Post.objects.get(blog__name__contains='Beatles')
Out[26]: <Post: Fisrt Post>


In [28]: Post.objects.get(blog__name__contains='Beatles', id=1)
Out[28]: <Post: Fisrt Post>

filter 조건에 맞는 값이 중간자 모델과 다중관계에 없을 경우에도 장고는 모든 값에 NULL값이 있다고 취급한다. 즉, 객체가 있고 유효하기 때문에 에러는 발생하지 않는다.

# Post 인스턴스 생성
In [30]: p2 = Post.objects.create(blog=b, title='Second Post')

n [33]: p.authors.all()
Out[33]: <QuerySet [<Author: John>, <Author: Paul>, <Author: George>, <Author: Ringo>]>

# Post 인스턴스 p2에는 author가 없다.
In [34]: p2.authors.all()
Out[34]: <QuerySet []>

# MTM 은 까다로움, 제약조건
# MTM필드가 빈 경우를 검색

# 제대로된 결과
In [31]: Blog.objects.filter(post__authors__isnull=True)
Out[31]: <QuerySet [<Blog: Beatles Blog>]>

# name이 null인 경우가 없지만 결과가 나옴
# name과 author를 모두 검색하기 때문
In [32]: Blog.objects.filter(post__authors__name__isnull=True)
Out[32]: <QuerySet [<Blog: Beatles Blog>]>

# 그럴땐,
# author와 name 모두 둘다 확인
In [33]: Blog.objects.filter(post__authors__isnull=False, post__authors__name__i
    ...: snull=True)
Out[33]: <QuerySet []>

Spanning multi-valued relationships

  • 장고는 단일 filter() 호출 내부의 모든 항목이 동시에 적용되어 모든 요구 사항과 일치하는 항목을 필터링한다.
  • 연속적인 filter() 호출은 객체 세트를 추가로 제한한다.
  • 주의 : 다중 값 관계는 이전 filter() 호출로 선택된 객체가 아닌 기본 모델에 링크 된 모든 객체에 적용된다.
# 연결관계에 따라 결과가 다르게 나온다
# filter()내의 , : like and 연산
>>> Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

# 첫번째 filter() 결과
# + 두번째 filter() 결과 : like or 연산
>>> Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

Filters can reference fields on the model

  • 지금까지는 모델 필드의 값과 상수를 비교했다.
  • 모델 필드의 값과 같은 모델의 다른 필드와 비교하려면 F 표현식을 사용한다. (같은 레벨에서는 __로 타고 들어가 검색이 불가능하기 때문에)
  • F()의 인스턴스는 모델 필드의 레퍼런스 처럼 행동한다.
from django.db.models import F
p1 = Post.objects.get(title='First Post')

p1.comments_count = 10
p1.pinbacks_count = 5

p2.comments_count = 10
p2.pinbacks_count = 15
p1.save()
p2.save()

# Post.objects.filter(comments_count > pingbacks_count)를
# 검색하기 위해 F표현식을 사용
Post.objects.filter(commnets_count__gt=F('ping_backs_count')

# Post.objects.filter(comments_count > pingbacks_count)
Post.objects.filter(commnets_count__lt=F('ping_backs_count')

Aggregate() expressions

  • 쿼리문 안쪽에서 변수하나를 할당해 변수를 비교 값으로 사용할 수 있게 하는것

과제

QnA

  • Documentation을 보는 과제는 ‘이런게 있다라는 것’을 아는게 중요하다.
  • pickling은 Python에서 지원하는 것으로 객체를 직렬화한다. QuerySet을 임시로 저장했다 가져가는 용도로 쓴다. (장기적 사용은 불가)