당신은 멋쟁이, 우리는 장고쟁이~

0%

Django Models 8편 - Many-to-many 예시

Django Models - ManyToMany 예시


예시 개요


대학교에서 학생들이 듣는 과정들과 학생들에 대한 모델링을 합니다.


과정은, Course 로 표현하고, 과목들은 아래와 같이 몇개만 정리 합니다.


CourseID CourseName
1 Algorithm Design
2 Systems Programming
3 Object-Oriented Programming

아래 학생들이 재학하고 있다고 가정합니다.


StudentID Name
1 djangojeng-e
2 Jennifer Lawrence
3 Will Smith

학생들은, 여러개의 과목들을 수강할수 있고.

한 과목은 여러명의 수강생들이 들을수 있습니다.


따라서, Students - to - Courses 는 Many-to-Many, 다대다 관계가 성립됩니다


일반적인 Many-to-Many 관계로는, 학생의 수강날짜, 수강종료날짜, 그리고 성적 같은것들을 모두 기록할수 없기 때문에. 중간 모델을 하나 만들어서 관리할수 있습니다.


모델링


Student 와 Course 모델들의 중간 모델인, Enrolment 를 생성하고.


Enrolment 에 수강날짜, 수강종료날짜, 그리고 성적을 관리 할겁니다.


Student Course Enrolment
name (CHARFIELD) name (CHARFIELD) student (ForeignKey, Student)
enrolments - Enrolment 라는 중간 모델을 통해서 Student 와 M2M을 성립 enrolment (ForeignKey, Course)
- 중간모델을 성립하기 위해 through 를 사용해야 함 enrolled_date (수강시작 날짜)
finished_date (수강종료 날짜)
academic_record (성적)

이렇게 정리된 사항을, Django 모델로 작성을 해보면, 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from django.db import models

# Create your models here.


class Student(models.Model);
name = models.CharField(max_length=40)

def __str__(self):
return self.name


class Course(models.Model):
name = models.CharField(max_length=50)
enrolments = models.ManyToManyField(Student, through='Enrolment')

def __str__(self):
return self.name


class Enrolment(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
enrolled_date = models.DateTimeField()
finished_date = models.DateTimeField(blank=True)

# 학점을 관리 하기 위해서, 등급을 ABCDEF 로 나눕니다
# 학점 선택을 위해서, models.TextChoice 로 선택할수 있는 학점을 밑에 넣어줍니다.

AcademicType = models.TextChoices('AcademicType', 'A B C D E F')

academic_record = models.CharField(blank=True, choices=AcademicType.choices, max_length=10)

def __str__(self):
return (self.student + " "
+ self.course + " "
+ self.enrolled_date + " "
+ self.finished_date + " "
+ self.academic_record)

객체 생성 연습


아래 테이블에 정리된 데이터 처럼, 객체 생성 연습을 해봅니다.


CourseID CourseName StudentID StudentName
1 Algorithm Design 1 djangojeng-e
2 Systems Programming 2 Jennifer Lawrence
3 Object-Oriented Programming 3 Will Smith

shell_plus 를 사용해서, 객체 생성을 해보았습니다.

Student 와 Course 데이터 생성


Student 와 Course 객체 생성


1
2
3
4
5
6
7
8
9
10
11
In [1]: djangojeng_e = Student.objects.create(name="장고쟁이")                                                                                                            

In [2]: jennifer = Student.objects.create(name="Jennifer Lawrence")

In [3]: will = Student.objects.create(name="Will Smith")

In [4]: algorith_design = Course.objects.create(name="Algorith Design")

In [5]: systems_programming = Course.objects.create(name="Systems Programming")

In [6]: object_oriented_programming = Course.objects.create(name="Object Oriented Programming")

db 에 아래와 같이 저장 됩니다




Student 객체 둘러보기


shell 에서, 아래와 같이 Student 객체를 생성후, dir(인스턴스)를 쳐보면, 해당 인스턴스의 메서드들이 모두 나열됩니다. 이 메서드중에, 가장 중요한것만 나열해 봅니다. (__메서드 들은 모두 빼주고 나열함)


1
2
3
4
5
6
7
8
9
10
11
12
13
djangojeng_e = Student.objects.create(name='장고쟁이')     

dir(djangojeng_e)
['DoesNotExist',
'MultipleObjectsReturned',
'course_set',
'delete',
'enrolment_set',
'id',
'name',
'pk',
'save',
]

1
2
3
4
5
6
7
8
9
10
11
12
13
# djangojeng_e 인스턴스로 호출가능한것들
djangojeng_e.id
djangojeng_e.name

# 해당 인스턴스와 연관된 enrolment 객체에 접근 가능
djangojeng_e.enrolment_set
# 예를들면,
djangojeng_e.enrolment_set.all()

# 해당 인스턴스와 연관된 course 객체에 접근 가능
djangojeng_e.course_set
# 예를들면,
djangojeng_e.course_set.all()

Course 객체 둘러보기


1
2
3
4
5
6
7
8
9
10
11
12
13
oop = Course.objects.create(name='객체지향 프로그래밍')
dir(oop)

['DoesNotExist',
'MultipleObjectsReturned',
'delete',
'enrolment_set',
'enrolments',
'id',
'name',
'pk',
'save',
]

1
2
3
4
5
6
7
8
9
oop.id 
oop.name
oop.pk

# oop 인스턴스와 연결된 enrolment 객체 조회 가능
oop.enrolment_set.all()

# enrolments
oop.enrolments



Enrolment 객체 생성


아래 테이블에 정리된대로, Enrolment 객체를 생성해 줍시다.


EnrolmentID Student Course enrolled_date finished_date academic_record
1 djangojeng-e Object-Oriented Programming 2020.04.20
2 Jennifer Lawrence Object-Oriented Programming 2020.04.20
3 Will Smith Systems Programming 2019.04.21 2020.04.21 A



1
2
3
4
5
6
7
8
9
10
11
12
13
14
# import datetime 을 먼저 하고 명령어를 실행해줘야 합니다. 


In [9]: e1 = Enrolment(student=djangojeng_e,
course=oop,
enrolled_date=datetime.datetime(2020, 4, 20))
In [10]: e1.save()

In [11]: oop.enrolments.all()
Out[11]: <QuerySet [<Student: 장고쟁이>]>

In [12]: djangojeng_e.course_set.all()
# 장고쟁이 학생에 관련된 course 를 조회하려면, course_set.all() 하면 됩니다. # 참고로, djangojeng_e.enrolment_set.all() 하면, enrol 된 객체가 조회 됩니다.
Out[12]: <QuerySet [<Course: 객체지향 프로그래밍>]>

1
2
3
4
5
6
7
8
9
10
11

In [15]: e2 = Enrolment.objects.create(student=jennifer,
course=oop,
enrolled_date=datetime.datetime(2020, 4, 20))


In [17]: e3 = Enrolment.objects.create(student=will,
...: course=systems_programming,
...: enrolled_date=datetime.datetime(2019, 4, 19),
...: finished_date=datetime.datetime(2020, 4, 20),
...: academic_record="A")

데이터베이스 데이터를 저장할때, datetime 관련 에러가 날수가 있는데. 이 점에 대해서는 추후에 해결해봐야 할것 같습니다.




add(), create() 혹은 set() 을 사용해서,

관계들을 생성 할수 있습니다. through_defaults 를 통해, 요구되는 필드값을 넣어줄수 있습니다.


1
2
3
4
5
6
7
8
9
10
In [18]: john = Student.objects.create(name="John Snow")   

In [19]: systems_programming.enrolments.add(john, through_defaults={'enrolled_date': datetime.datetime(2020, 4, 19)})

In [20]: object_oriented_programming.enrolments.create(name="Arya Stark", through_defaults={'enrolled_date': datetime.datetime(2020, 4, 19)})

In [21]: algorithm_design.enrolments.set([djangojeng_e, jennifer, will], through_defaults={'enrolled_date': datetime.datetime(2020, 4, 19)})

In [23]: algorithm_design.enrolments.all()
Out[23]: <QuerySet [<Student: 장고쟁이>, <Student: Jennifer Lawrence>, <Student: Will Smith>]>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
john = Student.objects.create(name="John Snow")
systems_programming.enrolments.add(jogn, through_defaults=
{'enrolled_date': datetime.datetime(2020, 4, 19) })

object_oriented_programming.enrolments.create(name="Arya Stark", through_defaults={'enrolled_date': datetime.datetime(2020, 4, 19)})

algorithm_design.enrolments.set(
[djangojeng_e, jennifer, will],
through_defaults={'enrolled_date': datetime.datetime(2020, 4, 19)}
)

algorith_design.enrolments.all()


Out[23]: <QuerySet [<Student: 장고쟁이>, <Student: Jennifer Lawrence>, <Student: Will Smith>]>



remove() 사용하기


1
2
3
4
5
6
7
8
# oop 에서 장고쟁이 빼기 

In [25]: object_oriented_programming.enrolments.all()
Out[25]: <QuerySet [<Student: 장고쟁이>, <Student: Jennifer Lawrence>, <Student: Arya Stark>]>

In [26]: object_oriented_programming.enrolments.remove(djangojeng_e)
In [27]: object_oriented_programming.enrolments.all()
Out[27]: <QuerySet [<Student: Jennifer Lawrence>, <Student: Arya Stark>]>



many-to-many 로 관계된 모델들의 속성을 사용하여

쿼리 생성하기


1
2
3
4
# 과목중에서, 수강중인 학생 이름이 will 로 시작하는 과목 찾기 

In [28]: Course.objects.filter(enrolments__name__startswith='Will')
Out[28]: <QuerySet [<Course: Systems Programming>, <Course: Algorith Design>]>

중간 모델을 사용하여, 쿼리 작성하기

Student 가 중간 모델을 이용해서, 찾고자 하는 데이터를 조회


1
2
3
4
5
6
7
8
9
# Student 중에서, 아래 조건들을 충족하는 데이터 검색 
# 수강 과목 이름이 "Systems Programming' 인것
# 수강 시작 날짜가 2018년 4월 19일 이후인것들을 조회

In [30]: Student.objects.filter(
...: course__name='Systems Programming',
...: enrolment__enrolled_date__gte=datetime.datetime(2018, 4, 19))

Out[30]: <QuerySet [<Student: Will Smith>, <Student: John Snow>]>



Enrolment 의 정보를 바로 접근

Enrolment 모델에 직접 쿼리를 사용해서 데이터를 조회할수 있습니다


1
2
3
4
5
6
7
8
9
In [32]: will_enrolment = Enrolment.objects.get(course=systems_programming, student=will)                                                                                 
In [33]: will_enrolment.enrolled_date
Out[33]: datetime.datetime(2019, 4, 19, 0, 0, tzinfo=<UTC>)

In [34]: will_enrolment.finished_date
Out[34]: datetime.datetime(2020, 4, 20, 0, 0, tzinfo=<UTC>)

In [35]: will_enrolment.academic_record
Out[35]: 'A'

Student 객체에서부터, enrolment 에 접근하는 many-to-many reverse


1
2
3
4
5
6
7
8
9
10
n [36]: will_enrolment = will.enrolment_set.get(course=systems_programming)                                                                                              

In [37]: will_enrolment.enrolled_date
Out[37]: datetime.datetime(2019, 4, 19, 0, 0, tzinfo=<UTC>)

In [38]: will_enrolment.finished_date
Out[38]: datetime.datetime(2020, 4, 20, 0, 0, tzinfo=<UTC>)

In [39]: will_enrolment.academic_record
Out[39]: 'A'

reverse, back_ward relationship, forward relationship 에 대한 정리도 필요할것으로 보입니다!