TIL 04-29

๊น€๋•ํ˜‘ยท2026๋…„ 4์›” 29์ผ

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
2/41

๐Ÿ“… 2026-04-29 (์ˆ˜)


โœ๏ธ ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ

DB : N:1 ๊ด€๊ณ„


1. ๊ด€๊ณ„

: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‚ด ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” ๊ฐ„ ๋…ผ๋ฆฌ์ ์ธ ์—ฐ๊ฒฐ ๊ด€๊ณ„

  • ๊ด€๊ณ„์˜ ์ข…๋ฅ˜
    • 1:1 (One to One) : ํ•œ ๋ช…์˜ ์‚ฌ์šฉ์ž๋Š” ์˜ค์ง ํ•˜๋‚˜์˜ ํ”„๋กœํ•„์„ ๊ฐ€์ง. / ํ•œ ํ…Œ์ด๋ธ”์˜ ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ๋‹จ ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ์™€๋งŒ 1:1 ๋งค์นญ

    • N:1 (Many to One) : ํ•˜๋‚˜์˜ ๊ฒŒ์‹œ๊ธ€์—๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋Œ“๊ธ€์ด ๋‹ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค. / ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ๊ฐ€ ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ์— ์†Œ์†๋˜๋Š” ๊ด€๊ณ„ (๋Œ“๊ธ€๊ณผ ๊ฒŒ์‹œ๊ธ€์˜ ๊ด€๊ณ„ --> N : 1)

    • N:M (Many to Many) : ํ•œ ๋ช…์˜ ํ•™์ƒ์€ ์—ฌ๋Ÿฌ ์ˆ˜์—… ์‹ ์ฒญ ๊ฐ€๋Šฅํ•˜๋‹ค. ๋™์‹œ์— ํ•˜๋‚˜์˜ ์ˆ˜์—…์„ ์—ฌ๋Ÿฌ ํ•™์ƒ์ด ์ˆ˜๊ฐ•ํ•œ๋‹ค. / ์ค‘๊ฐœ ํ…Œ์ด๋ธ” ํ•„์š”, ์—ฌ๋Ÿฌ ๋ ˆ์ฝ”๋“œ๊ฐ€ ์—ฌ๋Ÿฌ ๋ ˆ์ฝ”๋“œ์™€ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ.

2. ๋Œ“๊ธ€ ๋ชจ๋ธ ์ •์˜

  • ForeignKey ํ•„๋“œ : ํ•œ ๋ชจ๋ธ์ด ๋‹ค๋ฅธ ๋ชจ๋ธ์„ ์ฐธ์กฐํ•˜๋Š” ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•˜๋Š” ํ•„๋“œ (์ฃผ๋กœ N:1์—์„œ ์‚ฌ์šฉ, ๊ด€๊ณ„ ์„ค์ •์„ ์œ„ํ•œ FK๋ฅผ N:1์—์„œ 1์„ ๋‹ด๋‹นํ•˜๋Š” ํ…Œ์ด๋ธ”์— ์œ„์น˜์‹œํ‚ค๋ฉด ์•ˆ๋œ๋‹ค.)
ForeignKey(to, on_delete)

  • on_delete ์†์„ฑ ์ข…๋ฅ˜

    • CASCADE : ์ฐธ์กฐ๋œ ๊ฐ์ฒด(๋ถ€๋ชจ ๊ฐ์ฒด)๊ฐ€ ์‚ญ์ œ ๋  ๋•Œ ์ด๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๋ชจ๋“  ๊ฐ์ฒด๋„ ์‚ญ์ œ๋˜๋„๋ก ์ง€์ •
    • PROTECT : ์‚ญ์ œํ•˜๋ ค๋Š” ๋ถ€๋ชจ ๊ฐ์ฒด์— ์ž์‹ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ๋ถ€๋ชจ ๊ฐ์ฒด๋ฅผ ์‚ญ์ œํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์ง€์ •
    • SET_NULL : ๋ถ€๋ชจ ๊ฐ์ฒด๊ฐ€ ์‚ญ์ œ๋˜๋ฉด, ํ•ด๋‹น ํ•„๋“œ์— ๊ฐ’์ด NULL์ด ์ €์žฅ๋˜๋„๋ก ์ง€์ • (FK ํ•„๋“œ ์„ค์ • null=True์—ฌ์•ผ ํ•จ)

3. ์ฐธ์กฐ

: ์ง์ ‘ ๋Œ€์ƒ์˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ํ•„์š”ํ•  ๋•Œ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ
ex) comment.article.content


4. ์—ญ์ฐธ์กฐ

: ๋ˆ„๊ฐ€ ๋‚˜๋ฅผ ์ฐธ์กฐํ•˜๋Š”์ง€ ๊ฑฐ๊พธ๋กœ ์กฐํšŒํ•˜๋Š” ๊ฒƒ
article.comment_set.all()
๋ชจ๋ธ ์ธ์Šคํ„ด์Šค / ์—ญ์ฐธ์กฐ ์ด๋ฆ„(related manager) / QuerySetAPI

  • ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค : models.py์— ์ •์˜๋œ ๋ชจ๋ธ ํด๋ž˜์Šค๋กœ ์ƒ์„ฑ๋œ ์‹ค์ œ ๋ฐ์ดํ„ฐ
  • ์—ญ์ฐธ์กฐ ์ด๋ฆ„(related manager) : N:1 ํ˜น์€ N:M ๊ด€๊ณ„์—์„œ ์—ญ์ฐธ์กฐ ์‹œ์— ์‚ฌ์šฉํ•˜๋Š” ๋งค๋‹ˆ์ €, objects๋ฅผ ํ†ตํ•ด QuerySet API๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๋ฉด ๋จ.
  • QuerySet API :SQL์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์•ˆ์“ฐ๊ณ ๋„ DB ์‚ฌ์šฉ ๊ฐ€๋Šฅ.

โ€ป related manager ์ด๋ฆ„ ๊ทœ์น™ : ๋ชจ๋ธ ํด๋ž˜์Šค๋ช… + _set์ด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ, Django์—์„œ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ค€๋‹ค.
ํŠน์ • ๋Œ“๊ธ€์˜ ๊ฒŒ์‹œ๊ธ€ ์ฐธ์กฐ : comment.article
ํŠน์ • ๊ฒŒ์‹œ๊ธ€์˜ ๋Œ“๊ธ€ ๋ชฉ๋ก ์—ญ์ฐธ์กฐ article.comment_set.all()

5. ๋Œ“๊ธ€ ๊ตฌํ˜„

๋Œ“๊ธ€ create

# articles/forms.py
from .models import Article, Comment

class CommentForm(forms.ModelForm):
	class Meta:
    	model = Comment
        fields = ('content',)
        

# articles / views.py
def detail(request, pk):
	article = Article.objects.get(pk=pk)
    comment_form = CommentForm()
    context = {
    	'article': article,
        'comment_form': comment_form,
    }
    return render(request, 'articles/detail.html', context)

<!-- articles/detail.html -->
<!-- ๊ฒŒ์‹œ๊ธ€ ์ •๋ณด ์ถœ๋ ฅ ์ฝ”๋“œ -->
<hr>

<form action="{% url 'articles:comments_create' article.pk %}" method="POST">
  {% csrf_token %}
  {{ comment_form }}
  <input type="submit">
</form>

# articles/urls.py
app_name = 'article'
urlpatterns = [
	. . .
    path('<int:pk>/comments/', views.comments_create, name='comments_create'),
]


# articles/views.py
def comments_create(request, pk):
	article = Article.objects.get(pk=pk)
    comment_form = CommentForm(request.POST)
    if comment_form.is_valid():
    	comment = comment_form.save(commit=False)
        comment.article = article
        comment.save()
        return redirect('articles:detail', article.pk)
    context = {
         'article' = article,
         'comment_form': comment_form,
	}
    return render(request, 'articles/detail.html', context)

๋Œ“๊ธ€ read

# articles / views.py
def detail(request, pk):
	article = Article.objects.get(pk=pk)
    comment_form = CommentForm()
    
    context = {
    	'article': article,
        'comment_form': comment_form,
        'comments': comments,
    }
    return render(request, 'articles/detail.html', context)

๋Œ“๊ธ€ delete

# articles/urls.py
urlpatterns = [
	. . .
    path('<int:article_pk>/comments/<int:comment_pk>/delete/', views.comments_delete, name='comments_delete')
]

# articles/views.py
def comments_delete(request, article_pk, comment_pk):
	comment = Comment.object.get(pk=comment_pk)
    comment.delete()
    return redirect('articles:detail', article_pk)
<!-- articles/detail.html -->
<ul>
  <% for comment in comments%>
    <li>
      {{ comment.content }}
      <form action="{% url 'articles:comments_delete' article.pk comment.pk}" method="POST">
        {% csrf_token %}
        <input type="submit" value="DELETE">
      </form>
    </li>
  <% endfor %>
</ul>
profile
๋ญ˜๋ด

0๊ฐœ์˜ ๋Œ“๊ธ€