일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- redshift
- yarn
- docker
- 데이터파이프라인
- 웹 크롤링
- airflow.cfg
- 데이터 웨어하우스
- airflow
- ETL
- Django
- Serializer
- 웹 스크래핑
- AWS
- Django Rest Framework(DRF)
- truncate
- 데이터마트
- dag 작성
- Kafka
- dag
- 컨테이너 삭제
- docker hub
- snowflake
- 데이터레이크
- selenium
- ELT
- 알고리즘
- docker-compose
- Hive
- spark
- SQL
- Today
- Total
개발 기록장
02. Python Django 프레임웍을 사용해서 API 서버 만들기(2) 본문
학습 주제: Django, 뷰(Views)와 템플릿(Templates), 상세(detail) 페이지, 404 에러 처리, *args/**kwargs, Admin 커스텀
뷰(Views)와 템플릿(Templates)
- 뷰(Views)
- 뷰(Views) - Request에 대한 CRUD(Create, Read, Update, Delete) 처리
- polls/views.py
- context: polls/index.html로 데이터 전달
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)
- Django 템플릿 언어를 사용해 데이터를 처리 및 HTML 반환
- polls/templates/polls/index.html
- {{first_question}}: polls/views.py로부터 받아온 데이터 전달
<ul>
<li>{{first_question}}</li>
<ul>
템플릿(Templates) 제어문
: 템플릿에서 프로그래밍 언어의 제어문과 비슷한 역할을 하는 문법
- polls/templates/polls/index.html
- {% if questions %} ~1~ {%else%} ~2~ {% endif %}: question이 존재(참이면)하면 1을 수행, 아니면 2 수행
- {%for question in questions %} ~ {% endfor%}: questions에서 question 변수로 객체를 하나씩 뽑는 반복문
{% if questions %}
<ul>
{% for question in questions %}
<li>{{question}}</li>
{% endfor %}
</ul>
{% else %}
<p>no questions</p>
{% endif %}
- polls/views.py
- questions 리스트 모두 전달
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)
상세(detail) 페이지: Read
- polls/views.py
- detail(): polls/detail.html 및 question_id에 맞는 객체 반환
def detail(request, question_id):
question = Question.objects.get(pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
- polls/urls.py
- ex) http://127.0.0.1:8000/polls/1 ...
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'), #추가
]
- polls/templates/polls/detail.html
- {% for choice in question.choice_set.all %} ~ {% endfor %}: question에 해당하는 선택지 나열
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
- 상세(detail) 페이지로 링크 추가
- polls/index.html -> polls/detail.html
- polls/templates/polls/index.html
- {%url 'polls:detail' question.id%}: url을 통해 polls앱의 detail 함수로 question.id 전달
{% 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 question</p>
{%endif%}
에러 처리
: 에러를 완벽히 처리하지 않으면 웹 사이트가 정상적으로 작동하지 않거나, 보안상의 이슈가 생길 수 있다.
- 404 에러 처리
- 404 에러(Page not found): Client가 Server에 요청한 페이지가 없을 때(존재하지 않는 URL)
- get_object_or_404(): 해당 객체가 존재하지 않을 때 Http404 에러 발생
from models.py import *
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})
- 에러 방어 1
- radio 버튼을 클릭했을때 정해진 value가 아닌 다른 value가 넘어왔을 때 오류 처리
- 'error_message': f"선택이 없습니다. id = {request.POST['choice']}": 오류 메시지와 함께 잘못 넘어온 값 확인
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,))
- 에러 방어 2
- 여러 개의 서버에서 vote에 대한 연산을 수행하고 동시에 DB에 저장할 때 저장 값 오류 발생(+2가 되어야 하지만 +1됨)
- 서버는 여러 개지만 DB는 하나이므로 DB에서 vote 연산 수행
- F('votes'): 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:
# A서버: votes = 1
# B서버: votes = 1
# DB: votes = 2 되어야 정상인데 votes = 1로 저장 오류
selected_choice.votes = F('votes') + 1 #DB 값으로 연산
selected_choice.save()
return HttpResponseRedirect(reverse('polls:result', args = (question_id,)))
폼(Forms) : Create Or Update
: 데이터 수집 및 수정
- polls/views.py
- 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': '선택이 없습니다.'})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
- polls/urls.py
- ex) http://127.0.0.1:8000/polls/1/vote/
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
- {%csrf_token%}: Django 템플릿 상에서 CSRF 공격을 방어하기 위한 장치
<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>
결과(resul) 조회 페이지: Read
- polls/views.py
- result(): polls/result.html 및 question_id에 맞는 객체 반환(; 투표 결과 확인)
from django.shortcuts import get_object_or_404, render
def result(request, question_id):
question = get_object_or_404(Question, pk = question_id)
return render(request, 'polls/result.html', {'question':question})
- polls/templates/polls/result.html
- {{choice.choice_text}} -- {{choice.votes}}: ex) 커피 -- 2, 녹차 -- 1
<h1>{{question.question_text}}</h1></br>
{% for choice in question.choice_set.all %}
<label>
{{choice.choice_text}} -- {{choice.votes}}
</label>
</br>
{% endfor %}
- polls/urls.py
- ex) http://127.0.0.1:8000/polls/1/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>/result/',views.result, name = 'result'), #추가
path('<int:question_id>/vote/', views.vote, name = 'vote'),
]
*args 와 **kwargs
: 함수 인자 전달의 편의성을 높여줌.
- *args(:arguments)
- 가변인자로, 들어오는 인자의 개수가 정해져 있지 않다.
- 여러 개의 인자(argument)를 입력받는 상황에서 유연성을 높여줌.
- 예를 들어, def example(1, 2, 3, 4)와 def example(-1, 0, 1)의 인자의 개수는 다르다.
- 들어오는 인자의 개수가 다르더라도 같은 방식으로 함수 처리
def solution(param0):
#함수 sample에 인자 4개 전달(; 1,2,3,4 )
print(sample(1,2,3,4))
#함수 sample에 인자 3개 전달(; -1,0,1 )
print(sample(-1,0,1))
#sample함수: *args로 인자 입력
def sample(*args):
#입력 받은 인자의 합 return 함수
answer = 0
for i in args:
answer += i
return answer
- **kwargs(keyword argument)
- 키워드 인자(keyword argument): 인자의 값이 key와 value로 구분
- 함수가 호출될 때, 여러 개의 키워드 인자(keyword argument)를 받을 수 있음.
- 예를 들어, def example({'name': John, 'age': 30}) 같은 경우
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):
# value값이 가장 큰 key값 리턴
max_val = float('-inf')
max_key = None
for key, val in kwargs.items():
if val > max_val:
max_key = key
max_val = val
return max_key
Django Admin 페이지 커스터마이징
: 데이터를 CRUD할 수 있는 Admin 페이지를 편의를 위해 커스터마이징
- 편집 페이지 커스터마이징
- 질문과 대답 편집 페이지
- polls/admin.py
- class ChoiceInline():
TabularInline: 가로로 Choice 정렬 <-> StackedInline: 세로로 Choice 정렬
extra: Choice 입력창 extra 개로 늘림
- class QuestionAdmin():
fieldsets/'생성일'/classes -> collapse: '생성일' 숨기기(show/hide)
readonly_fields: 해당 객체 읽기 전용
inlines: 질문 클래스 안에 선택(대답) 클래스 넣기
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']}),
]
readonly_fields = ['pub_date'] #읽기 전용
inlines = [ChoiceInline] #질문안에 바로 선택(대답) 확인
admin.site.register(Question, QuestionAdmin)
- 목록 페이지 커스터마이징
- 질문 목록 페이지
- polls/models.py
- verbose_name: 편의를 위한 이름 지정
- @admin.display(boolean = True, description =..): 클래스가 아닌 함수에 대하여 admin display 지정
import datetime
from django.db import models
from django.utils import timezone
from django.contrib import admin
class Question(models.Model):
question_text = models.CharField(max_length = 200, verbose_name = "질문")
pub_data = models.DateTimeField(auto_now_add= True, verbose_name = "생성일")
@admin.display(boolean = True, description = "최근생성(하루기준)") #추가
def was_published_recently(self):
return self.pub_data >= 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_data}'
- polls/admin.py
- class QuestionAdmin():
list_filter: 장고 기본 필터
search_fields: 질문 제목 또는 선택(대답)으로 검색
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_data'], 'classes': ['collapse']}),
]
list_display = ('question_text','pub_data', 'was_published_recently')
readonly_fields = ['pub_data']
inlines = [ChoiceInline]
list_filter = ['pub_data'] # 장고 기본 필터
search_fields = ['question_text', 'choice__choice_text'] #질문, 선택(대답) 검색
admin.site.register(Question, QuestionAdmin)
공부하며 느낀점
장고를 사용하면서 Admin은 기본으로만 사용하고 커스텀 해 볼 생각을 못 했었는데, Admin 커스텀에 다양한 기능이 존재한다는 사실을 알게 되어 유익했다.
'데브코스(DE) > 장고 활용한 API 서버 제작' 카테고리의 다른 글
05. Python Django 프레임웍을 사용해서 API 서버 만들기(5) (0) | 2024.04.12 |
---|---|
04. Python Django 프레임웍을 사용해서 API 서버 만들기(4) (0) | 2024.04.11 |
03. Python Django 프레임웍을 사용해서 API 서버 만들기(3) (1) | 2024.04.10 |
01. Python Django 프레임웍을 사용해서 API 서버 만들기(1) (0) | 2024.04.08 |