Many to Many Relationship

M:N

  • M has many N

  • N has many M

Naming convention

  • 1:N

    • ์ •์˜: ๋ชจ๋ธ ๋‹จ์ˆ˜ํ˜• (.user)

    • ์—ญ์ฐธ์กฐ: ๋ชจ๋ธ_set (.article_set)

  • M:N

    • ์ •์˜ ๋ฐ ์—ญ์ฐธ์กฐ: ๋ชจ๋ธ ๋ณต์ˆ˜ํ˜•(.like_users, like_articles)

M:N ๊ด€๊ณ„ ์ ‘๊ทผ

image-20200428192056085

1. ๋‹จ์ˆœ ์ง๊ด€์  ๋ชจ๋ธ๋ง

์œ„์˜ ๋„์‹ํ™”ํ•œ ์• ์šฉ models.py๋กœ ์˜ฎ๊ฒจ๋ณด๊ธฐ

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

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

class Reservation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
  • ํ™˜์ž/์˜์‚ฌ ์ƒ์„ฑ

    d1 = Doctor.objects.create(name='dr.john')
    d2 = Doctor.objects.create(name='dr.kim')
    
    p1 = Patient.objects.create(name='๊ตฌ๋ฆ„')
    p2 = Patient.objects.create(name='๊ทผ์ œ')
  • ์˜ˆ์•ฝ ๋งŒ๋“ค๊ธฐ

    Reservation.objects.create(doctor=d1, patient=p1)
    Reservation.objects.create(doctor=d1, patient=p2)
    Reservation.objects.create(doctor=d2, patient=p1)
  • 1๋ฒˆ ์˜์‚ฌ์˜ ์˜ˆ์•ฝ ๋ชฉ๋ก

    d1.reservation_set.all()
  • 1๋ฒˆ ํ™˜์ž์˜ ์˜ˆ์•ฝ ๋ชฉ๋ก

    p1.reservation_set.all()
  • 1๋ฒˆ ์˜์‚ฌ์˜ ํ™˜์ž ์ถœ๋ ฅ

    for reservation in d1.reservation_set.all():
        print(reservation.patient.name)

2. ์ค‘๊ฐœ ๋ชจ๋ธ ํ™œ์šฉ

์˜์‚ฌ - ํ™˜์ž๋“ค / ํ™˜์ž - ์˜์‚ฌ๋“ค๋กœ ์ง์ ‘ ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ManyToManyField๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

through ์˜ต์…˜์„ ํ†ตํ•ด ์ค‘๊ฐœ ๋ชจ๋ธ์„ ์„ ์–ธํ•œ๋‹ค.

image-20200428192226753

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

class Patient(models.Model):
    name = models.CharField(max_length=10)
    # M:N ํ•„๋“œ! reservation ํ†ตํ•ด์„œ, Doctor์— ์ ‘๊ทผ์„ ์˜๋ฏธ
    doctors = models.ManyToManyField(Doctor, 
                                    through='Reservation')

class Reservation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
  • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ๋งŒ๋“ค๊ฑฐ๋‚˜, migrate๋ฅผ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

    • ์ฆ‰, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ „ํ˜€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์€ ์—†๊ณ , ORM ์กฐ์ž‘์—์„œ์˜ ์ฐจ์ด๋งŒ ์กด์žฌํ•œ๋‹ค.

  • ์˜์‚ฌ, ํ™˜์ž ์˜ค๋ธŒ์ ํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ

    p1 = Patient.objects.get(pk=1)
    d1 = Doctor.objects.get(pk=1)
  • 1๋ฒˆ ํ™˜์ž์˜ ์˜์‚ฌ ๋ชฉ๋ก

    ManyToManyField ๊ฐ€ ์ •์˜๋œ Patient ๋Š” ์ง์ ‘ ์ฐธ์กฐ

    p1.doctors.all()
  • 1๋ฒˆ ์˜์‚ฌ์˜ ํ™˜์ž ๋ชฉ๋ก

    Doctor ๋Š” ์ง์ ‘ ์ฐธ์กฐ๊ฐ€ ์•„๋‹ˆ๋ผ Patient ๋ชจ๋ธ์˜ ์—ญ์ฐธ์กฐ.

    ์ฆ‰, ๊ธฐ๋ณธ naming convention์— ๋”ฐ๋ผ ์ฐธ์กฐ

    d1.patient_set.all()                                                                   
  • related_name : ์—ญ์ฐธ์กฐ ์˜ต์…˜

    ๊ธฐ๋ณธ ๊ฐ’์€ {model ์ด๋ฆ„}_set

    image-20200428192427702
    • ์—ญ์ฐธ์กฐ ์„ค์ •์ด ๋ฐ˜๋“œ์‹œ ์„ค์ •๋˜์–ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ์žˆ๋‹ค

      • django์—์„œ makemigrations ํ•˜๋Š” ๊ฒฝ์šฐ ์ง์ ‘ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์ค€๋‹ค

      • ex) ์ž‘์„ฑ์ž(User)-๊ฒŒ์‹œ๊ธ€(Article), ์ข‹์•„์š”๋ˆ„๋ฅธ์‚ฌ๋žŒ(User)-๊ฒŒ์‹œ๊ธ€(Article)

        • ์œ„์˜ ๊ด€๊ณ„ ์„ค์ •์‹œ ๋ชจ๋‘ Article ํด๋ž˜์Šค์— related_name ์—†์ด ์ •์˜๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด, ์—ญ์ฐธ์กฐ ์ด์Šˆ ๋ฐœ์ƒ

    class Doctor(models.Model):
        name = models.TextField()
    
    class Patient(models.Model):
        name = models.TextField()
        # ์—ญ์ฐธ์กฐ ์„ค์ •. related_name
        doctors = models.ManyToManyField(Doctor, 
                            through='Reservation',
                            related_name='patients')
    
    class Reservation(models.Model):
        doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
        patient = models.ForeignKey(Patient, on_delete=models.CASCADE)

3. ์ค‘๊ฐœ ๋ชจ๋ธ ์—†์ด ์„ค์ •

์ผ๋ฐ˜์ ์œผ๋กœ ์ถ”๊ฐ€ ํ•„๋“œ ๊ตฌ์„ฑ์ด ํ•„์š” ์—†์ด id ๊ฐ’๋งŒ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์„ ์–ธํ•œ๋‹ค.

class Doctor(models.Model):
    name = models.TextField()

class Patient(models.Model):
    name = models.TextField()
    doctors = models.ManyToManyField(Doctor, 
                        related_name='patients')
  • ์ด ๊ฒฝ์šฐ ์•ฑ์ด๋ฆ„_patient_doctors ๋กœ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋œ๋‹ค.

  • ํ•ด๋‹น ํ…Œ์ด๋ธ”์— Create/Delete๋ฅผ ์œ„ํ•ด์„œ๋Š” (์˜ˆ์•ฝ์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”) ์•„๋ž˜์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ™œ์šฉํ•œ๋‹ค.

    d1.patients.add(p1)
    # ๊ฐ๊ฐ ์กฐํšŒ ํ•ด๋ณด์ž.
    d1.patients.all()
    p1.doctors.all()
    d1.patients.remove(p1)
    # ๊ฐ๊ฐ ์กฐํšŒ ํ•ด๋ณด์ž.
    d1.patients.all()
    p1.doctors.all()

๊ฒฐ๋ก 

  • ์ค‘๊ฐœ model์ด ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ

    • ํŠน์ • Class์— ManyToManyField ์„ ์–ธ (์ค‘๊ฐœ table ์ž๋™์„ ์–ธ)

  • ์ค‘๊ฐœ model์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ (์ถ”๊ฐ€ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ)

    • ์ค‘๊ฐœ model ์ •์˜ ํ›„

    • ํŠน์ • Class์— ManyToManyField์— through option์„ ํ†ตํ•ด ์กฐ์ž‘

+

  • ManyToMany ์—์„œ๋Š” ๋ฐ˜๋””์Šค ๋ณต์ˆ˜ํ˜•์˜ ํ‘œํ˜„์œผ๋กœ related_name์„ ์„ ์–ธํ•˜์ž!!

id๋กœ ์›ํ•˜๋Š” ๊ฐ’ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

In [13]: Reservation.objects.filter(doctor_id=3)                                                                   
Out[13]: <QuerySet [<Reservation: Reservation object (3)>]>

Delete

๋ถˆํŽธํ•œ ver.

In [18]: Reservation.objects.filter(patient_id=1,doctor_id=1).delete()                                             
Out[18]: (1, {'manytomany.Reservation': 1})

์ข‹์•„์š” ๊ธฐ๋Šฅ

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    image = models.ImageField(blank=True)
    # DB ์ €์žฅ x, ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด ์ž˜๋ผ์„œ ํ‘œํ˜„
    image_thumbnail = ImageSpecField(source='image',
                                      processors=[ResizeToFill(300, 300)],
                                      format='JPEG',
                                      options={'quality': 60})
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    #์ข‹์•„์š” ๊ธฐ๋Šฅ
    users = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                    related_name='like_posts')

$ python manage.py makemigrations
SystemCheckError: System check identified some issues:

ERRORS:
posts.Post.user: (fields.E304) Reverse accessor for 'Post.user' clashes with reverse accessor for 'Post.users'.
        HINT: Add or change a related_name argument to the definition for 'Post.user' or 'Post.users'.
posts.Post.users: (fields.E304) Reverse accessor for 'Post.users' clashes with reverse accessor for 'Post.user'.
        HINT: Add or change a related_name argument to the definition for 'Post.users' or 'Post.user'.

  • URL - variable routing

  • View - ์ข‹์•„์š” ๋ˆŒ๋ €์œผ๋ฉด ์ทจ์†Œ, ์•ˆ๋ˆŒ๋ €์œผ๋ฉด ์ข‹์•„์š” (Toggle)

ํŒ”๋กœ์šฐ ๊ธฐ๋Šฅ

  • URL - variable routing

    • accoutns/1/follow

  • View

    • follow ๋˜์–ด์žˆ๋Š” ๊ฒฝ์šฐ add

    • ์•ˆ๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ remove

  • Template(์‘๋‹ต)

    • User detail redirect

Last updated

Was this helpful?