장고에서는 모델(Question, Choice)를 통해 DB 정보를 저장하고 읽어오면,
- View : 앞서 읽어온 정보를 활용!
- Template : html을 활용하여 데이터를 웹 사이트에 잘 표시하도록 도와주는 도구!
template활용을 위해선, 만들어 놓은 app에 새 폴더를 만들고 views.py에 있는 함수명과 동일한 이름을 가진 html 파일을 생성해 해당 내용을 작성해주고, views.py에는 return에 해당 html을 작성해 처리해준다.
💡 Template에서의 제어문 활용
html에선 반복문을 다음과 같이 %를 활용하여 나타낸다.
<ul> <!-- 반복문, 조건문 등의 제어문은 중괄호 1개 --> {% for question in questions %} <!-- 변수 내용 html에 입력할 경우 중괄호 2개 --> <li>{{question}}</li> {% endfor %} </ul> {% else %} <p>no questions</p> {% endif %}
💡 detail page 만들기
웹 상에서 클릭 후 연결되는 페이지 만드는 방법!
- detail.html template을 만들어주고 views.py에서 detail함수를 생성해준다.
- app/숫자 에 형태로 path가 들어오는 경우 처리해주는 코드를 urls.py에서 작성해준다.
< 예시는 다음과 같다 ><!-- detail.html --> <h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>
# app/views.py # 특정 Question 오브젝트를 가져오기 위한 과정이 필요 def detail(request, question_id): # Question모델에서 id 값이 detail 뷰의 인자로 전달된 question_id와 일치하는 객체가져와 변수로 저장 question = Question.objects.get(pk=question_id) return render(request, 'polls/detail.html', {'question': question}) # app/urls.py urlpatterns = [ path('', views.index, name='index'), path('some_url', views.some_url), path('<int:question_id>/', views.detail, name='detail'), ]
💡 detail 페이지에 링크 다는 방법
앞서 만든 detail 페이지에 링크 다는 방법!
즉, template에서 a태그(url태그)를 통해서 링크를 거는 방법이다.<!-- templates/index.html --> {% if questions %} <ul> {% for question in questions %} <!-- url app_name:name --> <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> {% endfor %} <ul> {% else %} <p>no questions</p> {% endif %}
# app_name/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'), ]
만일, app_name 을 수정한다면 다음과 같이 수정한다.
urls.py
app_name = 'questions'
path(, name = 'question_detail')index.html
url 불러올 때 : url 'questions:qustion_detail'
간혹 url 이동 시 404 page not found를 발견할 수 있다.
이를 처리하는 방법!`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 클래스를 활용해 목록이 아닌 선택 가능한 form 형태로 만드는 방법
마찬가지로 appname/views.py , appname/urls.py, template/detail.html에서 숫정이 필요하다.<!-- 폼 생성 --> <form action = "{% url 'polls:vote' question.id %}" method = 'post'> <!-- token : 서버에서 그려준 폼에서만 값 제출 가능토록 함 (방어용) --> {% 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>
# appname/views.py
# Form에서 views.vote 처리해주기
from django.http import HttpResponseRedirect
from django.db.models import F
from django.urls import reverse
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
# 사용자가 입력한 값: Form에서 입력한 input의 name = choice
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': '선택이 없습니다.'})
# 제출 버튼누르려는 과정에서 삭제하는 문제 시
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
# 아닌 경우 DB에 해당 결과 저장
else:
# 같은 DB 공유하지만 서버가 다를 때 동시 진행 시 VOTES 수에 문제 발생
# A서버에서도 Votes = 1
# B서버에서도 Votes = 1
# 아래와 같이 문제 발생
selected_choice.votes += 1
# 아래로 수정 必
selected_choice.votes = F('votes') + 1
selected_choice.save()
# 모든 처리 완료 후 유저에게 페이지 보내기 (전체목록으로 다시 돌아가기)
return HttpResponseRedirect(reverse('polls:index'))
# appname/urls.py에 views.note에서 입력 처리하는 path 추가해주기
path('<int:question_id>/vote/', views.vote, name='vote')
views.py에 result 함수 생성!
# appname/views.py from django.shortcuts import get_object_or_404, render def vote(request, question_id): ... else: selected_choice.votes = F('votes') + 1 selected_choice.save() **return HttpResponseRedirect(reverse('polls:result', args=(question.id,)))** # form 결과 페이지 생성 def result(request, question_id): question = get_object_or_404(Question, pk = question_id) return render(request, 'polls/result.html', {'question':question}) # urls.py에 result 페이지 path 추가 path('<int:question_id>/result/', views.result, name='result')
<!-- appname/result.html 생성 --> <!-- 각 choice 별 답변과 몇개가 선택되었는지 label로 결과 띄워주기 --> <h1>{{ question.question_text }}</h1><br> {% for choice in question.choice_set.all %} <label> {{ choice.choice_text }} -- {{ choice.votes }} </label> <br> {% endfor %}
- *args : Python 함수에서 사용되는 파라미터(parameter)로, 함수가 호출되고 여러 개의 인자(argument)를 입력받는 상황에서 유연성을 높여주는 기능을 제공합니다. (리스트 활용)
- **kwargs : Python 함수에서 사용되는 파라미터(parameter)로, kwargs에서 분리된 key값과 value값을 원하는 형식에 넣어 반복문으로 출력합니다. (딕셔너리 활용)
django admin에 모델을 추가하기만 하면 모델 내용에 대한 CRUD가 가능한 페이지를 생성할 수 있는 장점이 있다. (CRUD : CREATE, READ, UPDATE, DELETE)
# appname/admin.py # ADMIN 편집 페이지 # django admin에 모델을 추가하기만 하면 모델 내용에 대해 # CRUD가 가능한 페이지를 생성할 수 있는 장점이 있다. from .models import Choice, Question admin.site.register(Choice) # Choice가 Question내에서 작동하도록 생성 # StackedInline : 각 choice 결과가 상하로 표시 # TabularInline : 각 choice 결과가 좌우로 표시 class ChoiceInline(admin.TabularInline): model = Choice # default로 3개 추가해서 표현 extra = 3 class QuestionAdmin(admin.ModelAdmin): fieldsets = [ ('질문 섹션', {'fields': ['question_text']}), # 숨기고싶은 필드에 classes 지정 ('생성일', {'fields': ['pub_date'], 'classes': ['collapse']}), ] # 편집 시 수정 X readonly_fields = ['pub_date'] # 앞서 만든 ChoiceInline 추가해 question, choice를 함께 편집가능 inlines = [ChoiceInline] admin.site.register(Question, QuestionAdmin)
model에서 한 컬럼은 제목, 한 컬럼은 날짜가 나오도록 변경해주는 방법
admin.py와 model.py의 수정작업이 필요하다.
# appname/admin.py
from django.contrib import admin
from .models import Choice, Question
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도 가능!
# (question_text는 question옵션만 가능), choice__choice_text로 choice옵션도 검색으로 찾기
search_fields = ['question_text', 'choice__choice_text']
admin.site.register(Question, QuestionAdmin)
# appname/models.py
# 보여지는 컬럼명 수정 원하면 model에서 변경 필요!!
import datetime
from django.db import models
from django.utils import timezone
from django.contrib import admin
class Question(models.Model):
# verbose_name 으로 컬럼명 지정
question_text = models.CharField(max_length=200, verbose='질문')
pub_date = models.DateTimeField(auto_now_add=True, verbose='생성일')
# 모델에 정의된 field가 아니고 모듈이기에 admin decorator해주기
# boolean = True : 아이콘 생성 가능
@admin.display(boolean=True, description='최근생성(하루기준)')
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
def __str__(self):
return f'제목: {self.question_text}, 날짜: {self.pub_date}
어제에 이어 오늘도 장고에 대해 알아보았다.
특히 오늘은 뷰, 탬플릿, 커스터마이징 등 어제 학습한 부분에 대해 좀 더 깔끔하게 웹사이트를 구축하는 방식에 대해 살펴보았고, 이를 통해 url, model,views 등 사용방식과 연관성에 대해서도 알게 되었다.
확실히 어제보다 더 수월하게 배워가는 느낌..!
그러나, 주말에 시간투자를 좀 해서 이번 주차를 무조건 한번 다시 복습 해야할 듯 하다....