Django Model 공부

민준수·2021년 10월 13일
0

Model

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
);
-- serial 속성은 int auto increment 속성이다.
-- 위 코드는 PostgreSQL을 기준으로 생성한 것 이다. 만약 다른 DB를 사용하면 그것에 맞는 SQL문을 생성해준다.

장고에서 모델을 상속받아 생성하게되면 아래와 같은 SQL문을 자동으로 생성하여 테이블을 생성해준다.

Field Option

  • Null: True면 Django는 NULL 값을 데이터베이스에 저장한다. Default는 False 이다.

  • Blank: True면 Django는 데이터베이스에 빈 값을 저장한다. Null과의 차이점은 유효값 검증 부분에 있다. True면 Form Validation에서 빈 값을 허용해준다. Default는 False이다.

  • Choices: Widget에서 Select box를 보여 준다.

    YEAR_IN_SCHOOL_CHOICES = [
        ('FR', 'Freshman'),
        ('SO', 'Sophomore'),
        ('JR', 'Junior'),
        ('SR', 'Senior'),
        ('GR', 'Graduate'),
    ]
    # 만약 이러한 튜플을 choices 속성으로 저장하게 된다면 실제 DB에는 FR이 저장되고, Widget에서는 Freshman 속성이 보이게 된다.
    
    # 다른 방식으로 Choice 필드 구현하기
    # 아래와 같은 방식은 Choice를 enum 형식으로 구현한 것이다.
    class Card(models.Model):
    
        class Suit(models.IntegerChoices):
            DIAMOND = 1
            SPADE = 2
            HEART = 3
            CLUB = 4
    
        suit = models.IntegerField(choices=Suit.choices)
  • Default: 저장될 필드의 Default 값을 지정해준다. 이때, Default 값은 함수를 지정해줄 수 있으며, 매 생성 시마다 실행된다.

    # Default 값으로 함수를 사용하는 예시
    def contact_default():
        return {"email": "to1@example.com"}
    
    contact_info = JSONField("ContactInfo", default=contact_default)
  • help_text: Widget에서 텍스트를 보여주기 위해 사용된다.

  • Primary_key: True면 해당 필드를 PRIMARY KEY 속성으로 지정해준다. 만약 따로 지정해주지 않으면 장고는 자체적으로 IntegerField를 PK로 생성하고, 자동으로 증가하게 해준다.

  • Unique: True면 해당 값을 테이블에서 유일하게 저장할 수 있도록 해준다.

  • Verbose_name: 필드명의 별명(?)을 설정해준다. (관계를 위한 필드에서는 사용 불가)

  • editable: True면 ModelForm 혹은 admin에서 나오지 않게 된다. Default는 True

  • error_message: 만약 ValueException이 발생할 경우 각자의 Case에 맞는 에러 메시지를 보여주기 위하여 사용된다.

  • Unique_for_date: 어떠한 필드에 Date와 엮여서 유니크하게 설정하기 위해 사용된다. 만약 Title에 unique_for_date='pub_date' 속성을 추가해준다면, Title과 pub_date가 중복되는 데이터는 하나만 저장되게 된다.

  • validators: 값이 저장될 때 유효한 값인지 검증하기 위한 수단으로 사용된다.

PRIMARY KEY 추가 설명

# Django는 위에서 설명한 것과 같이 PK를 명시하지 않으면 자체적으로 PK를 생성해준다.
id = models.BigAutoField(primary_key=True)

Field Types

  • AutoField: IntegerField에 자동 증가 옵션이 추가된 필드. ID에 사용된다.

  • BigAutoField: BigIntergerField에 자동 증가 옵션이 추가된 필드.

  • BigIntegerField: 64-bit integer Field로 -9223372036854775808 ~ 9223372036854775807 까지 저장 된다.

  • BinaryField: Raw Binary Data를 저장하기 위해 사용되는 필드로 editable = False가 지정되어있다. 또한, 인자로 max_length를 받을 수 있다.

    BinaryField의 오용: 이는 정적 파일들을 저장하는 필드가 절대 아니다.

  • BooleanField: True or False의 값을 저장하기 위한 필드. Default Value는 None이다.

  • CharField: 적은 양의 문자열을 저장하기 위한 필드 (만약, 긴 문자열이라면 TextField를 사용하자!) CharField의 경우 max_length를 필수 인자로 받는다.

  • DateField: Python의 datetime.date instance를 저장해주기 위한 필드. 추가적으로 2개의 인자를 받을 수 있다. auto_now = True의 경우 해당 Model이 저장될 때마다 현재 시간을 저장해준다. auto_now_add = True 해당 Model에 새로운 데이터가 입력될 때만 현재 시간을 저장해준다. 만약 두 옵션이 True 라면 Django는 해당 필드를 editable = False, blank = True를 자동으로 지정해준다.

  • DateTimeField: Date 뿐만 아니라 Time까지 기록해주는 필드이다. 필요한 속성은 DateField와 동일하다.

  • DecimalField: 정확한 소수점 자리수를 저장해주는 필드이다. 필수 인자는 max_digits(최대 숫자 개수), decimal_places(소수점 이하 자리수 개수)로 총 2개이다.

  • DurationField: Python의 datetime.timedelta 를 저장해주는 필드로, 특정한 시간을 저장해줄 수 있다.

  • EmailField: max_length=254와 EmailValidator가 적용된 CharField 필드이다.

  • FileField: 파일을 저장해주는 필드로, 2개의 인자를 받을 수 있다. upload_to = 'path'는 해당 파일이 저장될 위치를 지정해줄 수 있는 속성이며, 이때 '%Y/%m/%d'를 사용하여 해당 연/월/일을 자동으로 찾아 저장해줄 수도 있다. 또한, upload_to는 호출에 의해 작성될 수도 있는데, 아래 코드처럼 새로운 함수를 만들어 경로를 지정해줄 수도 있다. max_length 는 Default로 100으로 지정되어 있다.

    def user_directory_path(instance, filename):
        # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
        return 'user_{0}/{1}'.format(instance.user.id, filename)
    
    class MyModel(models.Model):
        upload = models.FileField(upload_to=user_directory_path)
  • FilePathField: 추후 공부

  • FloatField: 소수를 저장해주는 필드이다. Decimal Type은 직접 최대 자리수를 지정해줄 수 있지만, FloatField는 지정할 수 없다.

  • ImageField: FileField를 상속받지만, 유효한 이미지인지 검증하는 Validator가 포함되어있다. 또한 이미지가 저장될 때마다 height와 width를 지정? 해줄 수 있는 인자를 받을 수 있다. 그렇기 때문에 Pillow 라이브러리가 필수적으로 필요하다.

  • IntegerField: -2147483648to2147483647 까지의 정수를 저장할 수 있는 필드이다.

  • GenericIPAddressField: IP를 저장해줄 수 있는 필드로, 2가지의 인자를 추가적으로 받을 수 있다. Protocol = 'both' or 'IPv4' or 'IPv6' 중 하나를 선택할 수 있으며, unpack_ipv4 = able은 IPv4를 IPv6 형식으로 저장히지 않도록 하는 속성이다.

  • JSONField: JSON 형식을 저장할 수 있는 필드로 2가지의 인자를 받을 수 있다. Encoder 인자는 JSON을 인코딩할 형식을 지정해줄 수 있고, Decoder 인자는 JSON을 디코딩할 형식을 지정해줄 수 있다. 두개 모두 json.JSONEncoder, Decoder를 기본으로 사용한다.

  • SlugField: 신문사에서 주로 사용하는 Slug를 저장해주는 필드로, max_length를 지정해줄 수 있다. Default는 50이다. 또한 Field Option 으로 db_index=True로 설정되어 있다.

  • TextField: 큰 문자열을 저장하는 필드로 max_length를 지정해줄 필요가 없다.

  • TimeField: Python의 datetime.time 을 저장해주는 필드이다.

  • URLField: URLValidator와 max_length = 200 이 설정된 CharField이다.

  • UUIDField: Python의 UUID를 저장해주는 필드로, PK로 주로 사용되는 AutoField를 대신하기에 좋은 필드이다.

Relationship

1:N 관계

from django.db import models

class Manufacturer(models.Model):
    # ...
    pass

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    # ...

만약 1:N의 관계를 정의하고 싶다면 ForeignKey Field를 사용하면 된다. 이때, 첫 번째 인자는 참조할 Model을 작성하고, on_delete 속성으로 부모 Model 이 삭제되었을 때 처리를 해준다.

ForeignKey Options

  • on_delete
    1. CASCADE: 만약 부모가 삭제된다면, 자식들도 전부 삭제해주는 옵션이다.
    2. PROTECT: 만약 자식이 있는 부모가 있다면 삭제를 막아주는 옵션이다.
    3. RESTRICT: 뭔지 모르겠다
    4. SET_NULL: 만약 부모가 삭제되었다면, 자식 필드를 NULL로 채워준다. 이는 무조건 null=True 옵션이 지정되어있어야한다.
    5. SET_DEFAULT: 만약 부조가 삭제되었다면 저장된 자식 필드의 값을 Default로 지정한다.
  • Limit_choices_to: 추후 공부
  • related_name: 부모 Model에서 참조할 수 있는 관계명을 지정해준다. 만약 '+'를 지정한다면 부모에서는 자식 Model을 접근할 수 없어진다.
  • related_query_name: 자식 Model 에서 참조할 수 있는 이름을 지정해준다.

M:N 관계

from django.db import models

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

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

만약 M:N 관계를 정의하고 싶다면 ManyToManyField를 사용하면 된다. 이때, 어느 모델에 다대다 필드를 정의하든 상관 없지만, 오직 한쪽에서만 지정해주어야 한다. 일반적으로는 Form에의하여 수정되어질 모델에 정의한다. 위의 예시는 <피자가 여러 토핑을 가진다> 가 <토핑이 여러 피자에 놓여있다> 보다 자연스럽기 때문에 다음과 같이 정의하였다. 이렇게 정의하면 Django는 자동으로 Topping과 Pizza를 연결해주는 새로운 Model을 생성하여 관리해준다.

ManyToManyField Options

  • related_name: ForeignKey와 동일함.

  • related_query_name: ForeignKey와 동일함.

  • limit_choices_to: ForeignKey와 동일함.

  • symmetrical options: 참조할 Model을 문자열로 지정해도 된다.

    from django.db import models
    
    class Person(models.Model):
      	# 둘 중 하나를 선택해서 사용
        friends = models.ManyToManyField("self")
        firends = models.ManyToManyField('Person')
  • through: Django에서는 M:N 관계의 경우 자동으로 새로운 테이블을 생성해주지만, 만약 이를 직접 관리해주고 싶을 때 사용하는 옵션이다.

  • througn_fields: 만약 M:N 관계로 생성되는 모델이 특정한 모델에 2개 이상 ForeignKey를 사용한다면 정확히 어떠한 관계를 만들어 줄 것인지 적어주기 위하여 사용하는 필드이다.

만약 ManyToManyField를 사용하여 자동 생성되는 Model에 여러 필드를 추가시켜주고 싶다면 다음 방법을 사용해보자.

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, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

만약 다음과 같이 작성하게되면, Person 과 Group을 M:N 관계로 묶어주는 Membership Model을 사용자가 직접 생성하여 사용할 수 있다. 또한, 사용자가 직접 필요한 칼럼들을 추가적으로 정의해줄 수 있다. 이러한 모델을 설계하게 된다면, Membership Model 에는 무조건 ForeignKey 속성을 정의 해주어야한다.

# M:N 관계 데이터를 생성하는 방법은 다움과 같다.
entry = Entry.objects.get(pk=1)
joe = Author.objects.create(name="Joe")
# 아래와 같이 하나의 instance를 저장해줄 수 있다.
entry.authors.add(joe)

john = Author.objects.create(name="John")
paul = Author.objects.create(name="Paul")
george = Author.objects.create(name="George")
ringo = Author.objects.create(name="Ringo")
# 아래와 같이 여러개의 instance를 한번에 저장해줄 수도 있다.
entry.authors.add(john, paul, george, ringo)

# Group Model에서 연관된 Person 찾아보는 방법: [Model 명]_set 사용
person = Person.objects.get(pk=1)
member_ship = person.group_set.all()
membership = person.membership_set.get(group=group)

# Person Model에서 연관된 Group 찾아보는 방법: 작성한 필드명 사용
group = Group.objects.get(pk=1)
member_ship = group.members.all()

# 해당 Person 필드들을 전부 삭제하기
group.members.remove(person)

# 해당 그룹에 대한 필드들을 전부 삭제하기
group.members.clear()

# Person 모델에서 Group 모델 데이터 조회하기
Person.objects.filter(group__name='The Beatles', membership__date_joined__gt=date(1961,1,1))

# Group 모델에서 Person 모델 데이터 조회하기
Group.objects.filter(members__name__startswith='Paul')

# Membership 모델에서 알맞는 Group, Person 모델 조회하기
membership = Membership.objects.get(group=group, person=person)

1:1 관계

from django.db import models

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

    def __str__(self):
        return "%s the place" % self.name

class Restaurant(models.Model):
    place = models.OneToOneField(
        Place,
        on_delete=models.CASCADE,
        primary_key=True,
    )
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

    def __str__(self):
        return "%s the restaurant" % self.place.name

class Waiter(models.Model):
    restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)

    def __str__(self):
        return "%s the waiter at %s" % (self.name, self.restaurant)

Model을 다음과 같이 설계하면 1:1 관계에대한 모델을 만들 수 있다. 1:1 관계는 모델을 상속 받는 새로운 모델이 필요할 때 유용하다.

# Place와 관계를 맺는 Restaurant를 찾는 방법
place = Place.objects.get(pk=1)
place.restaurant

# Restaurant와 관계를 맺는 Place를 찾는 방법
restaurant = Restaurant.objects.get(pk=1)
restaurant.place

# 만약 관계를 맺는 모델이 없다면 ObjectDoesNotExist 에러를 발생시킨다.
# 따라서 이러한 에러를 막기 위하여 try - catch를 사용하거나 hasattr()를 사용한다.
hasattr(person, 'restaurant') # Return True or False

# 만약 이미 존재하는 관계에 새로운 속성을 부여하게 된다면 쌍방으로 변경된다.
place2 = Place.objects.get(pk=2)
restaurant.place = place2
restaurant.save()

place2.restaurant # Return restaurant
restaurant.place # Return place2

Field Naming Restrictions

  1. 필드명은 Python 예약어(pass, continue ...)을 사용할 수 없다.
  2. 필드명에 하나 이상의 underscore(_)를 사용할 수 없다.
  3. 필드명은 underscore(_)로 끝날 수 없다.

Meta Option

  • Abstract: 해당 모델이 추상 모델이면 True를 설정해준다.

  • db_table: 만약 따로 지정해주지 않으면 Django는 자동으로 APP_Model으로 테이블을 생성해주지만, 사용자가 커스텀 해주고 싶다면 따로 지정해준다.

  • db_tablespace: 아직은 이해하지 못함.

  • Managed: True면 Django에서 Model이 수정될 경우 migrate 명령을 통해서 테이블 생성, 수정, 삭제 등을 관리해준다. Default는 False 이다. 만약 이미 존재하는 Table이거나 View를 사용하는 경우 유용한 기능이다.

  • Ordering: Django에서 Default Order을 설정해준다. List 타입을 넣어주면 된다. 만약 내림차순 정렬을 하고 싶다면, -를 붙여서 사용하면된다.

  • permissions: 아직은 이해하지 못함.

  • Proxy: 아직은 이해하지 못함.

  • Indexes: Model의 index를 지정해주고 싶다면 사용하면 된다. 사용법은 다음과 같다.

    from django.db import models
    
    class Customer(models.Model):
        first_name = models.CharField(max_length=100)
        last_name = models.CharField(max_length=100)
    
        class Meta:
            indexes = [
                models.Index(fields=['last_name', 'first_name']),
                models.Index(fields=['first_name'], name='first_name_idx'),
            ]
  • unique_together: 여러 필드를 묶어서 unique 옵션을 줄 수 잇다.

  • index_together: 새로운 index를 선언하는 방식? 이해 못함....

Model Method

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()

    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"

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)
      
		def __str__(self):
      	return 'Instance Name'
      
		def get_absolute_url(self):
      	return reverse('AppName')
      
    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs) # Call Real save function
        do_something_else()

Model 에서 사용할 수 있는 여러 method들은 다음과 같다.

  1. Baby_boomer_status: 장고에서 모델과 관련된 함수를 만들어 줄 수 있다. 이때, 인자를 받아 model의 값들을 변경해줄 수도 있다.
  2. Full_name: 장고에서 어떠한 함수를 변수로 사용하고 싶다면 @property 옵션을 사용해주면 된다. 다음과 같이 선언해주면 instance.full_name 과 같은 형식으로 사용 가능하다.
  3. get_absolute_url: 이미 선언된 모델의 url 을 overriding 하는 것으로 instance.url 을 사용하기 위하여 인스턴스가 반환할 url을 설정해주는 것이다. 이때는 reverse를 사용해서 url name을 사용해줄 수 있다.
  4. Save: 장고에 이미 있는 save란 메서드를 override 하여 저장이 되기전 혹은 저장이 된 이후 어떠한 작업이 실행되게 할 수 있다.

여러 Model 관리하기

# models/__init__.py
from .organic import Person
from .synthetic import Robot

만약 하나의 앱에서 다양한 모델을 정의해야될 경우 models.py를 관리하는 것보다 models 디렉터리를 생성하고 __init__.py 를 생성하여 필요한 여러 모델들을 정의하는 것이 더욱 효율적이다.

QuerySet

QuerySet 특징

  1. Filtered QuerySets are unique: 필터링된 쿼리셋들은 모두 고유하다.
  2. QuerySets are lazy: QuerySet은 실제로 사용되기 전까지 조회를 하지 않는다.

Retrieving objects

# 다음과 같이 입력하면 Entry Model의 모든 객체를 불러올 수 있다.
all_entries = Entry.objects.all()

# 다음과 같이 입력하면 조건에 맞는 객체들을 불러올 수 있다.
Entry.objects.filter(pub_date__year=2006)

# 다음과 같이 입력하면 조건에 맞지 않은 객체들을 불러올 수 있다.
Entry.objects.exclude(pub_date__year=2006)

# 다음과 같이 입력하면 여러 조건들을 Chaining하여 객체를 불러올 수 있다.
Entry.objects.filter(headline__startswith='What')\
		.exclude(pub_date__gte=datetime.date.today())\
  	.filter(pub_date__gte=datetime.date(2005, 1, 30))
    
# 다음과 같이 입력하면 조건을 만족하는 하나의 QuerySet을 반환 해준다.
first = Entry.objects.get(pk=1)

get()의 경우 해당 조건을 만족하는 결과가 없다면 DoesNotExist Exception을 반환하고, 해당 조건을 만족하는 결과가 여러개라면 MutipleObjectsReturned Exception을 반환해준다.

Limiting QuerySets

# LIMIT 5
Entry.objects.all()[:5]

# OFFSET 5 LIMIT 5
Entry.objects.all()[5:10]

Django의 경우 Negative indexing([-1])를 지원하지 않는다.

Field lookups

Django에서 WHERE 조건절에 특정한 조건을 넣고 싶다면 filter() or exclude() or get()을 사용하며 lookuptype을 사용해서 조회할 수 있다.

# 만약 ForeignKey를 기준으로 조회하고 싶다면 다음과 같이 입력하면 된다.
Entry.objects.filter(blog_id=1)

# exact: 대소문자까지 정확한 값을 입력해주는 것
Entry.objects.filter(headline__exact='Cat bites dog')
# SQL: SELECT ... WHERE headline = 'Cat bites dog';

# iexact: 대소문자와는 상관없이 정확한 값을 입력해주는 것
Blog.objects.filter(name__iexact="beatles blog")

# contains: 해당 문자열이 포함되기만 하는 것
Entry.objects.filter(headline__contains='Lennon')
# SQL: SELECT ... WHERE headline LIKE '%Lennon%';

# startswith: 해당 문자열로 시작하는 것들을 찾아줌.
# endswith: 해당 문자열로 종료하는 것들을 찾아줌.


# lt, lte, gt, gte: 작다, 작거나 같다, 크다, 크거나 같다.
Entry.objects.filter(id__lt=5)
Entry.objects.filter(id__gte=5)

# in: 해당 리스트에 포함되어 있는 것들을 찾아줌.
Entry.objects.filter(id__in=[1, 4, 7])

Field를 조회할 때 만약 다른 Model 과의 관계를 가진 Field를 lookup 하게 된다면 Django내에서 자동으로 해당 Model과 Join을 시켜준다.

F expressions

from django.db.models import F

Entry.objects.filter(number_of_comments__gt=F('number_of_pingback'))

사실 잘 모르겠당...

Caching and QuerySets

Django에서 Database access를 최소화 하기 위하여 각각의 QuerySet에는 cache가 포함되어 있다. 따라서 이를 이해하고 사용할 필요가 있다.

print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])

만약 다음과 같이 사용하게 된다면 장고에서는 Database에 2번을 접근해서 리스트를 보여줄 것이다. 그렇다면 이를 어떻게 최소화 하며 설계할 수 있을까?

queryset = Entry.objects.all()
print([p.headline for p in queryset]) # Evaluate the query set.
print([p.pub_date for p in queryset]) # Re-use the cache

다음과 같이 queryset이라는 변수에 QuerySet을 넣어준다면 최초에는 이를 조회하겠지만, 그 다음에는 Database를 조회하지 않고, QuerySet에 포함된 cache를 통해서 조회할 것이다. 그렇다면cache를 사용하지 않은 경우는 어떤 경우일까?

# Senario #1
queryset = Entry.objects.all()
print(queryset[5]) # Queries the database
print(queryset[5]) # Queries the database again

# Senario #2
queryset = Entry.objects.all()
[entry for entry in queryset] # Queries the database
print(queryset[5]) # Uses cache
print(queryset[5]) # Uses cache

Django에서 Limiting QuerySet을 사용하는 경우 cache를 사용하지 않고, 계속 새롭게 Database에 조회할 것이다. 따라서 시나리오 1번의 경우에는 cache를 사용하지 않을 것이다. 하지만, QuerySet을 미리 계산해두고 Limiting QuerySet을 사용할 경우 cache를 사용해서 조회할 것이다. 이를 항상 고려해서 QuerySet을 사용한다면 더욱 효율적으로 관리할 수 있을 것이다.

0개의 댓글