본문 바로가기

dev course - DE/TIL

[데브코스] TIL 12일차

첫번째 강의인 view & templates를 듣다 보니, 전체적인 구조를 먼저 알아야겠단 생각이 들었음
그러다 참고한 사이트가 https://djangojeng-e.github.io/2020/04/12/Writing-your-first-Django-app-part1-4%ED%8E%B8/

 

 

현재 우리가 만들고 있는 polls 앱의 디렉토리 구조

 

Django의 MVT 모델

[출처] https://blog.stepskochi.com/category/python/

  • 사용자가 URL을 통해 View에 요청 → View는 요청을 처리해 사용자에게 결과물을 반환
  • View ↔︎ Model
    : 데이터베이스에서 데이터를 가져오고 저장
  • View ↔︎ Templates
    : View는 Templates에게 처리 결과를 그려줌. View는 Templates를 사용자에게 다시 반환

 

Q. 그렇다면 Model, View, Templates 중 어떤걸 먼저 작성해야할까?

  • 논쟁의 여지가 있다만, 본인 논리 구조에 편한걸 선택하면 된다. 결과물이 좋으면 순서는 장땡
  • 공식 문서에서는 View 부터 시작함
  • View 작성 순서
    1) polls/views.py에 view 작성
    2) 요청을 전달할 polls/urls.py 생성 및 작성(이 url은 작성된 view를 요청함)
    3) 프로젝트 url과 polls/urls.py를 연결

 

 

Views & Templates

  • polls/views.py
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)
  • Django shell
>>> from polls.models import *
>>> Question.objects.order_by('-pub_date')[:5]
>>> print(Question.objects.order_by('-pub_date')[:5].query)
  • polls/templates/polls/index.html
<ul>
    <li>{{first question}}</li>
</ul>

 

  • render()
    - Django의 내장 함수 중 하나로, HTTP 요청을 받아 해당 요청에 대해 원하는 템플릿 파일을 렌더링하여 응답하는 기능을 가지고 있음
    - 보통 View에서 사용되며 첫 번째 인자로 request 객체, 두 번째 인자로 템플릿 파일의 경로, 세 번째 인자로 Context 변수를 입력 받음

 

 

 

Templates - 제어문 사용하기

  • polls/templates/polls/index.html
{% if questions %}
<ul>
    {% for question in questions %}
        <li>{{question}}</li>
    {% endfor %}
</ul>
{% else %}
    <p>no questions</p>
{% endif %}
  • polls/views.py
from .models import *
from django.shortscuts import render

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

 

 

 

페이지 만들기 - detail 페이지

  • polls/views.py
def detail(request, question_id):
    question = Question.objects.get(pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})
  • polls/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('some_url', views.some_url),
    path('<int:question_id>/', views.detail, name='detail'),
]
  • 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>

 

 

 

페이지 만들기 - detail 페이지에 링크 추가하기

  • 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'),
]
  • polls/templates/polls/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 처리하기

404 Error - 사용자가 잘못된 요청을 했을 때 보여줌

  • polls/views.py
from models.py import *
from django.http import HttpResponse
from django.http import Http404
from django.shortcuts import render , get_object_or_404

...
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})

 

 

 

폼 만들기 - 만들어둔 Question, Choice를 통해 사용자에게 투표를 받아보자

  • 일단, 현재 상황으로 투표를 클릭하면 다음과 같은 에러 메시지가 뜸

  • detail.html에 다음을 추가
{% csrf_token %}
  • 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'))
  • 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/templates/polls/detail.html
<!DOCTYPE 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 %} <!--html상에선 .all()이 아닌 .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>

 

 

 

에러 방어하기

  • polls/views.py - Choice.DoesNotExist
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:results', args=(question.id,)))

 

  • polls/views.py - F : db에 있는 데이터를 불러와 바꿔줌. 동시에 접속해 vote시 발생하는 오류 해결
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:
        # 서버가 둘일때
        # A 서버에서도 Votes = 0
        # B 서버에서도 Votes = 0
        # 동시에 누르면, 선택은 두명이 했는데 총 1이 되어버림
        
        selected_choice.votes = F('votes') + 1 # 이럴땐 db에서 +1하면 됨
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:index'))

 

 

 

 

결과(result) 조회 페이지

  • polls/views.py
from django.shortcuts import render, get_object_or_404

...
def vote(request, question_id):
...
    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})
  • polls/urls.py
from django.urls import path
from . import views

app_name = 'polls'
urlpatterns = [
...
    path('<int:question_id>/result', views.result, name='result')
]
  • polls/templates/polls/result.html
<!DOCTYPE html>

<h1>{{question.question_text}}</h1>
{% for choice in question.choice_set.all %}

    <label>
        {{choice.choice_text}} -- {{choice.votes}}
    </label>
    <br>
{% endfor %}

 

 

 

*args, **kwargs

  • *args : python 함수에서 사용되는 파라미터로, 함수가 호출되고 여러 개의 인자를 입력받는 상황에서 유연성을 높여주는 기능을 제공함
def solution(param0):
    #함수 sample에 1,2,3,4 라는 4가지 인자가 전달됩니다.
    print(sample(1,2,3,4))
    
    #함수 sample에 -1,0,1 이라는 3가지 인자가 전달됩니다.
    print(sample(-1,0,1))

#sample함수는 *args를 사용하여 인자를 입력받아야 합니다. 
def sample(*args):
    # 이 아래에 입력받은 인자들의 합을 리턴하는 코드를 작성해주세요.
    return sum(args)
  • **kwargs : python 함수에서 사용되는 파라미터로, 함수가 호출될 때 여러개의 키워드 인자를 받을 수 있도록함. 키워드 인자란 key와 value값으로 구분되어 있는 경우
def solution(param0):
    #함수 sample에 4가지 산의 이름을 key값으로, 해당 산들의 해발고도를 value값으로 가지는 딕셔너리가 전달됩니다. 
    print(sample({'백두산': 2774, '한라산': 1950, '지리산': 1915, '설악산': 1708}))
    
    #함수 sample에 3가지 과목이름을 key값으로, 해당 과목들의 성적을 value값으로 가지는 딕셔너리가 전달됩니다. 
    print(sample({'수학': 80, '영어': 90, '국어': 85}))

# sample함수는 **kwargs를 사용하여 키워드 인자를 입력받아야 합니다
def sample(**kwargs):
    max_val = float('-inf')
    answer = ''
    for key,value in kwargs.items():
        if value > max_val:
            max_val = value
            answer = key
    # 이 아래에 value값이 가장 큰 key값을 리턴하는 코드를 작성해주세요.
    return answer

 

 

 

Django admin - 편집 페이지 커스터마이징

  • polls/admin.py
from django.contrib import admin
from .models import Question, Choice

# Register your models here.
admin.site.register(Choice)

# CRUD Create Read Update Delete

class ChoiceInline(admin.TabularInline): # StackedInline : 상하, TabularInline : 좌우
    model = Choice
    extra = 3

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        ('질문 섹션', {'fields': ['question_text']}),
        ('생성일', {'fields': ['pub_date'],'classes':['collapse']}) # 숨기기 기능. 'classes':['collapse']
    ]
    readonly_fields = ['pub_date'] # 편집시 수정하지 않도록
    inlines = [ChoiceInline] # Question, Choice 한번에 편집할 수 있도록

# Register your models here.   
admin.site.register(Question,QuestionAdmin)

 

 

 

Django admin - 목록 페이지 커스터마이징

  • polls/admin.py
from django.contrib import admin
from .models import Question, Choice

# CRUD Create Read Update Delete

class ChoiceInline(admin.TabularInline): # StackedInline : 상하, TabularInline : 좌우
    model = Choice
    extra = 3

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        ('질문 섹션', {'fields': ['question_text']}),
        ('생성일', {'fields': ['pub_date'],'classes':['collapse']}) # 숨기기 기능. 'classes':['collapse']
    ]
    list_display = ('question_text', 'pub_date','was_published_recently') # 
    readonly_fields = ['pub_date'] # 편집시 수정하지 않도록
    inlines = [ChoiceInline] # Question, Choice 한번에 편집할 수 있도록
    list_filter = ['pub_date']
    search_fields = ['question_text','choice__choice_text']

# Register your models here.   
admin.site.register(Question,QuestionAdmin)

 

  • polls/models.py
from django.db import models
from django.utils import timezone
from django.contrib import admin
import datetime

# 질문 : 여름에 놀러간다면 어디 갈래?
# 산
# 강
# 바다
# 도심 호캉스


class Question(models.Model): # models.Model 상속
    question_text = models.CharField(max_length=200,verbose_name='질문') # verbose_name='' 은 필드명을 바꿔줌
    pub_date = models.DateTimeField(auto_now_add=True,verbose_name='생성일')
    
    @admin.display(boolean=True,description='최근생성(하루기준)') # was_published_recently는 field가 아니기 때문에 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}'

 

'dev course - DE > TIL' 카테고리의 다른 글

[데브코스] TIL 14일차  (0) 2024.04.13
[데브코스] TIL 13일차  (0) 2024.04.12
[데브코스] TIL 11일차  (0) 2024.04.08
[데브코스] TIL 10일차  (0) 2024.04.05
[데브코스] TIL 9일차  (0) 2024.04.04