1. Views

  • models : DB에 정보를 저장하고 읽어옴
  • view : 읽어온 모델의 정보들 활용

polls/view.py

from django.http import HttpResponse
from .models import *

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

스크린샷 2023-11-02 오후 8 04 58

  • polls/urls.py 에서 http://127.0.0.1:8000/polls/ 를 접속했을 때 views.index 를 표시하도록 했기 때문에 위와 같이 나옴

2. Templates

  • HTML로 데이터를 잘 표시해주는 도구

  • polls/templates/polls 폴더 생성
  • 그 안에 index.html 생성
<ul>
    <li>text</li>
</ul>
  • polls/views.py
from django.http import HttpResponse
from .models import *
from django.shortcuts import render

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return render(request, 'polls/index.html')

스크린샷 2023-11-02 오후 8 14 41


모델의 값 출력하기

  • polls/views.py
    • render import
    • context 변수 값을 템플릿에 넘겨줌
from django.http import HttpResponse
from .models import *
from django.shortcuts import render

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    context = {'first_question': latest_question_list[0]}
    return render(request, 'polls/index.html', context)


  • templates/polls/index.html
    • 변수는 {{중괄호}} 두개!
    • first_question 변수에 들어있는 값 출력
      • Question 모델의 __str__
<ul>
    <li> {{first_question}} </li>
</ul> 

스크린샷 2023-11-02 오후 8 18 01

반복문

  • django shell 에서
from polls.models import *

latest_question_list = Question.objects.order_by('-pub_date')[:5]
for question in latest_question_list:
    print(question.question_list)

휴가 계획이 있나요?
커피 vs 녹차
가장 좋아하는 디저트는?
휴가를 어디서 보내고 싶으세요?


  • templates 으로

  • indexing
    • questions.0
  • 제어문
from django.http import HttpResponse
from .models import *
from django.shortcuts import render

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    context = {'questions': latest_question_list}
    return render(request, 'polls/index.html', context)
<ul>
     {% for question in questions %} 
        <li> {{question}} </li>
     {% endfor %} 
</ul> 

스크린샷 2023-11-02 오후 8 26 36

조건문

  • question이 없다면 ‘no questions’ 출력
 {% if questions %} 

<ul>
     {% for question in questions %} 
        <li> {{question}} </li>
     {% endfor %} 
</ul> 

 {% else %} 
<p>no questions</p>
 {% endif %} 

스크린샷 2023-11-02 오후 8 29 54

3. 상세페이지

  • question id로 질문 내용 출력하기

polls/urls.py

  • url에 숫자 입력받는 방법
    • <int: >
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
]

polls/views.py

from django.http import HttpResponse
from .models import *
from django.shortcuts import render

def detail(request, question_id):
    question = Question.objects.get(pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

templates/polls/detail.html

  • question.choice_set.all 뒤에 괄호 넣지 않음
<h1></h1>
<ul>
 {% for choice in question.choice_set.all %} 
    <li> {{choice.choice_text}} </li>
 {% endfor %} 
</ul>

스크린샷 2023-11-02 오후 9 22 50

4. 상세 페이지로 링크 추가

  • <a> 태그, url 태그 이용!

url에 앱 이름 설정

  • polls/urls.py
    • app_name = 'polls' 추가
    • 꼭 앱의 이름으로 해야하는 것이 아닌, 임의로 지정하는 값임!
from django.urls import path
from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
]

templates/polls/index.html

{% if questions %} 
<ul>
     {% for question in questions %} 
         <li><a href="{% url 'polls:detail' question.id%}">{{question.question_text}}</li> 
     {% endfor %}  
</ul> 
{% else %} 
<p>no questions</p>
{% endif %} 
  • url 태그로 앱 이름이 polls 이고, path 이름이 detail 인 url을 불러옴
  • 전달하는 인자는 question.id 이고, 출력하는 값은 question.question_text

스크린샷 2023-11-02 오후 9 33 44

/polls 기본 화면


스크린샷 2023-11-02 오후 9 34 06

링크를 타고 들어간 화면 - question.id : 5가 전달됨
detail view에서 detail.html 불러옴
detail.html에 의해 question.choice_set.all 이 출력됨

5. 404 에러 처리

polls/views.py

def detail(request, question_id):
    question = Question.objects.get(pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})
  • questino = Question.objects.get(pk=100000)
    • pk가 100000인 데이터가 없을 경우 Error를 발생시킴
    • 에러가 발생했을 경우 서버는 500 에러 코드를 내림
    • 이를 설명할 수 있는 404 에러로 바꿀 것

스크린샷 2023-11-02 오후 9 51 32


try-except 구문 활용

from django.http import Http404

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DeosNotExist:
        raise Http404('Question does not exist')
    return render(request, 'polls/detail.html', {'question': question})

스크린샷 2023-11-02 오후 9 53 05

shortcuts 활용

  • 훨씬 간결한 코드!
from django.shortcuts import render, get_object_or_404

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

스크린샷 2023-11-02 오후 9 54 38

6. 폼 Forms

선택 옵션 Radio 버튼으로

templates/polls/detail.html

<form action='#' method="post">
    <h1>{{question.question_text}}</h1>
 
     {% for choice in question.choice_set.all %} 
        <input type="radio" name="choice" id="choice " value="">
        <label for="choice{{ forloop.counter }}">
            {{choice.choice_text}}
        </label>
        <br> 
     {% endfor %} 
    
<input type="submit" value="Vote">
</form>

스크린샷 2023-11-02 오후 10 00 42

  • forloop.counter
    • 반복문을 돌면서 1부터 1씩 증가하는 값

token

  • vote 버튼을 누르면 403 Error - CSRF verification failed. 발생
    • 제출할 토큰이 없는데 제출한 경우
  • token
    • 서버에서 그려준 폼에서만 값을 제출할 수 있도록 방어하는 역할
  • templates/polls/detail.html
    • form 태그 밑에 {% csrf_token %} 추가
<form action='#' method="post">
      {% csrf_token %} 
    <h1>{{question.question_text}}</h1>
 
      {% for choice in question.choice_set.all %} 
        <input type="radio" name="choice" id="choice {{ forloop.counter }}" value="{{choice.id}}">
        <label for="choice{{ forloop.counter }}">
            {{choice.choice_text}}
        </label>
        <br>
      {% endfor %} 

<input type="submit" value="Vote">
</form>

스크린샷 2023-11-02 오후 10 06 52

제출 내용 받기

polls/urls.py

from django.urls import path
from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

polls/views.py

from django.http import HttpResponse, HttpResponseRedirect
from .models import *
from django.shortcuts import render, get_object_or_404
from django.urls import reverse

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    context = {'questions': latest_question_list}
    return render(request, 'polls/index.html', context)

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    selected_choice = question.choice_set.get(pk=request.POST['choice'])  
        # choice는 detail.html 에서 지정한 name // value 값이 들어옴
    selected_choice.votes += 1
    selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))

templates/polls/detail.html

  • form 태그의 action 옵션에 뷰 지정
 <form action={% url 'polls:vote' question.id %} method="post"> 
     {% csrf_token %} 
    <h1>{{question.question_text}}</h1>
 
     {% for choice in question.choice_set.all %} 
        <input type="radio" name="choice" id="choice {{ forloop.counter }}" value="{{choice.id}}">
        <label for="choice{{ forloop.counter }}">
            {{choice.choice_text}}
        </label>
        <br>
     {% endfor %} 

<input type="submit" value="Vote">
</form>

스크린샷 2023-11-02 오후 10 15 36

스크린샷 2023-11-02 오후 10 15 24

  • 를 누르고 admin 페이지에서 vote 값을 확인해보면 1 증가한 것을 볼 수 있음

아무것도 안누르고 제출했을 때 오류 방어

polls/views.py

from django.http import HttpResponse, HttpResponseRedirect
from .models import *
from django.shortcuts import render, get_object_or_404
from django.urls import reverse

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    context = {'questions': latest_question_list}
    return render(request, 'polls/index.html', context)

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])  # detail.html 에서 지정한 name // value 값이 들어옴
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다'})
    else:   
        selected_choice.votes += 1
        selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))


  • error message 표시
    • templates/polls/detail.html 에 if문 추가
 <form action={% url 'polls:vote' question.id %} method="post"> 
     {% csrf_token %} 
    <h1>{{question.question_text}}</h1>
    
     {% if error_message %} 
        <p><strong>{{ error_message }}</strong></p>
     {% endif %} 
 
     {% for choice in question.choice_set.all %} 
        <input type="radio" name="choice" id="choice {{ forloop.counter }}" value="{{choice.id}}">
        <label for="choice{{ forloop.counter }}">
            {{choice.choice_text}}
        </label>
        <br>
     {% endfor %} 

<input type="submit" value="Vote">
</form>

스크린샷 2023-11-02 오후 10 20 51

급 render 함수에 대한 궁금증

django.shortcuts 의 render 라이브러리에 대해 설명해줄래?

django.shortcuts는 Django 웹 프레임워크에서 자주 사용되는 뷰 함수를 작성할 때 도움을 주는 유틸리티 라이브러리입니다. 이 라이브러리는 간단한 HTTP 응답 생성 및 다양한 렌더링 작업을 수행하는 데 사용됩니다. 가장 일반적으로 사용되는 함수 중 하나는 render 함수입니다.

render 함수는 HTML 템플릿을 사용하여 클라이언트에게 렌더링된 페이지를 제공하는 데 사용됩니다. 주로 뷰 함수 내에서 사용되며, 다음과 같은 인자를 받습니다:

  • request (필수): 요청 객체. 이 객체는 웹 브라우저로부터 받은 요청에 대한 정보를 포함하고 있습니다.
  • 템플릿 이름 (필수): 렌더링할 HTML 템플릿 파일의 경로 및 이름을 지정합니다. 예를 들어, “myapp/mytemplate.html”와 같이 템플릿 파일의 경로를 문자열로 제공합니다.
  • context (선택적): 템플릿에 전달할 데이터를 포함하는 딕셔너리. 이 데이터는 템플릿에서 변수로 사용됩니다.
  • content_type (선택적): 렌더링된 응답의 콘텐츠 유형을 나타내는 문자열. 기본값은 “text/html”입니다.
  • status (선택적): HTTP 응답 상태 코드를 나타내는 정수. 기본값은 200 (OK)입니다.
  • using (선택적): 사용할 템플릿 엔진을 지정하는 문자열. 기본적으로 Django는 설정에서 설정한 템플릿 엔진을 사용합니다.

render 함수는 템플릿을 렌더링하고, context로 전달된 데이터를 템플릿 변수와 함께 사용하여 최종 HTML 응답을 생성합니다. 이후 이 응답은 클라이언트에게 반환됩니다. 이렇게 함으로써 Django 개발자는 웹 애플리케이션의 다양한 페이지를 생성하고 데이터를 동적으로 표시하는 작업을 쉽게 수행할 수 있습니다.


html에서 보여지는 부분을 그려주는 함수라고 이해하면 될까?

네, render 함수는 HTML에서 보여지는 부분을 그려주는 함수로 이해할 수 있습니다. 이 함수는 Django 뷰에서 사용되며, HTML 템플릿을 렌더링하여 클라이언트에게 전달되는 웹 페이지의 내용을 생성합니다.

일반적으로 Django 웹 애플리케이션에서 다음과 같은 작업을 수행합니다:

  • 사용자의 요청을 처리하는 뷰 함수를 작성합니다.
  • 해당 뷰 함수에서 render 함수를 사용하여 HTML 템플릿을 렌더링합니다.
  • 템플릿에 데이터를 전달하여 동적으로 생성된 페이지를 만듭니다.
  • 이 페이지를 HTTP 응답으로 반환하여 클라이언트에게 보여집니다.

따라서 render 함수는 HTML 페이지의 내용을 그리고 사용자에게 보여주는 역할을 합니다. 이를 통해 Django 애플리케이션은 사용자 인터페이스를 동적으로 구성하고, 데이터베이스와 상호작용하여 웹 페이지를 생성할 수 있습니다.

7. 에러 방어하기

1) 중간에 선택 옵션이 없어지는 경우

  • ‘휴가 계획이 있나요?’ 질문에 ‘네’, ‘아니오’ 옵션이 있음
  • 사용자가 선택하려는 와중에 ‘네’ 옵션이 사라짐

polls/views.py

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])  
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다'})
    else:   
        selected_choice.votes += 1
        selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))
  • except 구문에 Choice.DoesNotExist 에러를 포함함으로써 에러 처리

2) 서버가 정확한 값을 전달해주지 않는 경우

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])  # detail.html 에서 지정한 name // value 값이 들어옴
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
    else:   
        selected_choice.votes += 1
        selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))
  • return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"}) 구문으로 id 값을 같이 출력해주어 에러 원인 확인

3) 같은 DB를 사용하는 두 서버에서 동시에 버튼을 누를 경우

  • vote 값이 1만 증가할 것
  • 1 증가하는 연산을 서버에서 하는 것이 아니라 DB에서 하도록!
    • F 함수 사용
    • F : 값을 DB에서 읽어서 사용해라
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])  # detail.html 에서 지정한 name // value 값이 들어옴
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
    else:   
        selected_choice.votes = F('votes') + 1
        selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))

8. 결과 조회 페이지

1) polls/views.py 에 view 추가
2) polls/urls.py 에 path 추가
3) polls/templates/polls 에 템플릿 추가
4) polls/views.py 에서 이 페이지로 이동하는 코드 추가


1)

def result(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/result.html', {'question': question})


2)

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
    path('<int:question_id>/result/', views.result, name='result'),
]


3)

<h1>{{question.question_text}}</h1><br>
 {% for choice in question.choice_set.all %} 
    <label>
        {{choice.choice_text}} -- {{choice.votes}}
    </label>
    <br>
 {% endfor %} 


4)

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])  # detail.html 에서 지정한 name // value 값이 들어옴
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
    else:   
        selected_choice.votes = F('votes') + 1
        selected_choice.save()
    return HttpResponseRedirect(reverse('polls:result', args=(question_id,)))
  • vote view에서 vote 버튼을 누르면 result view로 이동하도록!
  • 주의 인자를 전달하는 args=() 옵션을 추가할 때 꼭 뒤에 , 를 넣어주어야 함!


  • vote를 누르고 status code = 302
    • 302 : Redirect status code

스크린샷 2023-11-03 오전 12 34 49

9. admin 페이지 편집

polls/admin.py

편집 페이지 커스터마이징

from django.contrib import admin
from .models import *

admin.site.register(Choice)

class ChoiceInline(admin.TabularInline):
    # stackedInline : 수직
    # TabularInline : 수평
    model = Choice
    extra = 3       # 추가로 등록할 옵션의 개수

class QuestionAdmin(admin.ModelAdmin):
    # 섹션 나누기, 순서 바꾸기
    fieldsets = [
        ('질문 섹션', {'fields': ['question_text']}),
        ('생성일', {'fields': ['pub_date'], 'classes': ['collapse']}),  # 숨김처리
    ]
    # 읽기 전용 설정
    readonly_fields = ['pub_date']
    # inline 적용
    inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

스크린샷 2023-11-03 오전 12 48 25

목록 페이지 커스터마이징

from django.contrib import admin
from .models import *

# admin.site.register(Choice)

class ChoiceInline(admin.TabularInline):
    model = Choice
    extra = 3       

class QuestionAdmin(admin.ModelAdmin):
    # 섹션 나누기, 순서 바꾸기
    fieldsets = [
        ('질문 섹션', {'fields': ['question_text']}),
        ('생성일', {'fields': ['pub_date'], 'classes': ['collapse']}), 
    ]
    # 목록 편집
    list_display = ('question_text', 'pub_date', 'was_published_recently')
    readonly_fields = ['pub_date']
    inlines = [ChoiceInline]
    # filter 생성
    list_filter = ['pub_date']
    # 리스트로 표현할 수 없는 값 검색
    search_fields = ['question_text', 'choice__choice_text']

admin.site.register(Question, QuestionAdmin)

스크린샷 2023-11-03 오전 1 04 37

스크린샷 2023-11-03 오전 1 05 11

옵션으로도 검색 가능