프로젝트 설정
# pyenv local 설정 해제하는 법
$ rm -rf .python-version
# .gitignore 파일 복사
$ cp ~/fastcampus/projects/django_girls_class/.gitignore .
# 소스파일설정하기
# 모델 작성
# 세팅에 INSTALLED_APP 앱 추가
# migrate
# createsuperuser
>>> from person.models import Person
>>> p = Person(name='hm', shirt_size='M')
>>> p.save()
>>> p.shirt_size
'M'
In [1]: from person.models import Person
In [3]: p = Person.objects.get(id=1)
In [4]: p.shirt_size
Out[4]: 'M'
In [6]: p.get_shirt_size_display()
Out[6]: 'Medium'
In [7]: p.shirt_size = 'ohter'
In [8]: p.save()
In [9]: p.get_shirt_size_display()
Out[9]: 'ohter'
In [28]: Person.objects.all()
Out[28]: <QuerySet [<Person: Person object>]>
In [26]: Person.objects.values_list()
Out[26]: <QuerySet [(1, 'hm', 'M')]>
In [29]: Person.objects.values_list('name', flat=True)
Out[29]: <QuerySet ['hm']>
In [32]: Person.objects.all()
Out[32]: <QuerySet [<Person: Person object>, <Person: Person object>]>
In [31]: Person.objects.values_list()
Out[31]: <QuerySet [(1, 'hm', 'M'), (2, 'na', 'M')]>
In [30]: Person.objects.values_list('name', flat=True)
Out[30]: <QuerySet ['hm', 'na']>
Models
모델은 데이터 정보의 소스이다. 일반적으로 각 모델은 하나의 데이터베이스 테이블과 연결한다.
기본 사항:
- 각 모델은
django.db.models.Model
의 서브클래스인 파이썬 클래스이다. - 모델의 각 속성은 데이터베이스 필드로 표현한다.
- 장고는 자동으로 생성한 데이터베이스 접근 API(ORM)를 제공한다. 쿼리를 만들어 볼 수 있다.
Quick example
모델 예는 first_name
과 last_name
을 가지는 Person
을 정의한다.
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name
과 last_name
은 모델의 필드이다. 각 필드는 클래스 속성으로 지정하고, 각 속성은 데이터베이스 column과 연결된다.
위의 Person
모델은 아래와 같은 데이터베이스를 만든다.
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
-
테이블 이름
- 장고는 자동적으로 ‘모델 클래스의 이름’과 ‘앱 이름’에서 데이터베이스 테이블 이름을 얻는다.
- 위의 예에서 테이블 이름은
myapp_person
이다.
-
id 필드는 자동으로 추가된다. (override 불가)
Using models
모델을 정의하자마자 그 모델을 사용할 것이라고 장고에게 알려야 한다. 장고에게 알리는 것은 설정 파일 INSTALLED_APPS
에 추가하면 된다.
INSTALLED_APPS = [
#...
'myapp',
#...
]
INSTALLED_APPS
에 새로운 앱을 추가한 후, manage.py migrate
를 실행해야 한다.
Fields
모델의 유일한 필수 부분이자, 가장 중요한 부분은 필드가 정의한 데이터베이스 리스트이다. 필드는 클래스 속성에 의해 지정된다. clean, save, delete 같은 models API
와 충돌하는 필드 이름을 고르지 않도록 주의하자.
Field types
모델의 각 필드는 적절한 Field
클래스의 하나의 인스턴스여야 한다. 장고는 필드 클래스 유형을 사용하여 몇 가지를 결정한다.
- column type : 데이터베이스에 저장할 데이터 종류 (예:
INTEGER
,VARCHAR
,TEXT
) - 폼 필드를 렌더링할 때, 사용할 기본 HTML
widget
(widget = 장고 내의 HTML input element, 예: choice 선택박스를 만들어줌) - 장고 관리자 안에서 요구된 최소한의 검증
Django에는 수십 가지 내장 필드 타입이 있다. model field reference에서 전체 목록을 찾을 수 있다. 또한, 커스텀 모델 필드를 쉽게 만들수 있다.
Field options
각 필드는 필드 별 인자 세트를 갖는다. 예를 들면, CharField
는 VARCHAR
의 사이즈를 지정하는 max_length
인자가 필요하다.
또한 모든 필드 타입으로 사용할 수 있는 공통 인자 세트가 있다. 모든 것은 옵션이다. 가장 자주 사용하는것만 요약해보자.
null
True
면, 데이터베이스는 NULL
이라는 빈 값을 데이터베이스에 저장할 것 이다. (Default False
)
blank
True
면, 필드는 공백을 허용한다. (Default False
)
주의 : null
과는 다르다. null
은 순전히 데이터베이스 관련인 반면, blank
는 유효성과 관련이다.
choices
필드의 choices로 사용할 2차원 튜플을 반복 가능한 (예 : list 또는 tuple) 객체에 담아 주면, 기본 폼 위젯은 문자 필드 대신 선택 상자(select box)가되어 주어진 선택 사항으로 선택 항목을 제한한다. (admin 페이지에서 확인 할 수 있다.)
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
각 튜플의 첫 번째 요소는 데이터베이스에 저장될 값이다. 두 번째 요소는 폼 위젯 또는 ModelChoiceField
에 의해 표시된다. 모델 인스턴스가 주어지면, choices 필드를 위한 표시 값은 get_FOO_display()
메서드를 사용해 choices 값에 접근할 수 있다.
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
default
기본 값을 위한 필드이다. 값 또는 호출가능한 객체면 가능하다. 만약 호출하는 경우라면, 새로운 객체가 생성될 때 마다 호출된다.
from django.db import models
class Person(models.Model):
SHIRT_SIZES = [
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
]
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES, default=SHIRT_SIZES[1][0])
nationality = models.CharField(max_length=100, default='South Korea')
In [1]: from person.models import Person
# creat하면 save까지 모두 완료
In [2]: p = Person.objects.create(name='Tom')
In [4]: p.nationality
Out[4]: 'South Korea'
In [5]: p.name
Out[5]: 'Tom'
help_text
위젯과 함께 표시될 추가 “help” 텍스트이다. 필드를 폼에 사용하지 않을지라도 문서화에 유용하다.
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=60, help_text='성과 이름을 붙여 적으세요.')
primary_key
True
면, 이 필드는 모델의 기본 키이다.
모델의 어떤 필드에도 primary_key=True
를 지정하지 않으면, 장고가 기본 키를 보유하는 IntegerField
를 자동으로 추가한다. 그래서 디폴트 기본키 동작을 재정의하려는 경우가 아니면, 어떤 필드에도 primary_key=True
를 설정할 필요가 없다.
기본 키 필드는 읽기 전용입니다. 기존 객체의 기본 키 값을 변경하고 저장하면, 이전 객체와 함께 새로운 객체가 생성된다.
In [1]: from person.models import Person
In [2]: person = Person.objects.all()
In [3]: person
Out[3]: <QuerySet [<Person: Fred>, <Person: Tom>]>
In [4]: p1 = Person.objects.get(id=1)
# 기본 키 변경
In [7]: p1.id = 5
In [8]: p1.save()
In [9]: p1.id
Out[9]: 5
# 이전 객체
In [10]: Person.objects.get(id=1)
Out[10]: <Person: Fred>
# 생성된 객체
In [11]: Person.objects.get(id=5)
Out[11]: <Person: Fred>
unique
True
면, 이 필드는 테이블 전체에서 유일해야 한다.
Automatic primary key fields
기본적으로 장고는 각 모델에 다음의 필드를 제공한다.
id = models.AutoField(primary_key=True)
자동 증가 기본 키이다. 만약 커스텀 기본 키를 지정하고 싶다면, 필드 중 하나에 primary_key=True
를 지정한다. 장고가 Field.primary_key
를 명시적으로 설정한 것을 알면, 자동 증가 기본키를 추가하지 않는다.
각 모델은 primary_key=True
(명시적으로 선언되거나 자동으로 추가됨)를 갖기 위해 정확히 하나의 필드가 필요하다.
Verbose field names
ForeignKey
, ManyToManyField
, OneToOneField
를 제외한 각 필드 타입은 선택적인(optional) 첫번째 위치 인자로 verbose name을 취한다. 만약 verbose 이름이 주어지지 않으면, 장고는 필드의 속성 이름을 사용해 자동으로 생성한다. 이때 underscore(_)는 spaces(공백)으로 변환한다.
# verbose name: person's first name
# 변수명 대신 verbose name으로 관리자페이지 필드명이 출력 됨
first_name = models.CharField("person's first name", max_length=30)
# ForeignKey 일 때는 키워드 인자로 넘겨줌
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
관례상 verbose_name
의 첫 글자를 대문자로 사용하지 않습니다. 장고는 필요할 때 자동으로 첫 글자를 대문자로 변환한다.
Relationships
분명히 관계형 데이터베이스의 힘은 테이블을 서로 연관시키는 데 있다. 장고는 데이터베이스 관계의 가장 일반적인 세 가지 유형을 정의하여 제공한다. many-to-one, many-to-many and one-to-one.
Many-to-one relationships
다대일 관계를 정의하려면 django.db.models.ForeignKey
를 사용한다 . 다른 Field
타입과 마찬가지로 모델의 클래스 속성으로 포함하여 사용한다. ForeignKey
모델과 관련된 하나의 클래스 위치 인자가 필요하다.
예를 들어 Car
모델이 Manufacturer
를 가진다면 다음 정의를 사용한다. 즉, Manufacturer
가 여러 자동차를 생산하지만 각 Car
는 오직 하나의 Manufacturer
를 가진다.
from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
title = models.CharField(max_length=100)
def __str__(self):
return '{} {}'.format(self.manufacturer, self.title)
재귀 관계
(그 자체와 다 대일 관계가 있는 객체)와 아직 정의되지 않은 모델과 관계
를 만들 수 있다. 재귀 관계를 만들려면 models.ForeignKey('self', on_delete = models.CASCADE)
를 사용한다. 아직 정의되지 않은 모델과 관계를 작성해야하는 경우 모델 오브젝트 자체가 아닌 모델 이름을 사용할 수 있다.
(아직 정의되지 않은 모델과 관계 예)
from django.db import models
class Car(models.Model):
'''
Car와 Manufacturer의 위치를 변경하면,
아직 정의되지 않은 모델을 참조하게 되어 아래의 에러가 발생한다.
manufacturer = models.ForeignKey(Manufacturer)
NameError: name 'Manufacturer' is not defined
'''
manufacturer = models.ForeignKey('Manufacturer')
title = models.CharField(max_length=100)
def __str__(self):
return '{} {}'.format(self.manufacturer, self.title)
class Manufacturer(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
(다대일 재귀 관계 예: 강사와 학생관계)
from django.db import models
class Person(models.Model):
SHIRT_SIZES = [
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
]
"""
일대다 재귀 관계 실습
"""
instructor = models.ForeignKey(
'self', # 외래키를 자기 자신의 클래스에 대해 갖을 때
verbose_name='담당 강사',
null=True,
blank=True,
on_delete=models.SET_NULL, # 외래키로 참조된 객제가 삭제될 때, 장고가 실행할 행동 지정
related_name='student_set' # 역참조 할 때, 사용할 이름 지정
)
"""
related_name 사용 이유 확인
owner와 mentor 작성 후 makemigrations를 진행하면 아래와 같은 오류가 발생한다.
person.Person.mentor: Reverse accessor for 'Person.mentor' clashes with reverse accessor for 'Person.owner'.
HINT: Add or change a related_name argument to the definition for 'Person.mentor' or 'Person.owner'.
그렇기 때문에 related_name을 작성해주는 것이 좋다.
"""
owner = models.ForeignKey(
'self',
verbose_name='사장님',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='employee_set',
)
mentor = models.ForeignKey(
'self',
verbose_name='멘토',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='mentee_set'
)
name = models.CharField('이름', max_length=60, help_text='성과 이름을 붙여 적으세요.')
shirt_size = models.CharField('셔츠 사이즈', max_length=1, choices=SHIRT_SIZES, default=SHIRT_SIZES[1][0])
nationality = models.CharField('국적', max_length=100, default='South Korea')
def __str__(self):
return self.name
In [1]: from person.models import Person
In [3]: Person.objects.create(name='lhy')
Out[3]: <Person: lhy>
In [5]: instructor = Person.objects.get(name='lhy')
In [7]: p1 = Person.objects.get(id=1)
In [8]: p2 = Person.objects.get(id=2)
In [11]: p1.instructor = instructor
In [12]: p1.save()
In [13]: p2.instructor = instructor
In [14]: p2.save()
In [15]: p1.instructor
Out[15]: <Person: lhy>
In [16]: p2.instructor
Out[16]: <Person: lhy>
# 자동으로 생성되는 역참조 이름이 person_set이라 명확하게 하기 위해,
# related_name을 지정한다.
In [20]: instructor.person_set.all()
Out[20]: <QuerySet [<Person: Fred>, <Person: Tom>]>
In [1]: from person.models import Person
In [2]: instructor = Person.objects.get(name='lhy')
In [3]: instructor.student_set.all()
Out[3]: <QuerySet [<Person: Fred>, <Person: Tom>]>
Many-to-many relationships
다대다 관계를 정의하려면 ManyToManyField
를 사용한다. 다른 Field
타입과 마찬가지로 모델의 클래스 속성으로 포함하여 사용한다. ManyToManyField
모델과 관련된 클래스 위치 인자가 필요하다.
예를 들어, Pizza
에 여러 개의 Topping
객체가있는 경우 다음과 같이 표현할 수 있다. (Topping
이 여러 피자에있을 수 있으며, 각 Pizza
에 여러 토핑이있을 수 있다.)
from django.db import models
class Topping(models.Model):
title = models.CharField(max_length=30)
def __str__(self):
return self.title
class Pizza(models.Model):
title = models.CharField(max_length=100)
# Pizza와 Topping은 서로 ManyToMany 관계
# 포함관계에서 상위요소에 해당하는 클래스에 ManyToMany를 선언
toppings = models.ManyToManyField(Topping)
def __str__(self):
return '{} {}'.format(
self.title,
', '.join(self.toppings.value_list('title', flat=True))
)
In [1]: from pizza.models import Pizza, Topping
In [2]: bp = Pizza.objects.create(title='불고기 피자')
In [3]: cp = Pizza.objects.create(title='치즈 피자')
In [4]: sp = Pizza.objects.create(title='슈퍼슈프림 피자')
In [5]: t1 = Topping.objects.create(title='치즈')
In [6]: t2 = Topping.objects.create(title='햄')
In [7]: t3 = Topping.objects.create(title='불고기')
In [8]: t4 = Topping.objects.create(title='피망')
In [9]: sp.toppings.add(t1, t2, t4)
In [10]: sp
Out[10]: <Pizza: 슈퍼슈프림 피자, Toppings : 치즈, 햄, 피망>
In [13]: cp.toppings = t3, t4
In [14]: cp
Out[14]: <Pizza: 치즈 피자 불고기, 피망>
In [18]: Pizza.objects.all()
Out[18]: <QuerySet [<Pizza: 불고기 피자 >, <Pizza: 치즈 피자 불고기, 피망>, <Pizza: 슈퍼슈프림 피자 치즈, 햄, 피망>]>
In [19]: Pizza.objects.values_list()
Out[19]: <QuerySet [(1, '불고기 피자'), (2, '치즈 피자'), (3, '슈퍼슈프림 피자')]>
In [21]: Pizza.objects.values_list('title')
Out[21]: <QuerySet [('불고기 피자',), ('치즈 피자',), ('슈퍼슈프림 피자',)]>
In [22]: Pizza.objects.values_list('title', flat=True)
Out[22]: <QuerySet ['불고기 피자', '치즈 피자', '슈퍼슈프림 피자']>
In [23]: ', '.join(Pizza.objects.values_list('title', flat=True))
Out[23]: '불고기 피자, 치즈 피자, 슈퍼슈프림 피자'
In [25]: Pizza.objects.get(id=1)
Out[25]: <Pizza: 불고기 피자 >
In [26]: Pizza.objects.get(id=1).toppings
Out[26]: <django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager at 0x10ba430b8>
migrations 파일을 보면 아래와 같은 구조로 되어 있다.
- pizza_pizza : 따로 연결된 키 없음
- id
- title
- pizza_topping : 따로 연결된 키 없음
- id
- title
- pizza_pizza_toppings : 서로 연결하는 foreign key 존재 (REFERENCES)
- id
- pizza_id
- topping_id
BEGIN;
--
-- Create model Pizza
--
CREATE TABLE "pizza_pizza" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(100) NOT NULL
);
--
-- Create model Topping
--
CREATE TABLE "pizza_topping" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(30) NOT NULL
);
--
-- Add field toppings to pizza
--
CREATE TABLE "pizza_pizza_toppings" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"pizza_id" integer NOT NULL REFERENCES "pizza_pizza" ("id"),
"topping_id" integer NOT NULL REFERENCES "pizza_topping" ("id"));
CREATE UNIQUE INDEX "pizza_pizza_toppings_pizza_id_5ff9fcdb_uniq" ON "pizza_pizza_toppings" ("pizza_id", "topping_id");
CREATE INDEX "pizza_pizza_toppings_c3c2621e" ON "pizza_pizza_toppings" ("pizza_id");
CREATE INDEX "pizza_pizza_toppings_3a22b0a0" ON "pizza_pizza_toppings" ("topping_id");
COMMIT;
ForeignKey
와 마찬가지로 재귀 관계
(그 자체와 다 대다 관계가 있는 객체)와 아직 정의되지 않은 모델과의 관계
를 만들 수 있다. 재귀 관계를 만들려면 models.ForeignKey('self', on_delete = models.CASCADE)
를 사용한다. 아직 정의되지 않은 모델과 관계를 작성해야하는 경우 모델 오브젝트 자체가 아닌 모델 이름을 사용할 수 있다.
(다대다 재귀함수 예: 친구관계, 트위터 팔로잉/팔로우 관계)
class User(models.Model):
name = models.CharField(max_length=50)
friends = models.ManyToManyField('self')
def __str__(self):
return self.name
In [1]: from person.models import User
In [3]: u1 = User.objects.create(name='lhy')
In [4]: u2 = User.objects.create(name='na')
In [5]: u3 = User.objects.create(name='hml')
# 연결되어 있는거를 확인할 수 있음
In [6]: u1.friends
Out[6]: <django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager at 0x10e51ebe0>
In [7]: u1.friends.add(u2)
In [8]: u1.friends.all()
Out[8]: <QuerySet [<User: na>]>
In [9]: u2.friends.all()
Out[9]: <QuerySet [<User: lhy>]>
# 자기 자신 참조라 user_set이 없음
In [10]: u1.user_set.all()
--------------------------------------------------------
AttributeError: 'User' object has no attribute 'user_set'
장고는 위의 모델(ManyToManyFields
로 자기자신을 가질 경우)을 처리할 때, User 클래스에 user_set 속성을 추가하지 않도록 정의한다. 대신에 대칭적(symmetrical)이라고 가정한다. 즉, 내가 너의 친구이면 너도 나의 친구라고 가정한다.
만약 재귀 다대다 관계(ManyToManyField('self'))
에서 대칭구조를 원하지 않으면, symmetrical=False
로 설정한다.
class User(models.Model):
# 팔로우, 팔로잉을 나타내는 경우
# ManyToManyField에 symmetrical 옵션으로 설정 가능
following = models.ManyToManyField(
'self',
related_name='follower_set',
symmetrical=False,
)
friends = models.ManyToManyField('self')
name = models.CharField(max_length=50)
def __str__(self):
return self.name
In [1]: from person.models import User
In [2]: u1 = User.objects.get(id=1)
In [3]: u2 = User.objects.get(id=2)
In [6]: u1.following.add(u2)
In [7]: u1.following.all()
Out[7]: <QuerySet [<User: na>]>
In [10]: u2.following.all()
Out[10]: <QuerySet []>
In [6]: u1.follower_set.all()
Out[6]: <QuerySet []>
In [7]: u2.follower_set.all()
Out[7]: <QuerySet [<User: lhy>]>
필수는 아니지만, ManyToManyField
의 이름(위의 예에서의 toppings)은 관련된 모델 객체 세트를 설명하는 복수형으로 사용할 것을 권장한다.
어떤 모델이 ManyToManyField
를 가지고 있는지 중요하지 않지만, 두 모델 중 하나에만 넣어야 한다.
일반적으로 ManyToManyField
인스턴스는 폼에서 편집할 객체 안에 있어야 한다. 위의 예에서 (Topping이 pizzas의 ManyToManyField를 갖는것보다) toppings는 Pizza 안에 있다. pizza가 topings를 가지는게 topping이 여러개의 피자를 가지는거보다 더 자연스러운 생각이기 때문이다. 위에 설정된 방식대로 Pizza
양식을 사용하면 사용자가 토핑을 선택할 수 있다.
Extra fields on many-to-many relationships
피자와 토핑을 믹싱하고 매칭하는 것과 같은 단순한 다대다 관계 만 처리 할 때는 ManyToManyField
만 있으면된다. 그러나 때로는 두 모델 간의 관계에 데이터를 연결해야 할 수도 있다.
예를 들어, 뮤지션들이 속한 그룹을 추적하는 어플리케이션의 경우를 생각해보자. person과 멤버가 속한 group 사이에 다대다 관계가 있다. 이 관계를 표현하기 위해 ManyToManyField
를 사용할 수 있다. 그러나 그룹에 가입한 날짜와 같이 수집하려는 멈버십 정보의 세부 정보가 많이 있다.
이러한 상황에서 장고는 다대다 관계를 관리하는 데 사용될 모델을 지정할 수 있다. 중간자(intermediate) 모델에 별도의 필드를 추가 할 수 있다. 중간자 모델은 ManyToManyField
에 중개자로 행동할 모델을 가르키는 through
인자를 사용해 연관된다.
아이돌
레이나
나나
리지
유이
가희
아이돌 그룹
에프터스쿨
오렌지캬라멜
멤버쉽
가입 날짜
# 다대다 모델 + 추가적 필드 : ManyToManyField + 중간자 모델
class Idol(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Group(models.Model):
# Group과 Idol은 서로 ManyToMany 관계
# 포함관계에서 상위요소에 해당하는 클래스에 ManyToMany를 선언
name = models.CharField(max_length=100)
members = models.ManyToManyField(
'Idol',
# 별도의 필드를 추가해 두 관계를 정의하는 중간자 모델
# MemberShip이 정의되기 전이므로 String으로 작성
through='MemberShip',
)
def __str__(self):
return self.name
class Membership(models.Model):
group = models.ForeignKey(Group)
Idol = models.ForeignKey(Idol)
date_join = models.DateTimeField()
중간자 모델을 설정할 때, 다대다 관계에 관여된 모델에 외래 키(foreign key)를 명시적으로 지정한다. 이 명시적 선언은 두 모델이 어떻게 관련이 있는지 정의한다.
중간 모델에는 몇 가지 제한 사항이 있다.
- 중간자 모델은 소스(source) 모델(예:
Group
)에 대한 단 하나의 외래 키를 가져야만 한다. 또는 장고가ManyToManyField.through_fields
로 관계를 사용할 수 있게 외래키를 명시적으로 지정해야만 한다. 만약 하나 이상의 외래 키를 가지고through_fields
도 선언되지 않았다면, 유효성 검증 에러가 발생한다. 유사한 제한이 대상(target) 모델(예:Person
)에 대한 외래 키에도 적용된다. - 중간자 모델을 통해 자신을 다대다 관계로 가지는 모델의 경우 같은 모델에서 두 개의 외래 키가 허용되지만, 다대다 관계의 다른 두개의 측면으로 처리됩니다. 만약 두개 이상의 외래 키를 가진다면, 위의 항목처럼
through_fields
를 선언해야만 한다. 그렇지 않으면 유효성 검증 에러가 발생한다. - 중간자 모델을 사용하여 자신의 모델로부터 다대다 관계를 정의할 때, 반드시
symmetrical=False
를 사용해야 한다.
이제 중개자 모델(예: Membership
)을 사용하기위해 ManyToManyField
를 설정 했으므로 이제 다대다 관계를 만들 준비가 된 것이다. 중간자 모델의 인스턴스를 생성해서 수행한다.
(1번 제약 사향 예)
# 다대다 모델 + 추가적 필드 : ManyToManyField + 중간자 모델
class Idol(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Group(models.Model):
# Group과 Idol은 서로 ManyToMany 관계
# 포함관계에서 상위요소에 해당하는 클래스에 ManyToMany를 선언
name = models.CharField(max_length=100)
members = models.ManyToManyField(
'Idol',
# 별도의 필드를 추가해 두 관계를 정의하는 중간자 모델
# MemberShip이 정의되기 전이므로 String으로 작성
through='MemberShip',
# 제약사항 1번
# 관계를 형성하는 두 클래스 중 한 클래스라도 한 번을 초과하여 필드로 사용할 경우
# 기본 값 설정
through_fields=('group', 'idol'),
)
def __str__(self):
return self.name
class Membership(models.Model):
group = models.ForeignKey(Group)
Idol = models.ForeignKey(Idol)
date_join = models.DateTimeField()
# 제약사항 1번
# 추천인이라는 Target 모델에 대한 추가 필드가 있을 경우,
# through_fields를 정의해야 한다.
recommneder = models.ForeignKey(
Idol,
null=True,
blank=True,
related_name='recommender_membership_set'
)
(2번 제약 사항 예)
사람
친구관계
(기본적으로 생성되는 정보)
사람1
사람2
...
친구관계에 대한 정보
사람1(from_user)
사람2(to_user)
...
누가 소개 시켜줬느냐 (사람3, recommender)
소개해준날
# 다대다 모델
class User(models.Model):
'''
다대다 재귀 관계 실습
'''
# 팔로우, 팔로잉을 나타내는 경우
# ManyToManyField에 symmetrical 옵션으로 설정 가능
following = models.ManyToManyField(
'self',
related_name='follower_set',
symmetrical=False,
)
# symmetrical 기본 값은 True > 한 객체에서 '친구' 설정 시, 반대쪽도 자동으로 '친구' 설정
# 중간자 모델을 사용하려면, symmetrical이 반드시 False여야 한다.
# symmetrical=True 설정을 유지해 동시에 친구 관계를 맺으려면,
# 두 관계를 한 번에 생성해주는 메서드를 만들어 사용해야 한다.
friends = models.ManyToManyField('self')
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class UserFriendsInfo(models.Model):
from_user = models.ForeignKey(
User,
related_name='+',
)
to_user = models.ForeignKey(
User,
related_name='+',
)
# 중개인이라는 User ForeignKey가 추가로 존재할 경우,
# 중간자 모델을 사용하는 곳에서 through_fields를 정의해야 함
recommender = models.ForeignKey(
User,
null=True,
blank=True,
related_name='recommender_userfriendsinfo_set'
)
when = models.DateTimeField(auto_now_add=True)
In [2]: from person.models import Idol, Group, MemberShip
# 객체 생성
In [3]: yui = Idol.objects.create(name='유이')
In [4]: reina = Idol.objects.create(name='레이나')
In [5]: nana = Idol.objects.create(name= '나나')
In [6]: lizzy = Idol.objects.create(name='리지')
In [7]: eyoung = Idol.objects.create(name='이영')
In [8]: kaeun = Idol.objects.create(name='가은')
In [10]: Idol.objects.values_list('name', flat=True)
Out[10]: <QuerySet ['레이나', '나나', '유이', '레이나', '나나', '리지', '이영', '가은']>
In [11]: afterschool = Group.objects.create(name='애프터스쿨')
In [12]: orangecaramel = Group.objects.create(name='오렌지 캬라멜')
In [16]: from datetime import date, datetime, time, timedelta
In [17]: m1 = MemberShip(
...: idol=reina,
...: group=orangecaramel,
...: date_joined=date(2010, 6, 17))
In [18]: m1.save()
In [21]: MemberShip.objects.create(
...: idol=nana,
...: group=orangecaramel,
...: date_joined=date(2010, 6, 17))
Out[21]: <MemberShip: MemberShip object>
In [22]: MemberShip.objects.create(
...: idol=lizzy,
...: group=orangecaramel,
...: date_joined=date(2010, 6, 17))
Out[22]: <MemberShip: MemberShip object>
# 확인
In [23]: MemberShip.objects.all()
Out[23]: <QuerySet [<MemberShip: MemberShip object>, <MemberShip: MemberShip object>, <MemberShip: MemberShip object>]>
In [24]: MemberShip.objects.count()
Out[24]: 3
In [26]: MemberShip.objects.values_list('idol__name', flat=True)
Out[26]: <QuerySet ['레이나', '나나', '리지']>
In [27]: orangecaramel.members.all()
Out[27]: <QuerySet [<Idol: 레이나>, <Idol: 나나>, <Idol: 리지>]>
# 삭제할 때는 이런식으로 조회해 삭제
In [31]: MemberShip.objects.values_list('id', 'idol__name')
Out[31]: <QuerySet [(1, '레이나'), (2, '나나'), (3, '리지')]>
In [32]: nana.group_set.all()
Out[32]: <QuerySet [<Group: 오렌지 캬라멜>]>
In [33]: nana.membership_set.all()
Out[33]: <QuerySet [<MemberShip: MemberShip object>]>
In [34]: nana.membership_set.values_list('group__name', 'date_joined')
Out[34]: <QuerySet [('오렌지 캬라멜', datetime.datetime(2010, 6, 17, 0, 0, tzinfo=<UTC>))]>
# 전체 멤버 멤버쉽 추가
In [36]: for idol in Idol.objects.all():
...: MemberShip.objects.create(
...: idol=idol,
...: group=afterschool,
...: date_joined=date(2009, 1, 15)
...: )
...:
# 입력되어있던 멤버가 있다.
# 지우고 다시해야되는데 그냥 진행했다.
In [37]: afterschool.members.values_list('name')
Out[37]: <QuerySet [('레이나',), ('나나',), ('유이',), ('레이나',), ('나나',), ('리지',), ('이영',), ('가은',)]>
In [38]: orangecaramel.members.all()
Out[38]: <QuerySet [<Idol: 레이나>, <Idol: 나나>, <Idol: 리지>]>
In [39]: lizzy.group_set.all()
Out[39]: <QuerySet [<Group: 오렌지 캬라멜>, <Group: 애프터스쿨>]>
In [41]: lizzy.membership_set.values_list('group__name', 'date_joined')
Out[41]: <QuerySet [('오렌지 캬라멜', datetime.datetime(2010, 6, 17, 0, 0, tzinfo=<UTC>)), ('애프터스쿨', datetime.datetime(2009, 1, 15, 0, 0, tzinfo=<UTC>))]>
In [1]: from person.models import Group, Idol, MemberShip
In [2]: MemberShip.objects.all()
Out[2]: <QuerySet [<MemberShip: 오렌지 캬라멜 (레이나)>, <MemberShip: 오렌지 캬라멜 (나나)>, <MemberShip: 오렌지 캬라멜 (리지)>, <MemberShip: 애프터스쿨 (레이나)>, <MemberShip: 애프터스쿨 (나나)>, <MemberShip: 애프터스쿨 (유이)>, <MemberShip: 애프터스쿨 (레이나)>, <MemberShip: 애프터스쿨 (나나)>, <MemberShip: 애프터스쿨 (리지)>, <MemberShip: 애프터스쿨 (이영)>, <MemberShip: 애프터스쿨 (가은)>]>
# 중간자모델은 추가한 필드 때문에 crete를 사용할 수 없다.
# 추가하고 싶으면 무조건 Membership 객체로만 정의 가능하다.
# clear 삭제라기보다는 연결관계가 사라진다.
# 중간자 모델에 있는 것만 사라진다.
In [22]: lizzy = Idol.objects.get(name='리지')
In [24]: lizzy.membership_set.get(group__name='애프터스쿨')
Out[24]: <MemberShip: 애프터스쿨 (리지)>
QnA
- client > url 요청 > server > django app > urls.py(URLconf) > views.py(Controller) > render(template (View), Model ) > HttpResponse > client
- localhost = 127.0.0.1 = loopback
- port 8000 : 앱이 실행되고 있는 8000포트로 접속한다는 의미