2023/10/31

anso·2023년 10월 31일
0

TIL

목록 보기
9/20
post-thumbnail

뷰와 템플릿

  • Views : 장고의 DB를 읽어서 출력(렌더링)
  • Templates : html코드를 활용하여 데이터 출력
  1. 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]  # 가장 최근에 생성된 질문 5개
  context = {'first_question' : latest_question_list[0]}  
  return render(request, 'polls/index.html', context)  # request에 대해서 polls/index.html을 렌더링하고 인자로 context를 넘겨줌

def some_url(request):
    return HttpResponse("Some URL")
  1. polls에 templates/polls 디렉토리 생성하고 안에 index.html 생성

  2. polls/templates/polls/index.html

<ul>
  <li>{{first_question}}</li>
</ul>

# 중괄호를 두 번 씌운 이유 : 템플릿 엔진이나 서버 사이드 코드에서 해당 중괄호 안에 있는 변수 first_question의 값을 가져와 실제 데이터로 채우기 위해

출력 결과

템플릿에서 제어문 사용하기

  • {% %} 사용
{% if questions %}
<ul>
  {% for question in questions %}
    <li>{{question}}</li>
  {% endfor %}
</ul>

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

상세 페이지 만들기

  1. polls/urls.py 수정
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),  # url 뒤에 비어서 들어올 경우 views.index로 보내고 이름은 index로 설정
    path('<int:question_id>/', views.detail, name='detail'),  # url이 'polls/숫자'로 입력되면 views.detail로 전송하고 question_id로 전달
    path('some_url', views.some_url),
]
  1. 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]
  context = {'questions' : latest_question_list}
  return render(request, 'polls/index.html', context)

def some_url(request):
    return HttpResponse("Some URL")

# 추가
def detail(request, question_id):
   question = Question.objects.get(pk=question_id)  # 해당 question_id를 갖는 Question을 찾아 question에 저장
   return render(request, 'polls/detail.html', {'question':question})  # polls/detail.html로 렌더링
  1. templates/polls 안에 detail.html 생성

  2. polls/templates/polls/detail.html

<h1>{{question.question_text}}</h1>

<ul>
{% for choice in question.choice_set.all %}
  <li>{{choice.choice_text}}</li>
{% endfor %}
</ul>
  • ()붙이지 않도록 주의 (shell에서는 ()붙이기)

결과

상세 페이지 링크 추가

  1. polls/urls.py 수정
from django.urls import path
from . import views

# 해당 앱의 이름 설정.
app_name = 'polls'
urlpatterns = [
	path('', views.index, name='index'),
    path('some_url', views.some_url),
    path('<int:question_id>/', views.detail, name='detail')
]
  1. index.html 수정
{% if questions %}
<ul>
    {% for question in questions %}
        <li><a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a></li>
    {% endfor %}
</ul>

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

404 Error 처리

polls/views.py 수정

from django.http import HttpResponse
from .models import *
from django.shortcuts import render
# 추가
from django.http import Http404

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):
# 추가
     try:
       question = Question.objects.get(pk=question_id)
     except Question.DoesNotExist:
        raise Http404("Question does not exist")
   
    return render(request, 'polls/detail.html', {'question':question})

def some_url(request):
    return HttpResponse("Some URL")

더 간편한 방법 - get_object_or_404

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

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 some_url(request):
    return HttpResponse("Some URL")

Forms 처리

투표 기능 구현

  1. Choice 가능한 폼으로 변경
    polls/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{{ forloop.counter}}" value="{{choice.id}}">
        <label for="choice{{ forloop.counter }}">
            {{ choice.choice_text }}
        </label>
        <br>
    {% endfor %}

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

  1. 위 상태로 Vote 버튼을 누르면 token을 지정해주지 않았기 때문에 403 error 발생 → detail.html에 token 만들어주어야 함
<form action='#' method='post'>
  <!-- token 추가 -->
    {% 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>
  1. 선택에 따른 투표 수 증가 기능 구현
    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'),
    
    # vote에 대한 경로 추가
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

polls/views.py

from django.http import HttpResponse
from .models import *
from django.shortcuts import render, get_object_or_404
# from django.http import Http404

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 some_url(request):
	return HttpResponse("Some URL !!")

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

# vote 추가
def vote(request, question_id):
	question = get_object_or_404(Question, pk=question_id)
	selected_choice = question.choice_set.get(pk=request.POST['choice'])
    selected_choice.votes += 1
    selected_choice.save()
    return HttpResponseRedirect(reverse('polls:index'))

polls/templates/polls/detail.html

<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>

  1. 아무 선택을 하지 않고 Vote 버튼을 누르면 에러 발생 → 예외처리(방어코드) 추가
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 some_url(request):
    return HttpResponse("Some URL!!")

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    # selected_choice가 있으면 요청을 받고, 없으면 error message 출력
    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'))

에러 방어하기

  1. 투표를 하려고 하는데 서버에서 해당 선택이 DB에서 사라진 경우 : Choice.DoesNotExist

  2. 투표 후 value값을 넘겨주는데 해당 id를 가진 선택이 없는 경우 : id값 확인 필요
    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': f"선택이 없습니다. id={request.POST['choice']}"})
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:index'))
  1. 여러 사람이 동시에 서버에 접속하여 같은 DB에 대해 수행하는 경우 : 투표 수 증가 연산을 서버에서 하지 말고 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'])
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
    else:
    	# F : DB에서 읽기
        selected_choice.votes = F('votes') + 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:index'))

투표 결과 조회 페이지

  1. polls/urls.py에서 result경로 추가
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'),
    path('<int:question_id>/result/', views.result, name='result'),
]
  1. polls/templates/polls/result.html 파일 생성 후 작성
<h1>{{question.question_text}}</h1><br>
{% for choice in question.choice_set.all %}

  <label>
    {{ choice.choice_text }} -- {{choice.votes}}
  </label>
  <br>
{% endfor %}
  1. polls/views.py에 result뷰 추가하고 vote 후 result페이지로 redirect하도록 수정
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': f"선택이 없습니다. id={request.POST['choice']}"})
    else:
        selected_choice.votes = F('votes') + 1
        selected_choice.save()
        # 수정
        return HttpResponseRedirect(reverse('polls:result', args=(question_id,)))

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

Django Admin 편집 페이지 커스터마이징

1.polls/admin.py

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']}),
        # ['collapse'] : 숨김 처리
        ('생성일', {'fields' : ['pub_date'], 'classes' : ['collapse']}),
    ]
    readonly_fields = ['pub_date']
    inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

2.polls/admin.py

from django.contrib import admin
from .models import *

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]
    list_filter = ['pub_date']
    search_fields = ['question_text', 'choice__choice_text']

admin.site.register(Question, QuestionAdmin)

polls/models.py

from django.db import models
from django.utils import timezone
import datetime
from django.contrib import admin

class Question(models.Model):
    # verbose_name 옵션에 필드의 칼럼명 입력
    question_text = models.CharField(max_length=200, verbose_name="질문")
    pub_date = models.DateTimeField(auto_now_add=True, verbose_name="생성일")

    # @admin.display 어노테이션으로 함수의 칼럼명 입력 
    @admin.display(boolean=True, description='최근생성(하루기준)')
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

    def __str__(self):
        if self.was_published_recently():
            new_badge = "New"
        else:
            new_badge = ''
        return f'{new_badge} 제목: {self.question_text}, 날짜: {self.pub_date}'
    

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200) 
    votes = models.IntegerField(default=0)
    def __str__(self):
        return f'[{self.question.question_text}]{self.choice_text}'

업로드중..

0개의 댓글