recordingbetter's devlog

Python, Django, DRF, Postgresql, AWS, Docker....

Django Model

02 June 2017


  • 데이터베이스 테이블 설정
  • 1개의 클래스는 1개의 데이터베이스 테이블
  • 각 모델은 파이썬의 클래스이며 django.db.models.Model의 서브클래스
  • 모델의 각 속성은 데이터베이스의 필드
  • Django는 database-access API를 자동으로 만들어준다.
# models.py
from django.db import models

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

# 실제 데이터베이스 명령어
CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

Person이라는 테이블에 문자열 필드인 first_name, last_name 필드를 만든다. id라는 primary key 필드는 자동으로 생성된다.

model 사용하기

  • settings.py 파일의 INSTALLED_APPS에 사용할 app을 설정해준다.
INSTALLED_APPS = (
    #...
    'myapp',
    #...
)

  • 이후 터미널에서 ./manage.py migrate 를 실행하여 데이터베이스를 생성한다.
  • models.py가 변경되었을 경우, 터미널에서 ./manage.py makemigrations를 실행하여 model의 변경사항을 기록한 뒤, migrate를 샐행하여 데이터베이스에 적용한다.

Field types

  • 필드는 모델 클래스의 인스턴스로 표기된다.
  • 데이터베이스의 컬럼
from django.db import models

class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)

class Album(models.Model):
    artist = models.ForeignKey(Musician)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

필드의 종류

  • BooleanField : True / False
  • NullBooleanField : True / False / null
  • CharField : 문자열 필드. 항상 max_length 옵션을 줘야한다. 200자 이하.
  • TextField : 문자열 필드. 많은 문자 입력 가능
  • DateField : 날짜 필드.
  • DateTimeField : 타임스탬프 필드.
  • IntegerField : 정수 필드
  • FloatField : 실수 필드
  • 이 외에도 많음. custom field 생성 가능..

Field options

  • 각 필드에 옵션 설정 가능.
  • 필드 타입에 따라 사용 가능한 옵션이 정해져 있음.

공통 옵션

  • null : true (null 허용) / false (허용 안함)
  • blank : true (비어있는 값 허용) / false (허용 안함)
  • choices : 실제 저장될 값과 화면에 보여질 값이 다를때 사용. 2중 튜플이나 리스트이어야 한다.
  • default : 필드의 기본값 정의. 함수를 지정할 수 있다.
  • help_text : 폼 위젯으로 표시될때 함께 표시될 도움말
  • primary_key : true -> 해당 필드를 pk 필드로 지정 (자동으로 생성된 pk외에 추가로 생성됨)
  • unique : 해당 필드에는 고유한 값만 허용

Automatic primary key fields

  • 각 테이블 별로 id 필드를 자동 생성한다. (Primary Key)
id = models.AutoField(primary_key=True)

  • 자동증가(auto increment)하는 primary key 필드.
  • 모델의 필드 중에 primary_key=True 옵션이 명시적으로 선언된 필드가 있다면 자동적으로 id 필드를 생성하지 않음.

Verbose field names

  • 모델에서 verbose field name은 읽기좋은 형태의 필드이름.
  • 필드들은 공통적으로 첫번째 인자(optional)로 verbose name 값을 가진다.(ForeignKey, ManyToManyField, OneToOneField 는 키워드 인자로 전달))
  • 값이 주어지지 않는 경우 해당 필드의 어트리뷰트 이름을 verbose name으로 사용
  • 언더스코어 _는 공백으로 치환된다.
  • 소문자로…
# verbose name 'person's first name'
first_name = models.CharField("person's first name", max_length=30)

# verbose name 'first name'
first_name = models.CharField(max_length=30)

# 관계용 필드는 키워드 인자로 전달
poll = models.ForeignKey(Poll, verbose_name="the related poll")
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(Place, verbose_name="related place")

관계 - many to one / one to many?

  • 자식이 될 모델 클래스에 django.db.models.ForeignKey 클래스를 이용하여 선언 (부모 모델 클래스를 인자로)
from django.db import models

# 부모 모델
class Manufacturer(models.Model):
    ...
    pass

# 자식 모델
class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer)
    ...

# 재귀형태 (클래스 이름을 문자열로)
class Employee(models.Model):
    boss = models.ForeignKey('Employee')

관계 - many to many

  • 다대다 관계는 ManyToManyField.
  • 관계를 가지는 모델 클래스를 첫번째 인자로 받음.
  • 관계를 가지는 두 모델 클래스 중 1개에만 설정하면 된다.
  • 재귀형태 가능. 클래스 이름을 문자열로 전달.
# 피자에는 여러가지 토핑이 올라가며, 토핑은 여러 피자에 사용된다.
from django.db import models

class Topping(models.Model):
    ...
    pass

class Pizza(models.Model):
    ...
    toppings = models.ManyToManyField(Topping)

관계 - many to many 추가 필드

  • 다대다 관계를 가지는 두 모델 클래스에 관계와 관련한 추가 데이터 저장이 필요한 경우 중간 모델을 만든다.
  • 중간 모델은 원본 모델 클래스의 ForeignKey를 1개만 가지고 있어야 한다.
  • ManyToManyField에 through_fields 옵션으로 관계에 사용되는 클래스이름을 정해주면 ForeignKey 2개를 가질 수 있음.
  • 재귀형태일 경우, 중간 모델에 동일한 원본 모델의 ForeignField 2개를 선언할 수 있다.
  • 3개 이상일 경우는 through_fields 옵션으로 설정
  • 중간모델의 데이터를 생성하는 경우 중간모델에서 직접 생성
# 학생은 여러 동아리에 가입할 수 있고, 각 동아리는 여러명의 학생들을 회원으로 갖습니다. 
# 동아리에 가입한 날짜를 저장.

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person)
    group = models.ForeignKey(Group)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)


# 중간 모델에서 clear() 사용 가능, remove() 사용 불가
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
[]


# Paul로 시작하는 멤버가 속한 그룹
>>> Group.objects.filter(members__name__startswith='Paul')
[<Group: The Beatles>]


# 특정 날짜 이후 합류 필터(중간 모델 데이터)
>>> Person.objects.filter(
...     group__name='The Beatles',
...     membership__date_joined__gt=date(1961,1,1))
[<Person: Ringo Starr]


# 중간 모델 쿼리
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'


# 한쪽 객체로 부터 역참조
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

관계 - one to one

  • 일대일 관계 정의시 OneToOneField
  • 어떤 모델을 확장하여 새로운 모델을 만들 경우 사용.
  • OneToOneField 필드는 parent_link 라는 옵션을 제공. True 일때 부모 모델 클래스를 상속받는다.
  • primary_key=True 로 지정하여 Primary Key로 만들 수 있다.
  • 하나의 모델이 여러개의 OneToOneField를 가질 수 있다.

다른 앱의 모델과의 관계

  • 다른 앱에서 선언된 모델과 관계를 가질 수 있다.
  • 다른 앱의 모델을 import하여 선언
from django.db import models
from geography.models import ZipCode

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(ZipCode)

필드 이름 규정

  • 파이썬 예약어는 필드 이름으로 사용할 수 없다.
  • 언더스코어 2개 (__)를 연속으로 사용할 수 없다. (파이썬의 매직매소드) db_column 옵션을 사용
  • SQL 예약어는 필드이름으로 사용 가능.

Meta options

  • 모델클래스 내부에 Meta 라는 이름의 클래스 선언해서 모델에 메타데이터를 추가할 수 있다.
  • 모델 단위의 옵션 (정렬, 테이블 이름, 닉네임, 복수 표현(verbose_name, verbose_name_plural) 등..)
from django.db import models

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

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

Model attributes - objects

  • 모델 클래스에서 가장 중요한 속성은 Manager.
  • Manager 객체는 모델 클래스 선언을 기반으로 실제 데이터베이스에 대한 쿼리 인터페이스를 제공하며, 데이터베이스 레코드를 모델 객체로 인스턴스화 하는데 사용.
  • Manager를 할당하지 않으면 Django는 Default Manager 객체를 클래스 어트리뷰트로 자동 할당. (objects)
  • Manager는 모델 클래스를 통해 접근할 수 있으며, 모델 인스턴스(객체)를 통해서는 접근 할 수 없습니다.

Model methods

  • 레코드 단위의 기능 구현 : 모델 클래스의 메서드
  • 테이블 단위의 기능 구현 : Manager에서 구현
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

  # Person 객체의 특정 값의 범위에 따라 다른 값을 반환하는 커스텀 메서드
    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

   #  Person 객체의 full name을 반환하는 커스텀 메서드
    def _get_full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

    full_name = property(_get_full_name)

Override predefined model methods

  • 모델 클래스에 자동으로 주어지는 메서드들을 필요한 기능으로 override 할 수 있다.(save, delete 등..)
  • __str__() : 클래스가 출력되면 필요한 데이터가 나올 수 있게 한다.
  • get_absolute_url() : 해당 모델 객체의 URL을 계산한다. 모델 객체를 url로 표현하는 경우 사용. 모델 객체가 유일한 URL을 가지는 경우에 이 메서드를 구현해주어야 한다.
from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()
    
   # 저장 시, 특정한 동작을 수행하기 위해 save()를 재정의
    def save(self, *args, **kwargs):
        do_something()
        
        # super는 override하기 전 원래 동작을 수행. Python3에서는 인자 생략 가능.
        super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
        # save 이후의 동작을 지정
        do_something_else()


   # 또는, save()를 실행해도 저장이 되지 않게 함.
    def save(self, *args, **kwargs):
        if self.name == "Yoko Ono's blog":
            return # Yoko shall never have her own blog!
        else:
            super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.

  • 부모클래스의 메서드를 호출해야한다. 위 예제 코드에서 super(Blog, self).save(*args, **kwargs) 를 호출함으로써, 실제로 데이터베이스에 객체가 저장되었습니다. 부모 클래스의 메서드를 호출해주지 않는다면, 실제 데이터베이스에는 아무런 동작도 일어나지 않습니다.
  • 오버라이드한 메서드의 인자를 그대로 부모 클래스의 메서드에 넘겨주어야 한다.(*args, **kwargs)

  • 재정의된 모델 메서드는 벌크 조작(여러 개체를 한꺼번에 다룰때)시 호출되지 않는다.
  • delete는 “pre_delete”, “post_delete” 시그널을 이용해야 한다.
  • 생성이나 수정은 오버라이드된 메서드 실행 불가능.

SQL

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)
    
    # SQL 사용 가능
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
John Smith
Jane Jones

Model inheritance - 모델 상속

  • 파이썬의 클래스 상속과 거의 동일하다.
  • 베이스 클래스는 django.db.models.Model을 상속받았어야 한다.

모델 클래스 상속 스타일 3가지

  1. abstract class
    • 부모클래스는 실제로 데이터베이스 테이블을 만들지 않고, 자식 클래스에 필드 선언, 메서드 선언 만 상속해준다.
    • 상속 받은 자식 모델은 데이터베이스 테이블에 부모 클래스와 자기 자신에 선언된 필드들을 가진다.
  2. Multi-table inheritance
    • (다른 앱에서 사용중인) 다른 모델 클래스를 상속
    • 부모 모델은 자신의 데이터베이스 테이블을 가지며 자식 모델과는 별개로 독립적으로 사용 가능
  3. Proxy models
    • 모델의 필드 선언은 전혀 변경하지 않고 파이썬 레벨의 코드만 수정하고자 하는 경우 사용

모델 상속 1. Abstract base classes

  • 여러개의 모델 클래스가 공통적인 정보를 가지게 하고 싶은 경우.
  • Meta 클래스에 abstract = True 옵션을 주면 추상클래스가 된다.(데이터베이스를 생성하지 않음)
  • 자식 클래스들만 이 클래스에 정의된 필드들을 가지는 데이터베이스를 생성한다.(자식 자신이 가진 필드 포함)
  • 추상클래스(부모), 자식 클래스 간 동일한 이름의 필드가 있으면 에러.
  • 추상클래스는 테이블을 만들지 않고, Manager도 없고 인스턴스화도 할 수 없다.
from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True


class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

  • Student 모델은 3개의 필드(name, age, home_group)를 가진다.

모델 상속 1-1. Meta inheritance

  • 자식 클래스가 Meta 클래스를 가지지 않는 경우, 부모의 Meta 클래스를 상속받는다.
  • 부모 클래스에 선언된 Meta 클래스로부터 상속 받으면 된다.
from django.db import models

class CommonInfo(models.Model):
    ...
# 부모클래스는 추상클래스
    class Meta:
        abstract = True
        ordering = ['name']

# 부모 클래스를 상속
class Student(CommonInfo):
    ...
    # 부모의 Meta 클래스를 상속
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

  • 자식에게 상속된 Meta 클래스는 abstract = False로 변경됨.
  • 자식도 추상 클래스가 되길 원하면 abstract = True로 다시 선언하면 됨.
  • 추상 클래스의 Meta 옵션은 사용상 주의해야할 옵션들이 있다.
  • 이름을 부여하지 않으면 (class의 소문자 이름)_set
  • 관계에 이름을 부여할 수 있다. (related_name)
  • 관계를 가지는 테이블 별로 다른 related_name을 가져야 한다.
  • 추상클래스의 관계 필드에 related_name이 지정되었을 경우, 자식 클래스들이 같은 related_name을 가질 수 있다.
  • ’%(class)s’ 는 자식 클래스의 소문자화된 이름으로 치환.
  • ’%(app_label)s’는 자식 클래스가 선언된 앱의 소문자 이름.
  • related_query_name는 역참조할때 적는 이름
# common/models.py

from django.db import models

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

    class Meta:
        abstract = True

# related_name = 'common_childa_related'
class ChildA(Base):
    pass

# related_name = 'common_childb_related'
class ChildB(Base):
    pass



# rare/models.py:

from common.models import Base

# related_name = 'rare_childb_related'
class ChildB(Base):
    pass

# related_name & related_query_name

class Car(models.Model):
    name = models.CharField(max_length = 40)
    manufacturer = models.ForeignKey(
    	ManuFacturer, 
    	on_delete = models.CASCADE,
    	related_name = 'cars',
    	related_query_name = 'manufacturer_car'
    	)
 
 >>> m.cars
 --> car 매니저가 호출됨
 
 >>> Manufacturer.objects.filter(manufacturer_car__name__contains="3")
 --> 3 들어가는 이름의 차를 가진 Manufacturer 역참조

모델 상속 2. Multi-table inheritance

  • 부모 모델이든 자식 모델이든 모두 각자의 데이터베이스 테이블을 가진다.
  • 공통 필드는 부모 모델 클래스에서 설정, 데이터도 부모에 저장.
  • 자식 모델의 데이터는 자식 모델 테이블에 저장. (부모에 대한 링크(OneToOne)를 가진다.)
  • OneToOneField를 직접 지정하고 싶다면, OneToOneField 를 추가하고 필드 옵션에 parent_link=True로 설정해주면 된다.
from django.db import models

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

# Place를 상속받았으므로 부모테이블의 name, address 필드도 사용 가능.
class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

  • 자식 모델 클래스를 통해 부모 모델에 접근 가능하다.
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

# If p in not a Restaurant object, 
>>> p.restaurant  # Restaurant.DoesNowExist 예외 발생
  • 자동으로 제공되는 restaurant 어트리뷰트를 통해 Restaurant 객체에 접근 할 수도 있는데 이때, 어트리뷰트 이름은 자식 클래스의 이름을 소문자화한 이름

모델 상속 2-1. Meta and multi-table inheritance

  • multi-table 상속의 경우 기본적으로 자식모델은 부모모델의 Meta 클래스를 상속받지 않는다. (각각의 테이블을 가지기 때문)
  • 자식모델에 ordering, get_latest_by 옵션이 지정되지 않은 경우에 한해, 부모 모델의 해당 설정값을 상속받는다.
class ChildModel(ParentModel):
    ...
    class Meta:
        # 부모 모델 클래스의 ordering 옵션을 무효화 한다.
        ordering = []

모델 상속 2-2. Inheritance and reverse relations

  • 상속받는 모델 클래스와 ManyToMany 관계를 가지려면 related_name을 설정해준다.
class Supplier(Place):
    customers = models.ManyToManyField(Place, related_name='provider')

모델 상속 3. Proxy models

  • 테이블을 가지고 있는 부모 모델 클래스는 상속받되 자식 모델은 테이블을 만들 필요가 없는 경우
  • proxy 모델(자식 모델)을 이용해서 부모 모델의 객체를 만들고 수정, 삭제…(부모 테이블 상에서)
  • 부모(원본)모델의 설정 변경없이 proxy(자식)모델에서 설정 변경 가능
  • 자식 모델의 Meta 클래스에 proxy=True 로 선언
  • 필드를 만들 수 없다.
  • 한 테이블의 속성을 나눠서 관리하고 싶을때 사용.
from django.db import models

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

# Person 클래스를 상속받는 클래스의 Meta 클래스에 proxy = True 선언 --> proxy model이 된다.
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

  • 어떤 모델 클래스로 쿼리 했는지에 따라 객체의 타입이 결정된다. proxy model의 속성들은 부모 모델이 영향을 주지 않음.
  • proxy 모델은 추상클래스가 아닌 하나의 모델만 상속받을 수 있다.(Meta 옵션 상속받음)
  • 추상클래스를 상속받을 경우 그 추상클래스가 필드를 가지지 않아야 하며. 이 경우 몇개든 상속 가능.

모델 상속 3-1. Proxy models manager

  • proxy 모델에 manager를 지정하지 않은 경우 부모의 manager를 상속받는다.
# 기본 manager 변경 방법
from django.db import models

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

class NewManager(models.Manager):
    ...
    pass

# Person을 상속받지만 새로운 매니저 지정
class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

부모의 클래스의 manager를 상속받되, 새로운 manager를 추가하려면 추상클래스에 새로운 manager를 선언하고 그 클래스를 상속받는다.

from django.db import models

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

# NewManager가 될 새로운 추상클래스 선언
class ExtraManagers(models.Model):
    secondary = NewManager()

    class Meta:
        abstract = True

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

모델 상속 3-2. Differences between proxy inheritance and unmanaged models

unmanaged model

  • 자동으로 테이블을 생성하지 않게 managed=False옵션을 준 모델은 proxy 클래스와는 다르다.
  • 원본테이블이 변경될 때마다 데이터베이스 테이블을 직접 생성, 수정해야한다.
  • 원본 모델의 manager와 상관없음. 자체 모델의 manager 선언을 따름.

proxy model

  • 원본 모델이 변경되어도 수정할 필요 없다.
  • 원본 모델의 디폴트 manager를 포함한 모든 manager를 상속받는다.

두가지 옵션의 사용

  • 만약에 당신이 이미 존재하는 모델이나 데이터베이스 테이블을 참조하되, 원본 테이블의 모든 컬럼이 필요하지 않다면, Meta.managed=False 옵션을 사용하세요. Django에 의해 제어되지 않는 데이터베이스 뷰나 테이블을 사용하는 경우에 유용합니다.

  • 만약에 기존 모델의 파이썬 코드들을 오버라이드 하되, 원본 모델과 동일한 필드를 가지도록 하고 싶다면 Meta.proxy=True 옵션을 사용하세요.

모델 상속 3-3. 다중 상속 Multiple Inheritance

  • 파이썬의 name resolution rules이 적용된다.
  • 부모가 여럿일때 첫번째 부모의 Meta 클래스를 상속받는다.

mix-in

  • 타임스탬프 믹스인은 created_timestamp, modified_timestamp를 가짐

UserCreationForm 사용하기

문서

  • AbstractUser를 상속받아 User model을 생성했을때 Signup 등에 UserCreationForm을 사용할 수 있다.
blog comments powered by Disqus