첫번째 강의인 view & templates를 듣다 보니, 전체적인 구조를 먼저 알아야겠단 생각이 들었음
그러다 참고한 사이트가 https://djangojeng-e.github.io/2020/04/12/Writing-your-first-Django-app-part1-4%ED%8E%B8/
현재 우리가 만들고 있는 polls 앱의 디렉토리 구조
Django의 MVT 모델
- 사용자가 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 |