๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Web/Django

Django - ๊ฐ์ฒด ์ฐธ์กฐ ๊ด€๊ณ„

by DUSTIN KANG 2023. 11. 29.

๐Ÿšฉ ๋ฌธ์ œ ๋ฐœ์ƒ

์šฐ์„ , ์ œ๊ฐ€ ๋ถ€๋”ชํ˜”์—ˆ๋˜ ์ด์Šˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์œ ์ €๊ฐ€ ๊ฒŒ์‹œ๊ธ€์„ ์ƒ์„ฑํ•˜๋Š” ์š”์ฒญ์„ ํ•  ๋•Œ, ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ ๊ฐ’์œผ๋กœ '๊ธฐํƒ€'๋ผ๋Š” ๊ฐ’์œผ๋กœ ์ €์žฅํ•˜๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” '๊ธฐํƒ€'๋ผ๋Š” ๊ฐ’์€ ์• ์ดˆ์— DB ์ €์žฅ๋œ ๊ฐ’์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— NOT NULL ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ ๊ฒƒ์ด์ฃ .

 

  • models.py
class Article(CommonModel):
    author = models.ForeignKey("users.User", on_delete = models.CASCADE, related_name="articles",)
    title = models.CharField(max_length=100, blank=False, null=False)
    context = models.TextField()
    category = models.ForeignKey("boards.Category", on_delete=models.CASCADE, related_name="category")
    
class Category(models.Model):
    name = models.CharField(max_length=10, default='๊ธฐํƒ€')
    

# ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด ๊ด€๋ จ์—†๋Š” ์ฝ”๋“œ๋Š” ์ƒ๋žตํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋งŒ์•ฝ ์ด์ƒํƒœ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๊ฒŒ๋˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

django.db.utils.IntegrityError: NOT NULL constraint failed: boards_article.category_id

 

ํ•ด๋‹น ์—๋Ÿฌ๋Š” ๊ฒŒ์‹œ๊ธ€์„ ์ƒ์„ฑํ•  ๋•Œ ์นดํ…Œ๊ณ ๋ฆฌ ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจํ•œ ๊ฐ’์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์นดํ…Œ๊ณ ๋ฆฌ๋ผ๋Š” ํ…Œ์ด๋ธ”์— ๊ฐ’ ์ž์ฒด๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋งŒ๋“ค์ง€ ๋ชปํ–ˆ๋˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ ํ•„๋“œ์— ๊ฐ’์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•, NULL์„ ํ—ˆ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋งŒ์•ฝ ์ด๋ฏธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋Œ์ดํ‚ฌ ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด Null์„ ํ—ˆ์šฉํ•˜๊ณ  Blank๋ฅผ True๋กœ ํ•ด๋†“๋Š” ์ „๋žต์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. 

์ €๋Š” ์•„์ง ์ดˆ๊ธฐํ™”๋ฅผ ํ•˜์ง€ ์•Š๋Š” ์ƒํ™ฉ์ด๊ธฐ ๋•Œ๋ฌธ์— `default` ๊ฐ’์„ ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค. 

 

๊ทธ๋ฆฌ๊ณ  view ํ•จ์ˆ˜์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋นˆ ๊ฐ’์ผ ๊ฒฝ์šฐ ๊ธฐํƒ€๋ฅผ ๋ฐ›๊ฒŒ๋”ํ•ด์„œ ๋งŒ์•ฝ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

def exist_category(request):
    category_name = request.data.get('category', '๊ธฐํƒ€')
    category, _ = Category.objects.get_or_create(name=category_name)
    return category.id

 


๐Ÿค” ๊ฐ์ฒด ๊ฐ„ ์ฐธ์กฐ ๊ด€๊ณ„?

์˜ค๋Š˜ ๋ฌธ์ œ ๋ฐœ์ƒ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•ด์•ผํ•  ๋ถ€๋ถ„์€ ๊ฐ์ฒด ๊ฐ„ ์ฐธ์กฐ ๊ด€๊ณ„์ž…๋‹ˆ๋‹ค.

๊ฐ์ฒด ๊ฐ„ ์ฐธ์กฐ ๊ด€๊ณ„๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์™ธ๋ž˜ํ‚ค(Foregin Key)๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์™ธ๋ž˜ํ‚ค(Foreign Key)
์™ธ๋ž˜ํ‚ค๋Š” ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ํ–‰์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ํ‚ค๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ ์ฐธ์กฐ๋˜๋Š” ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ๊ธฐ๋ณธ ํ‚ค(Primary Key)๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค.
์ฐธ์กฐ๋˜๋Š” ํ…Œ์ด๋ธ”์˜ ํ–‰์„ ์ฐธ์กฐํ•˜๋Š” ์™ธ๋ž˜ํ‚ค(FK)๊ฐ€ ์กด์žฌํ•˜๋Š” ํ•œ ์‚ญ์ œ๋  ์ˆ˜ ์—†๊ณ  ๊ธฐ๋ณธํ‚ค(PK) ๋˜ํ•œ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์—†๋‹ค. (์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ)
๋Œ€ํ‘œ์ ์œผ๋กœ, 1:N ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๋Š”  ์˜ˆ๋Š” ๊ฒŒ์‹œ๋ฌผ๊ณผ ๋Œ“๊ธ€์˜ ๊ด€๊ณ„ ํ˜น์€ ์œ ์ €์™€ ๊ฒŒ์‹œ๊ธ€ ๊ด€๊ณ„๊ฐ€ ์žˆ๋‹ค. ํ•œ ๋ช…์˜ ์œ ์ €๊ฐ€ ์—ฌ๋Ÿฌ ๊ฒŒ์‹œ๋ฌผ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ•œ ๊ฒŒ์‹œ๋ฌผ์—๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋Œ“๊ธ€์ด ๋‹ฌ๋ฆด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

๋งŒ์•ฝ, ํ•˜๋‚˜์˜ ์นดํ…Œ๊ณ ๋ฆฌ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฒŒ์‹œ๋ฌผ์„ ํฌํ•จํ•˜๊ณ  ์‹ถ์œผ๋ฉด 1:N์˜ ๊ด€๊ณ„์ด๊ณ  ํ•œ ๊ฒŒ์‹œ๋ฌผ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๊ฐ€์ง„๋‹ค๋ฉด ํ•œ ์นดํ…Œ๊ณ ๋ฆฌ๋„ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฒŒ์‹œ๋ฌผ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— N:M์˜ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

 

์ƒํ™ฉ์— ๋”ฐ๋ผ ๊ด€๊ณ„๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.  ์ถ”๊ฐ€๋กœ ์œ ์ €์™€ ์œ ์ €์˜ ํ”„๋กœํ•„ ๋ชจ๋ธ์ธ 1:1 ๋Œ€์‘ ๊ด€๊ณ„ ํ…Œ์ด๋ธ”๋„ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿš€ ๋ชจ๋ธ์—์„œ 1:N ๊ตฌํ˜„ํ•˜๊ธฐ

๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด Memo๋ผ๋Š” ๊ฐ์ฒด์™€ Category๋ผ๋Š” ๊ฐ์ฒด๊ฐ€ 1:N ๊ด€๊ณ„๋ฅผ ๋งบ๊ฒŒ๋” ํ•˜๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค.

์ฆ‰, ํ•˜๋‚˜์˜ ์นดํ…Œ๊ณ ๋ฆฌ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ฉ”๋ชจ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

1:N ๊ฐ์ฒด ์ฐธ์กฐ ๊ด€๊ณ„

 

ํ•ด๋‹น ๊ฐ์ฒด์— ์™ธ๋ž˜ํ‚ค๋ฅผ ๊ฐ–๋Š” ์†์„ฑ์€ ํ•ด๋‹น ์˜ต์…˜๋“ค์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  ์„ค๋ช…
์ฐธ์กฐํ•  ํ…Œ์ด๋ธ” ๊ฐ€์žฅ ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์—ฐ๊ฒฐํ•  ๋ชจ๋ธ ํด๋ž˜์Šค๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋ฉฐ ํ•ด๋‹น ์™ธ๋ž˜ํ‚ค์—์„œ ์–ด๋–ค ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ• ์ง€๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
๊ด€๊ณ„์ƒ ์ด๋ฆ„(`related_name`) ๊ฐœ์ฒด ๊ด€๊ณ„์— ์‚ฌ์šฉํ•  ์ด๋ฆ„์„ ๋‚˜ํƒ€๋‚ด๋Š”๋ฐ ์—ญ๋ฐฉํ–ฅ์—์„œ ์ฐธ์กฐํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์ด๋ฆ„์ด๋‹ค. 
๊ฐœ์ฒด ์ˆ˜์ • ์‚ญ์ œ์‹œ ์ˆ˜ํ–‰ ๋™์ž‘(`on_delete / on_update`) ์นดํ…Œ๊ณ ๋ฆฌ ๋ชจ๋ธ์„ ์‚ญ์ œ๋  ๋•Œ Memo ๋ชจ๋ธ์ด ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ๋งํ•œ๋‹ค.
DB ํ•„๋“œ ์ด๋ฆ„(`db_colum`) ํ…Œ์ด๋ธ”์— ์ •์˜๋˜๋Š” ์ด๋ฆ„์„ ๋งํ•˜๋ฉฐ Memo ํ…Œ์ด๋ธ”์˜ category ํ•„๋“œ๊ฐ€ "categoty"๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ง€์ •๋œ๋‹ค.

 

๊ด€๊ณ„์ƒ ์ด๋ฆ„?

์—ฌ๊ธฐ์„œ, ๊ด€๊ณ„์ƒ ์ด๋ฆ„?์ด๋ผ๋Š” ๋ง์„ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šฐ์‹ค ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

์ž์‹์ด ๋ถ€๋ชจ๋ฅผ ์ฐธ์กฐํ•˜๊ณ ์ž ํ• ๋•Œ ์—ญ์ฐธ์กฐ๋ฅผ ํ•œ๋‹ค.

 

๋งŒ์•ฝ Memo๋ผ๋Š” ๋ชจ๋ธ์ด Category ๋ชจ๋ธ์„ ์ฐธ์กฐํ•˜๊ฒŒ๋œ๋‹ค๋ฉด ์ด๋ฅผ ์ •์ฐธ์กฐ๋ผ๊ณ  ํ•˜๊ณ  ๋ฐ˜๋Œ€๋กœ, Category ๋ชจ๋ธ์ด Memo ๋ชจ๋ธ์„ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์„ ์—ญ์ฐธ์กฐ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. 

 

์ •์ฐธ์กฐ

์ •์ฐธ์กฐ๋Š” ํŠน๋ณ„ํ•˜๊ฒŒ ๋ญ ์—†์ด ๋‹ค์Œ ์ฟผ๋ฆฌ์…‹ API๋กœ ํ•„๋“œ๋ฅผ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

memo = Memo.objects.get(pk=10)
memo.category.name # ์ผ์ƒ

 

์—ญ์ฐธ์กฐ

๋ฐ˜๋Œ€๋กœ ์—ญ์ฐธ์กฐ๋Š” `ํ…Œ์ด๋ธ”๋ช…_set`์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ ์•„๊นŒ ๋‚˜์™”๋˜ `related_name` ์˜ต์…˜์„ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

cate = Category.objects.get(pk=1)
cate.memos.all() # <QuerySet [<Memo: Memo object (9)>, <Memo: Memo object (10)>]>
cate.memos.get(pk=9) #  <Memo: Memo object (9)>

 

์‹ค์Šต

์ด๋ฒˆ์—๋Š” ์‹ค์ „์œผ๋กœ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ˆ˜์ • ๋ฐ ์‚ญ์ œ๋ฅผ ํ•ด๋ณด๋Š” ์‹ค์Šต์„ ์ง„ํ–‰ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ๊ฑด "์šด๋™"์ด๋ผ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ `memo1`์ด๋ผ๋Š” ๋ฉ”๋ชจ ๊ฐ์ฒด์— ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฟผ๋ฆฌ๋ฅผ ์š”์ฒญํ•˜๋ฉด `memo1`์ด๋ผ๋Š” ๊ฐ์ฒด์— ์šด๋™์ด๋ผ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค.

 

 

ํ•˜์ง€๋งŒ, ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒŒ ์žˆ์–ด์š”!

 

๋ฉ”๋ชจ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ "์šด๋™"์ด๋ผ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋„ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๋ ค ํ–ˆ๋Š”๋ฐ ์ด๋ฏธ "์šด๋™"์ด๋ผ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์กด์žฌํ–ˆ์„ ๋•Œ์˜ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์šด๋™์ด๋ผ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ƒˆ๋กœ ๋งŒ๋“ค๊ฒŒ ๋˜๋ฉด Uniqueํ•œ ๊ฐ’๊ณผ ์œ ํšจํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•„ํ„ฐ๋ง์„ ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ, `get()` ๋Œ€์‹  `first()`๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ์ด์œ ๋Š” ๋งŒ์•ฝ์— ์กด์žฌํ•˜๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ `first`์ผ ๊ฒฝ์šฐ None์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


๐Ÿš€ M:N ๊ด€๊ณ„

์ด๋ฒˆ์—๋Š” ํ•˜๋‚˜์˜ ํ…Œ์ด๋ธ”์— ํ•˜๋‚˜ ์ด์ƒ ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ ˆ์ฝ”๋“œ์™€ ์ฐธ์กฐํ•˜๋Š” ๊ด€๊ณ„(ManyToMany)์ž…๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ, ์ข‹์•„์š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์œ ์ €๊ฐ€ ์—ฌ๋Ÿฌ ๊ฒŒ์‹œ๋ฌผ์„ ๋ˆ„๋ฅผ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•˜์ฃ .

 

์™ธ๋ž˜ํ‚ค๋กœ M:N๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค๊ณ ?

๋งŒ์•ฝ ํ•™์ƒ์ด ์˜์–ด ์ˆ˜์—…๋„ ๋“ฃ๊ณ  ์ปดํ“จํ„ฐ ์ˆ˜์—…์„ ๋“ฃ๋Š”๋‹ค๊ณ  ๊ฐ€์ •ํ•  ๋•Œ ์™ธ๋ž˜ํ‚ค ์ฐธ์กฐ ๊ด€๊ณ„๋ฅผ ๋งŒ๋“ค๋ฉด ์–ด๋–ค ์ผ์ด ๋ฒŒ์–ด์งˆ๊นŒ์š”?

lecture1 = Lecture.objects.create(name='english')
lecture2 = Lecture.objects.create(name='computer')
student1 = Stuendt.objects.create(name='dustin', lecture=lecture1)
student1 = Stuendt.objects.create(name='dustin', lecture=lecture2) #?

 

1NF ์œ„๋ฐ˜์ด ๋˜์–ด ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ์†์„ฑ์— ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ’์„ ๋„ฃ๋Š” ๊ฒƒ์€ ์• ์ดˆ์— ๋ถˆ๊ฐ€๋Šฅํ•œ ์ผ์ž…๋‹ˆ๋‹ค.

 

M:N ๊ด€๊ณ„๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

  • ์ค‘๊ฐœ ํ…Œ์ด๋ธ”
  • ManyToManyField 

์ค‘๊ฐœ ํ…Œ์ด๋ธ”

ํ•™์ƒ๊ณผ ์ˆ˜์—… ์‚ฌ์ด์— ์ค‘๊ฐœํ…Œ์ด๋ธ”์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด `Student`๋‚˜ `Lecture` ํ…Œ์ด๋ธ”์—์„œ ์—ญ์ฐธ์กฐ๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด ๊ด€๊ณ„๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# models.py
class StuLec(models.Model):
	student = models.ForeignKey(Student, on_delete=models.CASCADE)
	lecture = models.ForeignKey(Lecture, on_delete=models.CASCADE)
    
# views.py
StuLec.objects.create(student=student1, lecture=lecture1)
StuLec.objects.create(student=student1, lecture=lecture2)

student1.StuLec_set.all()
lecture1.StuLec_set.all()

์ค‘๊ฐœํ…Œ์ด๋ธ”์„ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ

 

ManyToMany

์ด๋ฒˆ์—๋Š” ์ค‘๊ฐœํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ตณ์ด ์ค‘๊ฐœํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ๋ฐ”๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜์ฃ .

class Student(models.Model):
    lecture = models.ManyToManyField(Lecture, related_name="students")
    name = models.TextField(null=True)

 

  ์„ค๋ช…
์ฐธ์กฐํ•  ํ…Œ์ด๋ธ” ๊ฐ€์žฅ ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์–ด๋–ค ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ• ์ง€๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
๊ด€๊ณ„์ƒ ์ด๋ฆ„(`related_name`) ๊ฐœ์ฒด ๊ด€๊ณ„์— ์‚ฌ์šฉํ•  ์ด๋ฆ„์„ ๋‚˜ํƒ€๋‚ด๋Š”๋ฐ ์—ญ๋ฐฉํ–ฅ์—์„œ ์ฐธ์กฐํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์ด๋ฆ„์ด๋‹ค. 
through ์ปค์Šคํ…€์œผ๋กœ ์ค‘๊ฐœํ…Œ์ด๋ธ”์„ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์ด๋‹ค. (๊ทธ๋ƒฅ ์ค‘๊ฐœํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค ๋•Œ ์“ฐ์ž…๋‹ˆ๋‹ค.)
through_field ๊ตฌ์ฒด์ ์ธ ํ•„๋“œ๋ฅผ ์„ค์ •ํ•  ๋•Œ ์“ฐ์ธ๋‹ค. `through_fields = (A.field, B.field)

 

์œ„ ์ƒํƒœ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜๊ฒŒ๋˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ `app_student_lecture`๋ผ๋Š” ์ƒˆ๋กœ์šด ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

 

๊ด€๊ณ„ ํ˜•์„ฑํ•˜๊ธฐ

`add()`์™€ `remove()`๋ฅผ ํ†ตํ•ด ๊ด€๊ณ„๋ฅผ ํ˜•์„ฑํ•˜๊ณ  ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ด€๊ณ„๋ฅผ ์ œ๊ฑฐํ–ˆ์„ ๋•Œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋Š” ์‚ฌ๋ผ์ง€์ง€์•Š๊ณ  ๊ด€๊ณ„๋งŒ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

 

๋งŒ์•ฝ ๋„์ค‘ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์—๋Ÿฌ๋Š” M2M ํ…Œ์ด๋ธ”์—์„œ ์ค‘๊ฐœํ…Œ์ด๋ธ”๋กœ ๋ณ€๊ฒฝํ–ˆ์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” `app.Student.lecture` ์ปฌ๋Ÿผ์„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์—์„œ ์ง€์šฐ๊ณ  ๋‹ค์‹œ ์ƒ์„ฑํ•˜๋ฉด ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ๊ธฐ์กด ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ์šฐ์„  dumpํ•˜๊ณ  importํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 ValueError: Cannot alter field app.Student.lecture into app.Student.lecture - they are not compatible types (you cannot alter to or from M2M fields, or add or remove through= on M2M fields)

๐Ÿ“Œ 1:1 ๊ด€๊ณ„(OnetoOneField)

1:1 ๊ด€๊ณ„(์ผ๋Œ€์ผ ๊ด€๊ณ„)๋Š” ํ•œ๊ฐœ์˜ ๋ชจ๋ธ์ด ๋˜ ๋‹ค๋ฅธ ํ•œ๊ฐœ์˜ ๋ชจ๋ธ๊ณผ ์—ฐ๊ฒฐํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, User๋ผ๋Š” ๋ชจ๋ธ์ด ์žˆ์„ ๋•Œ, User๋ชจ๋ธ์˜ UserProfile ๋ชจ๋ธ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.

 

1:N๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์—ญ์ฐธ์กฐ ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š” ๊ฒฝ์šฐ์—” `related_name`์„ ํ†ตํ•ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

class User(models.Model):
	username = models.CharField(max_length=100)
    
class UserProfile(models.Model):
	user = models.OneToOneField(User, on_delete=models.CASCADE) # 1:1
    bio = models.TextField()

 

๐Ÿ“Œ GenericForeignKey(GFK)

์™ธ๋ž˜ํ‚ค์ด๊ธด ํ•œ๋ฐ Genericํ•œ ์™ธ๋ž˜ํ‚ค? ์ด๊ฒƒ์€ ๋ฌด์—‡์ผ๊นŒ์š”?

์–ด๋– ํ•œ ๋ฐ์ดํ„ฐ ํ˜•์‹์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ์—ฌ๋Ÿฌ ํƒ€์ž…์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๋‘์–ด ์™ธ๋ž˜ํ‚ค๋ฅผ ๋ชจ๋“  ๋ชจ๋ธ์— ์—ฐ๊ฒฐํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

 

์•„๋ž˜ ๊ทธ๋ฆผ์ฒ˜๋Ÿผ, ์ปค๋‹ค๋ž€ ๋ฉ”๋ชจ๋ณด๋“œ์— ๋ฉ”๋ชจ๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ๊ณ  ์ด๋ฏธ์ง€๋ฅผ ๋ถ™์ผ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๋ฉ”๋ชจ์ง€๋‚˜ ์ด๋ฏธ์ง€์— ์ฝ”๋ฉ˜ํŠธ๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋ชจ๋ธ์„ ์„ค๊ณ„ํ•œ๋‹ค๋ฉด `comment`๋ผ๋Š” ๋ชจ๋ธ์€ `Memo`์™€ `Image`๋ชจ๋ธ์—๋„ ๊ฐ๊ฐ ์กด์žฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋Œ€์ถฉ ์ด๋Ÿฐ ๋Š๋‚Œ.

 

class Memo(models.Model):
    title = models.CharField(max_length=30)
    content = models.TextField()

class Image(models.Model):
    title = models.CharField(max_length=100)
    image = models.ImageField(upload_to='images/')

class comment(models.Model):
    text = models.CharField(max_length=100)

 

ForeginKey๋ฅผ ์ƒ์„ฑํ•˜์—ฌ Memo๋‚˜ Image์— ๋ง๋ถ™์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ ๋‹ค๋ฉด ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ  ์ปฌ๋Ÿผ์„ ์˜๋„์น˜ ์•Š๊ฒŒ ์ƒ์„ฑํ•ด์•ผํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด GenericForeignKey๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

ContentType

Django์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ GFK๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด Contenttypes๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ฒ˜์Œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๊ฒŒ๋˜๋ฉด `django_content_type`์ด๋ผ๋Š” ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ž, ์ด Contenttype์ด๋ผ๋Š” ๊ฒƒ์„ ์ž˜๋งŒ ํ™œ์šฉํ•˜๋ฉด ์ผ์ผํžˆ ๋ชจ๋ธ๊ณผ FK๋ฅผ ๋งบ์„ ํ•„์š” ์—†์ด ContentType๊ณผ FK๋ฅผ ๋งบ์œผ๋ฉด ๊ด€๊ณ„๋ฅผ ์‰ฝ๊ฒŒ ๋งบ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

content_type ํ…Œ์ด๋ธ”

 

์‹ค์Šต

์•ž์„  ๋ชจ๋ธ๋“ค์„ GFK์™€ ContentType ๋ชจ๋ธ์„ ์ด์šฉํ•ด ์žฌ์ •์˜ ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์•„์ง๊นŒ์ง€ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šฐ์‹ค ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์šฐ์„ , ๊ธฐ์กด์˜ ๋ชจ๋ธ์— `GenericRelation`์ด๋ผ๋Š” ๊ฒƒ์ด ์ƒ๊ฒจ๋‚œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ฝ”๋ฉ˜ํŠธ ๊ฐ์ฒด๋ฅผ ์—ญ์ฐธ์กฐํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋Œ€๋ง์˜ ์ฝ”๋ฉ˜ํŠธ ๊ฐ์ฒด์—” ๊ธฐ์กด `text` ํ•„๋“œ๋ฅผ ์ œ์™ธํ•œ ์„ธ๊ฐ€์ง€์˜ ํŠน๋ณ„ํ•œ ํ•„๋“œ๊ฐ€ ์ƒ๊ฒจ๋‚ฌ์Šต๋‹ˆ๋‹ค.

  • content_type : ContentType ๋ชจ๋ธ๊ณผ FK๋กœ ์—ฐ๊ฒฐ๋˜๋Š” ํ•„๋“œ์ด๋‹ค.
  • object_id : ๊ด€๋ จ๋œ ๋ชจ๋ธ์˜ PK์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ํ•„๋“œ ์ด๋‹ค.
  • content_object :  ์œ„ ๋‘ ํ•„๋“œ์˜ ์ด๋ฆ„์„ ์ธ์ž๋กœ ๋„˜๊ฒจ์ค€๋‹ค. ๊ธฐ๋ณธ ๊ฐ’์ด๋ฏ€๋กœ ์ƒ๋žต์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation

class Memo(models.Model):
    title = models.CharField(max_length=30)
    content = models.TextField()
    comment = GenericRelation('comment', related_query_name='memo') # ์—ญ์ฐธ์กฐํ•  ๋•Œ ์‚ฌ์šฉ

class Image(models.Model):
    title = models.CharField(max_length=100)
    image = models.ImageField(upload_to='images/')
    comment = GenericRelation('comment', related_query_name='image') # ์—ญ์ฐธ์กฐํ•  ๋•Œ ์‚ฌ์šฉ

class comment(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    text = models.CharField(max_length=100)

 

 

๐Ÿ’ก content_object๋Š” DB์— ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค.
๋งˆ์ฐฌ๊ฐ€์ง€๋กœ GenericForeignKey๋Š” DB Table์— ์ปฌ๋Ÿผ์œผ๋กœ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. 

 

์‹ค์ œ๋กœ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

comment = Comment.objects.filter(memo__title='1์‹œ์— ํšŒ์˜ํ•ฉ๋‹ˆ๋‹ค:)')
comment = Comment.objects.create(content_object=memo, text="3์‹œ๋กœ ๋ณ€๊ฒฝ")

 

๊ทธ๋Ÿฌ๋ฉด..

์šฐ์„ , GFK๋ฅผ ์ดํ•ดํ•˜๊ณ  ๋‚˜์„œ ๋“ค์—ˆ๋˜ ์ƒ๊ฐ์€ ContentType์„ ๊ฑฐ์ณ ํŠน์ • ๋ชจ๋ธ์„ ์ฐพ์•„๊ฐ€์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” FK ๋ณด๋‹ค๋Š” ์„ฑ๋Šฅ์ด ์ข‹์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•œ๋‹ค. ๊ฐ์ฒด๋ฅผ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•˜๊ณ  ์—ฌ๋Ÿฌ๋ฒˆ ๊ฑฐ์ณ ๊ฐ€์ ธ์™€์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.  ๊ทธ๋Ÿฌ๋ฉด `prefetch_related`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋˜์ง€ ์•Š์„๊นŒ?  ์•ž์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด GFK๋Š” ์—†๋Š” ํ•„๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹คํŒจํ•  ๊ฒƒ์ด๋‹ค. ์ฆ‰, ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋  ๊ฒƒ ๊ฐ™๋‹ค. ์ผ๋‹จ ์ธ์ง€ํ•˜๊ธฐ๋งŒ ํ•˜์ž..


โ˜•๏ธ ํฌ์ŠคํŒ…์ด ๋„์›€์ด ๋˜์—ˆ๋˜ ์ž๋ฃŒ

์˜ค๋Š˜๋„ ์ €์˜ ํฌ์ŠคํŠธ๋ฅผ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์„ค๋ช…์ด ๋ถ€์กฑํ•˜๊ฑฐ๋‚˜ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ ๋ถ€๋ถ„์ด ์žˆ์œผ๋ฉด ๋ถ€๋‹ด์—†์ด ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.