Django Model

์žฅ๊ณ ๋Š” ORM๊ธฐ๋ฒ•์— ๋”ฐ๋ผ ํ…Œ์ด๋ธ”์„ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋กœ ์ •์˜ํ•˜๊ณ , ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ์€ ํด๋ž˜์Šค์˜ ๋ณ€์ˆ˜๋กœ ๋งคํ•‘ํ•œ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •

# settings.py
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydatabase',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'mypassword',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

๊ธฐ๋ณธ์ ์œผ๋กœ sqlite3๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ๋งŒ์•ฝ์— ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ENGINE์˜ ๋’ท๋ถ€๋ถ„์„ ์ˆ˜์ •ํ•˜๋ฉด๋œ๋‹ค.

ENGINE

  • django.db.backends.mysql

  • django.db.backends.oracle

  • django.db.backends.postgresql

  • django.db.backends.sqlite3

NAME

์—ฌ๊ธฐ์„œ name์€ ํ”„๋กœ์ ํŠธ ์ €์žฅ๋  ํŒŒ์ผ ๋ช…์ด๋‹ค.

์‚ฌ์šฉ์ž ์„ค์ •

SQLite ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ USER, PASSWORD, HOST๋ฅผ ์ถ”๊ฐ€ ์„ค์ •ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

>> mysql ์—ฐ๋™ํ•˜๊ธฐ

๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ

๊ฐ ๋ชจ๋ธ์€ Django.db.models.Model ํด๋ž˜์Šค์˜ ์„œ๋ธŒํด๋ž˜์Šค๋กœ ํ‘œํ˜„๋œ๋‹ค. ๊ฐ๊ฐ์˜ ๋ชจ๋ธ์€ ์—ฌ๋Ÿฌ๊ฐœ์˜ ํด๋ž˜์Šค ๋ณ€์ˆ˜(๋ชจ๋ธ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ•„๋“œ)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

from django.db import models

class ๋ชจ๋ธ๋ช…(models.Model):
    # ๋ชจ๋ธ์˜ ์†์„ฑ ์ง€์ •ํ•˜๊ธฐ

models๋Š” ์ƒ์„ฑํ•œ ๋ชจ๋ธ์ด ์žฅ๊ณ  ๋ชจ๋ธ์ž„์„ ์˜๋ฏธํ•œ๋‹ค. ์žฅ๊ณ ๋Š” ์ƒ์„ฑ๋œ model์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜์–ด์•ผํ•œ๋‹ค๊ณ  ์•Œ๊ฒŒ๋œ๋‹ค.

์•„๋ž˜์—์„œ ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด์ž.

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

์„ค๋ฌธ์กฐ์‚ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— Question๊ณผ Choice ๋‘๊ฐœ์˜ ๋ชจ๋ธ์„ ์ƒ์„ฑํ•ด๋ณด์ž. ์ด๋•Œ ๋‘๊ฐœ์˜ ๋ชจ๋ธ์€ ์—ฐ๊ด€๋˜์–ด ์žˆ๋‹ค.

์†์„ฑ(Field)

๋ชจ๋ธ์€ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์†์„ฑ์„ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ฐ ํ•„๋“œ๋งˆ๋‹ค ์–ด๋–ค ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…(ํ…์ŠคํŠธ, ์ˆซ์ž, ๋‚ ์งœ, ๋‹ค๋ฅธ ๊ฐ์ฒด ์ฐธ์กฐ ๋“ฑ)์„ ๊ฐ€์ง€๋Š”์ง€๋ฅผ ์ •ํ•ด์•ผํ•œ๋‹ค.

๊ฐ ์†์„ฑ์€ Field ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋กœ ํ‘œํ˜„๋œ๋‹ค.

Field

์„ค๋ช…

AutoField(**options)

id๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ž๋™์œผ๋กœ ์ฆ๊ฐ€ํ•˜๋Š” IntegerField์ด๋‹ค. ๋ชจ๋ธ์˜ ๊ธฐ๋ณธํ‚ค ํ•„๋“œ๋ฅผ ๋ณ„๋„๋กœ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋œ๋‹ค.

BooleanField(**options)

true / false ํ•„๋“œ์ด๋‹ค. null๊ฐ’ ํ—ˆ์šฉ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด NullBooleanField ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด๋œ๋‹ค. Default ์˜ต์…˜์„ ์ •์˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์€ None์ด๋‹ค.

CharField(max_length=None,**options)

๊ธ€์ž์ˆ˜๊ฐ€ ์ œํ•œ๋œ ํ…์ŠคํŠธ๋ฅผ ์ •์˜ ์ œ๋ชฉ๊ณผ ๊ฐ™์ด ์งง์€ ๋ฌธ์ž์—ด ์ •๋ณด๋ฅผ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. max_length ํ•„๋“œ์˜ ์ตœ๋Œ€๊ธธ์ด๋Š” ํ•„์ˆ˜ ์˜ต์…˜์ด๋‹ค.

DecimalField(max_digit=None, decimal_palces=None, **options)

๊ณ ์ • ์†Œ์ˆ˜๋กœ Python์˜ Decimal ์ธ์Šคํ„ด์Šค๋กœ ๋‚˜ํƒ€๋‚œ๋‹ค. max_digits ์ˆซ์ž์— ํ—ˆ์šฉ๋˜๋Š” ์ตœ๋Œ€ ์ž๋ฆฟ์ˆ˜์ด๋‹ค. decimal_palces ์ˆซ์ž์™€ ํ•จ๊ป˜ ์ €์žฅ๋  ์†Œ์ˆ˜ ์ž๋ฆฟ์ˆ˜์ด๋‹ค. DecimalField(โ€ฆ, max_digits=5, decimal_palces=2) ๋Š” ์ตœ๋Œ€ 999 ์†Œ์ˆ˜์  2์ž๋ฆฌ ์ดํ•˜์ด๋‹ค.

IntegerField(**options)

์ •์ˆ˜ ๊ฐ’

TextField(**options)

๊ธ€์ž ์ˆ˜์— ์ œํ•œ์ด ์—†๋Š” ๊ธด ํ…์ŠคํŠธ๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค.

DateTimeField(auto_now=False, auto_now_add=False,**options)

๋‚ ์งœ์™€ ์‹œ๊ฐ„์„ ์ •์˜ํ•œ๋‹ค. auto_now ๋Š” ๊ฐ์ฒด๊ฐ€ ์ €์žฅ๋  ๋•Œ๋งˆ๋‹ค ๋งค๋ฒˆ ์ž๋™์œผ๋กœ ํ˜„์žฌ์‹œ๊ฐ„์ด ์„ค์ •๋œ๋‹ค.( ์ตœ๊ทผ ์ˆ˜์ • ๋“ฑ timestamp๋กœ์จ ์œ ์šฉํ•˜๋‹ค.) Model.save๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์ž๋™์œผ๋กœ ์ˆ˜์ •๋œ๋‹ค. auto_now_add ๊ฐ์ฒด๊ฐ€ ์ฒ˜์Œ ์ƒ์„ฑ๋  ๋•Œ ์ž๋™์œผ๋กœ ํ˜„์žฌ์‹œ๊ฐ„์ด ์„ค์ •๋œ๋‹ค. (์ƒ์„ฑ์˜ timestamp๋กœ ์œ ์šฉ) ๋งŒ์•ฝ ์ˆ˜์ •์„ ์›ํ•œ๋‹ค๋ฉด default=today or default=tiemzone.now ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด๋œ๋‹ค. ์ด 3๊ฐœ์˜ ์˜ต์…˜์€ ๊ฐ™์ด ์“ธ ์ˆ˜ ์—†์œผ๋ฉฐ, ๋งŒ์•ฝ auto ์˜ต์…˜์„ True๋กœ ์„ค์ •ํ•˜๋ฉด editable=False, blank=True ๋กœ ์„ค์ •๋œ๋‹ค.

ForeignKey(**options)

๋‹ค๋ฅธ ๋ชจ๋ธ ์ฐธ์กฐํ‚ค์ด๋‹ค. models.ForeignKey(Question, on_delete=models.CASCADE) ์—์„œ๋Š” ๊ฐ๊ฐ์˜ Choice๊ฐ€ ํ•˜๋‚˜์˜ Question์— ๊ด€๊ณ„๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค€๋‹ค.

FilePathField(path=None, match=None, recursive=False, max_length=100, **options)

ํŒŒ์ผ ์‹œ์Šคํ…œ์—์„œ ํŠน์ •ํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ํŒŒ์ผ์ด๋ฆ„์œผ๋กœ ์ œํ•œ๋œ๋‹ค. path ๋Š” ํ•„์ˆ˜ ์ธ์ˆ˜๋กœ ์ ˆ๋Œ€ ํŒŒ์ผ ์‹œ์Šคํ…œ ๊ฒฝ๋กœ์ด๋‹ค.(/home/images) match : ์„ ํƒ์  ์ธ์ˆ˜๋กœ ํŒŒ์ผ ์ด๋ฆ„์„ ํ•„ํ„ฐ๋งํ•  ๋•Œ ์‚ฌ์šฉํ•  ๋ฌธ์ž์—ด๋กœ ๋œ ์ •๊ทœํ‘œํ˜„์‹์ด๋‹ค. ๊ธฐ๋ณธํŒŒ์ผ ์ด๋ฆ„์— ์ ์šฉ๋œ๋‹ค. (foo.*\.txt$) recursive : ์„ ํƒ์  ์ธ์ˆ˜๋กœ path์˜ ๋ชจ๋“  ์„œ๋ธŒ ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ํฌํ•จ๋˜์•ผํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์ง€์ •(true/false) allow_files or allow_folders : ์„ ํƒ์  ์ธ์ˆ˜๋กœ ์ง€์ •๋œ ์œ„์น˜์— ํŒŒ์ผ or ํด๋”๋ฅผ ํฌํ•จํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ์ง€์ •ํ•œ๋‹ค(true/false). ๋‘˜ ์ค‘ ํ•œ๊ฐœ๋Š” ๋ฐ˜๋“œ์‹œ True์—ฌ์•ผํ•œ๋‹ค.

๋ชจ๋ธ ๊ด€๊ณ„

One-to-One

ํ•œ ํ…Œ์ด๋ธ”์˜ ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ๋‹จ ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๋งŒ์„ ์ฐธ์กฐํ•  ๋•Œ, ์ด ๋‘ ๋ชจ๋ธ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์ผ๋Œ€์ผ ๊ด€๊ณ„๋ผ๊ณ  ํ•œ๋‹ค.

์œ„์™€ ๊ฐ™์ด ๊ฐœ์ธ ๊ณ ๊ฐ ํ•œ๋ช…๋‹น ์—ฌ๊ถŒ์€ ํ•œ๊ฐœ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

OneToOneField

OneToOneField๋Š” ForeignKey ํ•„๋“œ์— unique=Ture ์˜ต์…˜์„ ์ค€ ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. ์ฆ‰, ForeignKye ๊ฐ’์ด ๋ฐ˜๋“œ์‹œ ๊ณ ์œ ํ•œ ๊ฐ’์ด์–ด์•ผํ•œ๋‹ค.

class ๋ชจ๋ธ๋ช…(modesl.Model):
  ํ•„๋“œ๋ช… = models.OneToOneField(๊ด€๊ณ„๋Œ€์ƒ๋ชจ๋ธ)

์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

class Customer(models.Model):
  name = models.CharField(max_length=30)
  address = models.CharField(max_length=100)

class Passport(models.Model):
  custom = models.OneToOneField(Customer)
  passportNo = models.CharField(max_length=15)

์ผ๋Œ€์ผ ๊ด€๊ณ„์—์„œ ํ•œ ๋ชจ๋ธ์ด ๋‹ค๋ฅธ ๋ชจ๋ธ์„ ์ฐธ์กฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

obj = Passport.objects.first()
obj.custom

Many-to-One

ํ•œ ํ…Œ์ด๋ธ”์— ์žˆ๋Š” ๋‘ ๊ฐœ ์ด์ƒ์˜ ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์žˆ๋Š” ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒฝ์šฐ์ด๋‹ค.

class ๋ชจ๋ธ๋ช…(models.Model):
  ํ•„๋“œ๋ช… = models.ForeignKey(์—ฐ๊ฒฐ์ƒ๋Œ€๋ชจ๋ธ, on_delete=์‚ญ์ œ์˜ต์…˜)

๋‹ค์Œ๊ณผ ๊ฐ™์ด ForeignKey๋ฅผ ์ด์šฉํ•ด์„œ ๋‹ค๋Œ€์ผ ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•œ ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์„ ์—ฌ๋Ÿฌ๊ฐœ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด์ž

class Customer(models.Model):
  name = models.CharField(max_length=50)

class Order(models.Model):
  customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
  product = models.CharField(max_length=50)

์—ฌ๊ธฐ์„œ ForeignKey์˜ ํ•„๋“œ๋ช…์„ ์ž์œ ๋กญ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์—ฐ๊ฒฐ๋Œ€์ƒ ๋ชจ๋ธ์˜ ์†Œ๋ฌธ์ž๋กœ ์ •ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

on_delete

์˜ต์…˜

์„ค๋ช…

CASCADE

๋ ˆ์ฝ”๋“œ๊ฐ€ ์‚ญ์ œ๋˜๋ฉด, ๊ทธ ๋ ˆ์ฝ”๋“œ๋ฅผ ์™ธ๋ž˜ํ‚ค๋กœ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ ํ•จ๊ป˜ ์‚ญ์ œํ•œ๋‹ค.(default)

PROTECT

์™ธ๋ž˜ํ‚ค๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ์‚ญ์ œํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค. ์‚ญ์ œ๋ฅผ ์‹œ๋„ํ•˜๋Š” ๊ฒฝ์šฐ ProtectedError๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

SET_NULL

์™ธ๋ž˜ํ‚ค๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ์‚ญ์ œ๋˜๋ฉด, ์™ธ๋ž˜ํ‚ค ํ•„๋“œ ๊ฐ’์ด null ๊ฐ’์ด ๋œ๋‹ค. ์ด๋•Œ ์™ธ๋ž˜ํ‚ค ํ•„๋“œ์— null=True ์˜ต์…˜์ด ์žˆ์„ ๋•Œ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค.

SET_DEFAULT

์™ธ๋ž˜ํ‚ค๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ์‚ญ์ œ๋˜๋ฉด, ์™ธ๋ž˜ํ‚ค ํ•„๋“œ์˜ ๊ฐ’์ด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ฐ”๋€๋‹ค. (default ์˜ต์…˜์ด ์„ค์ •๋˜์–ด ์žˆ์„ ๋•Œ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค.)

SET()

SET() ํ•จ์ˆ˜์— ๊ฐ’์ด๋‚˜ ํ˜ธ์ถœ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์™ธ๋ž˜ํ‚ค๊ฐ€ ์ฐธ์กฐํ•˜๋Š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ์‚ญ์ œ๋˜๋ฉด ์ „๋‹ฌ๋œ ๊ฐ’ ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๋กœ ์™ธ๋ž˜ํ‚ค๋ฅผ ์ฑ„์šด๋‹ค.

์žฌ๊ท€์  ๊ด€๊ณ„(Recursive)

ํ•œ ํ…Œ์ด๋ธ”์˜ ๋ ˆ์ฝ”๋“œ๋“ค์ด ๊ฐ™์€ ํ…Œ์ด๋ธ”์˜ ๋‹ค๋ฅธ ๋ ˆ์ฝ”๋“œ๋“ค๊ณผ ๊ด€๊ณ„๋ฅผ ํ˜•์„ฑํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์ž๋ฉด, ๊ฐ™์€ ์Šคํ„ฐ๋””์—์„œ ํ•œ๋ช…์ด ๋‹ค๋ฅธ ์Šคํ„ฐ๋””์›์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์˜ˆ๋กœ ๋“ค์ˆ˜์žˆ๋‹ค.

ID

Name

Tutor_ID

1

Joe Satriani

1

2

John Petrucci

1

3

Steve Vai

1

class StudyGroup(models.Model):
  name = models.CharField(max_length=30)
  tutor = models.ForiegnKey('self', on_delete=models.SET_NULL)

๋‹ค์Œ๊ณผ ๊ฐ™์ด 'self' ๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ •์˜๋˜์ง€ ์•Š์€ ํ…Œ์ด๋ธ”๊ณผ์˜ ๊ด€๊ณ„

๋ชจ๋ธ์„ ์ƒ์„ฑํ•  ๋•Œ ์•„์ง ์ •์˜๋˜์ง€ ์•Š์€ ํ…Œ์ด๋ธ”๊ณผ์˜ ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” '๋ชจ๋ธ๋ช…' ์„ ์ „๋‹ฌํ•˜๋ฉด๋œ๋‹ค.

class Order(models.Model):
  customer = models.ForeignKey('Cusotmer', on_delete=models.CASCADE)
  product = models.CharField(max_length=50)

class Customer(models.Model):
  name = models.CharField(max_length=50)

Many-to-Many

ํ•˜๋‚˜์˜ ํ…Œ์ด๋ธ”์˜ ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒฝ์šฐ์ด๋‹ค. ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ, ๋‘ ํ…Œ์ด๋ธ” ์‚ฌ์ด์˜ ๊ด€๊ณ„๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ฐธ์กฐ ์ •๋ณด๋ฅผ ๋‹ด์€ ์ƒˆ๋กœ์šด ํ…Œ์ด๋ธ”(์ค‘๊ฐœ๋ชจ๋ธ)์„ ์ƒ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค.

class ๋ชจ๋ธ๋ช…(models.Model):
  ํ•„๋“œ๋ช… = models.ManyToManyField(์—ฐ๊ฒฐ๋Œ€์ƒ๋ชจ๋ธ)

ManyToManyField๋ฅผ์ด์šฉํ•ด์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

class Topping(models.Model):
  name = models.CharField(max_length=10)

class Pizza(models.Model):
  name = models.CharField(max_length=10)
  toppings = models.ManyToManyField(Topping)

ManyToManyField

๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„์˜ ํ•„๋“œ๋ช…์€ ๋ณต์ˆ˜ํ˜•์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

์„œ๋กœ ๊ด€๊ณ„๋œ ๋ชจ๋ธ๋“ค ์ค‘ ์–ด๋Š ๊ณณ์— ModelToManyField๋ฅผ ์„ ์–ธํ•˜๋“  ์ƒ๊ด€ ์—†์ง€๋งŒ ํ•œ ๋ชจ๋ธ์—๋งŒ ์„ ์–ธํ•ด์•ผํ•˜๋ฉฐ, ์˜๋ฏธ๊ฐ€ ์ž์—ฐ์Šค๋กœ์šด ๊ณณ์— ์„ ์–ธํ•ด์ฃผ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

์ค‘๊ฐœ๋ชจ๋ธ(Intermediary Model)

๋‘ ํ…Œ์ด๋ธ”์˜ ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ด์ฃผ๋Š” ๋ชจ๋ธ์ด๋‹ค. ๋‘ ๋ชจ๋ธ์˜ ์™ธ๋ž˜ํ‚ค๋ฅผ ํ•„๋“œ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ManyToManyField ๋กœ ๊ด€๊ณ„์„ค์ •ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์ง€๋งŒ ์ง์ ‘ ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

- though

class ๋ชจ๋ธ๋ช…(models.Model):
  ํ•„๋“œ๋ช… = models.ManyToManyField(์—ฐ๊ฒฐ๋Œ€์ƒ๋ชจ๋ธ, through='์ค‘๊ฐœ๋ชจ๋ธ')
class Artist(models.Model):
    name = models.CharField(max_length=50)


class Band(models.Model):
    name = models.CharField(max_length=50)
    members = models.ManyToManyField(Artist, through='Membership')


class Membership(models.Model):
    artist = models.ForignKey(Artist, on_delete=models.CASCADE)
    band = models.ForignKey(Band, on_delete=models.CASCADE)
    is_founding_member = models.BooleanField()

- through_field

์ค‘๊ฐœ ๋ชจ๋ธ์„ ์ง์ ‘ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ์— ๋‘ ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๋Š” ์™ธ๋ž˜ํ‚ค ํ•„๋“œ๋ฅผ ๋ช…ํ™•ํžˆ ์„ ์–ธํ•ด์•ผํ•œ๋‹ค.

  • ์†Œ์Šค๋ชจ๋ธ(Source Model) : ManyToManyField๊ฐ€ ์žˆ๋Š” ๋ชจ๋ธ์„ ๋งํ•œ๋‹ค.

  • ํƒ€๊ฒŸ๋ชจ๋ธ(Target Model) : ManyToManyField์— ์ธ์ž๋กœ ์ „๋‹ฌ๋˜๋Š” ๋ชจ๋ธ์„ ๋งํ•œ๋‹ค.

๋งŒ์•ฝ ์ค‘๊ฐœ ๋ชจ๋ธ์—์„œ ํ•˜๋‚˜ ์ด์ƒ์˜ ์™ธ๋ž˜ํ‚ค ํ•„๋“œ๊ฐ€ ์†Œ์Šค ๋ชจ๋ธ ํ˜น์€ ํƒ€๊ฒŸ ๋ชจ๋ธ์„ ์ฐธ์กฐํ•œ๋‹ค๋ฉด, through_field ์˜ต์…˜์„ ํ†ตํ•ด ์™ธ๋ž˜ํ‚ค ์„ค์ •์„ ํ•ด์ค˜์•ผํ•œ๋‹ค.

class ํƒ€๊ฒŸ๋ชจ๋ธ(models.Model):
  ํ•„๋“œ1
  ํ•„๋“œ2

class ์†Œ์Šค๋ชจ๋ธ(models.Model):
  ํ•„๋“œ๋ช… = models.ManyToManyField(
    ํƒ€๊ฒŸ๋ชจ๋ธ,
    through=์ค‘๊ฐœ๋ชจ๋ธ,
    through_fields=('์†Œ์Šคํ•„๋“œ','ํƒ€๊ฒŸํ•„๋“œ',) # ๋ฐ˜๋“œ์‹œ ์†Œ์Šคํ•„๋“œ ํƒ€๊ฒŸํ•„๋“œ ์ˆœ์œผ๋กœ ์ „๋‹ฌํ•ด์•ผํ•œ๋‹ค.
  )
  ํ•„๋“œ2

class ์ค‘๊ฐœ๋ชจ๋ธ(models.Model):
  ํƒ€๊ฒŸํ•„๋“œ = models.ForeignKey(ํƒ€๊ฒŸ๋ชจ๋ธ)
  ์†Œ์Šคํ•„๋“œ = models.ForeignKey(์†Œ์Šค๋ชจ๋ธ)
  ์ถ”๊ฐ€์™ธ๋ž˜ํ‚คํ•„๋“œ = models.ForeignKey(๊ด€๊ณ„๋Œ€์ƒ๋ชจ๋ธ)
class Artist(models.Model):
    name = models.CharField(max_length=50)


class Band(models.Model):
    name = models.CharField(max_length=50)
    members = models.ManyToManyField(
        Artist, 
        through='Membership',
        through_fields=('band', 'artist',)
    )

class Membership(models.Model):
    artist = models.ForignKey(Artist, on_delete=models.CASCADE)
    band = models.ForignKey(Band, on_delete=models.CASCADE)
    inviter = models.ForignKey(Artist, on_delete=models.CASCADE)

์—ญ์ฐธ์กฐ

์™ธ๋ž˜ํ‚ค ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ ์†Œ์Šค๋ชจ๋ธ์— ์—ฐ๊ฒฐ๋œ ํƒ€๊ฒŸ๋ชจ๋ธ์˜ ์ธ์Šคํ„ด์Šค๋“ค์€ ์ž์‹ ๊ณผ ์—ฐ๊ฒฐ๋œ ์†Œ์Šค๋ชจ๋ธ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” Manager ๋ฅผ ๊ฐ€์ง€๊ฒŒ๋œ๋‹ค. Manager๋Š” ์†Œ์Šค๋ชจ๋ธ๋ช…_set ์˜ ํ˜•ํƒœ๋กœ ์ƒ์„ฑ๋œ๋‹ค. Reverse accessor๋Š” ๊ด€๊ณ„๋ฅผ ์—ญ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ์ด Manger๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

# ์†Œ์Šค๋ชจ๋ธ > ํƒ€๊ฒŸ๋ชจ๋ธ ์ฐธ์กฐํ•˜๊ธฐ
o1 = Band.objects.get(id=1)
o1.artist.all()

# ํƒ€๊ฒŸ๋ชจ๋ธ > ์†Œ์Šค๋ชจ๋ธ ์—ญ์ฐธ์กฐํ•˜๊ธฐ
o2 = Artist.objects.get(id=1)
o2.band_set.all()

# ์ค‘๊ฐœ๋ชจ๋ธ > ํƒ€๊ฒŸ, ์†Œ์Šค๋ชจ๋ธ ์ฐธ์กฐ
o3 = Membership.objects.first()
o3.artist
o3.band

# ํƒ€๊ฒŸ, ์†Œ์Šค๋ชจ๋ธ > ์ค‘๊ฐœ๋ชจ๋ธ ์—ญ์ฐธ์กฐ
o4 = Artist.objects.get(id=1)
o4.membership_set.all()

- related_name

์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ์—ญ์ฐธ์กฐ Manger์ด๋ฆ„์„ related_name ์˜ต์…˜์œผ๋กœ ๋ฐ”๊ฟ”์ค„ ์ˆ˜ ์žˆ๋‹ค.

class Membership(models.Model):
    artist = models.ForignKey(Artist, on_delete=models.CASCADE)
    band = models.ForignKey(Band, on_delete=models.CASCADE)
    inviter = models.ForignKey(
      Artist, 
      on_delete=models.CASCADE, 
      related_name='membership_inviter_set'
    )

๋ชจ๋ธ ํ™œ์„ฑํ™”

makemigrations

makemigartions ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด ๋ชจ๋ธ์„ ์ƒ์„ฑ, ๋ณ€๊ฒฝ๋œ ์‚ฌํ•ญ์„ migration ์œผ๋กœ ์ €์žฅํ•˜๋Š” ๋ช…๋ น์–ด์ด๋‹ค.

Migration

๋ชจ๋ธ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ, ๋””์Šคํฌ์ƒ์˜ ํŒŒ์ผ๋กœ ์กด์žฌํ•œ๋‹ค.

$ python manage.py makemigrations <app_name>
$ python manage.py makemigrations polls
Migrations for 'polls':
  polls/migrations/0001_initial.py
    - Create model Choice
    - Create model Question
    - Add field question to choice

์ƒ์„ฑ๋œ migration๋“ค์„ ์‹คํ–‰์‹œ์ผœ์ฃผ๊ณ , ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” migrate ๋ช…๋ น์–ด๊ฐ€ ์žˆ๋‹ค.

sqlmigrate

migration์˜ ์ด๋ฆ„์„ ์ธ์ˆ˜๋กœ ๋ฐ›์•„์„œ ์‹คํ–‰ํ•˜๋Š” SQL๋ฌธ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ migration์„ ์‹คํ–‰ํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค. ๋‹จ์ˆœํžˆ ๊ฒฐ๊ณผ๋งŒ ์ถœ๋ ฅํ•  ๋ฟ์ด๋‹ค.

$ python manage.py sqlmigrate polls 0001
BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL);
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" RENAME TO "polls_choice__old";
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id") DEFERRABLE INITIALLY DEFERRED);
INSERT INTO "polls_choice" ("id", "choice_text", "votes", "question_id") SELECT "id", "choice_text", "votes", NULL FROM "polls_choice__old";
DROP TABLE "polls_choice__old";
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;

migrate

์•„์ง ์ ์šฉ๋˜์ง€ ์•Š์€ ๋ชจ๋“  migration์„ ์ˆ˜์ง‘ํ•ด ์‹คํ–‰ํ•œ๋‹ค. django_migrations ํ…Œ์ด๋ธ”์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ ์šฉ ์—ฌ๋ถ€๋ฅผ ๊ธฐ๋กํ•œ๋‹ค.

$ python manage.py migrate            
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK

Python Shell๋กœ ๋ชจ๋ธ ๋‹ค๋ค„๋ณด๊ธฐ

shell ์‹คํ–‰ํ•˜๊ธฐ

manage.py ๋ฅผ ํ†ตํ•ด์„œ shell์„ ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด Django์—์„œ ๋™์ž‘ํ•˜๋Š” ๋ชจ๋“  ๋ช…๋ น์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

$ python manage.py shell
Python 3.7.2 (default, Mar  5 2019, 16:08:31) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

Imort Model

>>> from polls.models import Choice, Question

๊ฐ์ฒด ์กฐํšŒํ•˜๊ธฐ

>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

__str__

>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ์ฒด์— ๋Œ€ํ•œ ์„ค๋ช…์„ __str__() ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    def __str__(self):
        return self.question_text

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    def __str__(self):
        return self.choice_text
>>> Question.objects.all()
<QuerySet [<Question: What's new?>]>

๊ฐ์ฒด ์ƒ์„ฑํ•˜๊ธฐ

>>> Question.objects.create(question_text="What's new?", pub_date=timezone.now())
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
>>> q.save()
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2019, 3, 7, 6, 23, 20, 357211, tzinfo=<UTC>)

ํ•„ํ„ฐ๋งํ•˜๊ธฐ

๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋ง ํ•˜๋Š” ๊ฒƒ์€ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋‹ค. ํ•„ํ„ฐ๋ง์„ ํ†ตํ•ด์„œ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

>>> Question.objects.filter(pub_date__year=2019)
<QuerySet [<Question: What's new?>, <Question: test2>, <Question: test3>, <Question: test4>, <Question: test5>, <Question: test6>]>

ํ•„ํ„ฐ๋ง์— ๊ด€๋ จํ•ด์„œ๋Š” ๊ณต์‹ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ์ฐธ์กฐํ•˜๋ฉด๋œ๋‹ค.

๋‹ค๋ฅธ ๋ชจ๋ธ๊ณผ ์—ฐ๊ฒฐํ•˜๊ธฐ

>>> q = Question.objects.get(id=1)

>>> q.choice_set.all()
<QuerySet []>

>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> q.choice_set.create(choice_text='Just hacking again', votes=0)
<Choice: Just hacking again>

>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

>>> c = Choice.objects.all()
>>> c
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

order_by : ์ •๋ ฌํ•˜๊ธฐ

๋ชจ๋ธ์˜ ์†์„ฑ์— ๋”ฐ๋ผ ์ •๋ ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

# ๋‚ด๋ฆผ์ฐจ์ˆœ
>>> Question.objects.order_by('-pub_date')
# ์˜ค๋ฆ„์ฐจ์ˆœ
>>> Question.objects.order_by('pub_date')

์†์„ฑ๋ช…์— -๊ฐ€ ๋ถ™์œผ๋ฉด ๋‚ด๋ฆผ์ฐจ์ˆœ์ด๋‹ค.

์ฐธ์กฐ

Last updated