구조 - user, web server, app server
-> 여태까지 했던 것처럼 단순히 runserver 하는 것은 적합하지 x
1. static file들을 한 곳에 모아야 함
settings.py에서 STATIC_URL밑에 아래 코드 작성
STATIC_ROOT = BASE_DIR / 'staticfiles'
후 console창에서 python manage.py collectstatic 입력.
2. WSGI 수정
WSGI : 웹서버와 웹 어플리케이션(장고) 직접적으로 통신하지 못하기 때문에 WSGI가 중재자 역할 해준다!
wsgi 터미널창에 깔아주고
$ uwsgi \
--http :80 : http 80 서비스로 구동할거다
--home /나의 pwd/venv/ :home directory 설정
--chdir /나의 pwd/ : 자식 디렉토리 설정
--static-map /static=/나의 pwd/staticfiles/ \
-w MBIT.wsgi
터미널 창에 이렇게 입력해주면 더이상 터미널 창에 runserver 입력안해도 port 80으로 접속하면 실행됨!
[uwsgi]
chdir = /workspace/mbittest/
module = MBIT.wsgi:application
home = /workspace/mbittest/venv/
uid = root
gid = root
http = :80
enable-threads = true
master = true
vacuum = true
pidfile = /workspace/mbittest/tmp/MBIT.pid
logto = /workspace/mbittest/log/uwsgi/@(exec://date +%%Y-%%m-%%d).log
log-reopen = true
static-map = /static=/workspace/mbittest/staticfiles/
- chdir : django project의 manage.py가 존재하는 경로를 지정해줌
- module : wsgi 파일을 지정해줌
- home : 가상환경의 경로를 지정해줌
- uid, gid : uWSGI를 실행할 사용자 및 사용자그룹을 지정해줌
- http : http 프로토콜을 통해서 요청을 받으며 포트 번호를 정해줌
- enable-threads : 스레드 사용 여부를 결정함
- master : 마스터 프로세스 사용 여부를 결정함
- vacuum : 실행 시 자동 생성되는 파일들을 삭제해줌
- pidfile : 실행되는 프로세스의 id 값을 담고 있는 파일, pidfile의 경로를 지정해줌
- logto : 로그파일을 작성할 위치를 설정함
- log-reopen : 재시작할 시 로그를 다시 열어줌
uwsgi.ini에 위와 같이 입력함으로써 긴 명령어 필요없이 모든 옵션이 들어있는 파일로 연다. ( 해당 옵션들을 절대 경로로 적어주다보니 명령어 하나의 양이 적지 않다는 문제를 해결 )
3. 웹서버 : HTTP를 통해 웹 브라우저에서 요청하는 HTML 문서나 object(이미지 파일 등)을 전송해주는 서비스 프로그램
-> 대표적으로 NGINX, apache가 있다
4. 프로젝트 개선하기
Redirect
웹 브라우저: 요청을 보내고 응답을 받은 뒤에도 보낸 요청이 무엇인지 기억하고 있는다
- > 유저가 새로고침하면 기억하고 있던 요청을 다시 보냄.
- > 결고 페이지에서 새로고침하면 설문데이터가 서버로 다시 전달 됨 -> 이를 방지하기 위해 redirect 사용해야한다
Redirect란?
-> 브라우저에게 어떤 특정한 url로 재요청을 하라고 명령하는 것
설문 제출 요청 후에 결과 페이지를 받기만 하는 요청이 한 번 더 이루어지기 때문에 더 이상 처음의 설문 제출 요청은 웹 브라우저에 남지 않게됨.( 웹 브라우저는 이전 상태는 전혀 기억하지 못하게 되어있기 때문에)
[이전] : 설문제출 요청 -> 처리 후 결과페이지 렌더링
[이후] : 설문 제출 요청 -> 처리 -> 결과 페이지 요청 -> 결과 페이지 렌더링
A라는 유저가 처음 submit이라는 B 페이지를 들어가고 result라는 C페이지로 redirect
def submit(request): # 10개의 문항을 돌면서 각 문항에서 선택한 항목이 어떤 개발자 유형에 속하는지 count한 후 문항 다 돌았을 때 어떤 개발자유형이 가장 많이 나왔는지 알아내어 최종적으로 적합한 개발자 유형 알려주는 시스템
# 문항수
N=Question.objects.count()
# 개발자 유형 수
K=Developer.objects.count()
counter=[0]*(K+1)
for i in range(1, N+1):
developer_id=int(request.POST[f'question-{i}'][0])
counter[developer_id]+=1
# 최고점 개발 유형
best_developer_id=max(range(1, K+1), key=lambda id: counter[id])
best_developer=Developer.objects.get(pk=best_developer_id) # 객체 가져옴
best_developer.count+=1 #객체 요소 count에 접근
best_developer.save()
context={
'developer':best_developer,
'counter':counter
}
return redirect(f'result/{best_developer_id}')
def result(request):
return render(request, 'result.html', context=context)
이런식으로 submit return에 redirect하고 result view 만들어준다
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.index), # views.py의 index라는 함수에 매핑시켜주겠다
path('form/', views.form),
path('submit/', views.submit),
path('result/<int:developer_id>', views.result),
]
urls.py에도 위와 같이 추가해준다
->submit함수에서 계산된 best_developer_id가 redirect에 사용되어 알맞은 result페이지로 redirect 시켜줌
->근데 잘 보니 result view에 best_developer_id 받는 부분이 없다. 만들어주기!
def result(request, developer_id):
developer=Developer.objects.get(pk=developer_id)
context={
'developer':developer, # developer는 바로 위에서 developer다
}
return render(request, 'result.html', context=context)
-> 이제 아무리 결과 페이지에서 새로고침을 해도 result함수만 호출되기 때문에 설문 참여자 수는 늘어나지 않
5. url 분리
여태까지 모든 url을 MBIT/urls.py에서 관리함. 실무에서는 urls.py에서 모든 url을 관리하지 않는다(url 개수 너무 많아서)
-> URL 설정을 앱별로 나누어서 관리하는 게 좋음
-> main앱과 관련된 url을 main/urls.py에 저장
1. 기존 MBIT/urls.py 내용을 main/urls.py로 옮기기(admin은 여기 통해 들어갈 게 아니니까 관련 내용 삭제)
2. MBIT/urls.py 아래와 같이 수정
from django.contrib import admin
from django.urls import path, include
from main import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('main.urls')), # 어떤 option이 붙든 main.urls.py로 연결된다
]
6. URL name
-URL을 변수(name)에 넣어서 사용.
-특정 URL에 우리가 알 수 있는 변수(name)을 지정하고, 이 URL을 사용해야 하는 곳에서 URL을 직접 사용하는 대신에 이 변수(name)를 사용
app_name='main'
urlpatterns = [
path('', views.index, name='index'),
path('form/', views.form, name='form'),
path('submit/', views.submit, name='submit'),
path('result/<int:developer_id>/', views.result, name='result'),
]
-> app_name : URL namespace(이름공간)으로 여러 앱들 마다 동일한 URL name을 가질 때 구별하기 위함
7. Template 상속
겹치는 부분은 base.html로 따로 빼내놓고 각 page마다 다른 부분만 block과 endblock으로 감싸줌
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>나의 개발 유형찾기 - final</title>
{% block head %}
{% endblock head %}
<title>Document</title>
</head>
<body>
{% block body %}
{% endblock body %}
{% block js %}
{% endblock js %}
</body>
</html>
-> base.html
8. 개발자 유형별 데이터 Json형태로 추가하기
main > models.py의 class Developer 데이터 추가하기
-> model이 변경되었으므로 터미널 창에 makemigrations 해주기
이런 문구가 뜨게 되는데 얘는 data가 비어 있을 때 default 값을 줄건지 물어보는거다. 1을 선택하면 비어있을 때 default 값을 주게 된다.
만들어 둔 data.json파일에 다운받은 json 파일을 복사해 붙여넣고 터미널 창에 python manage.py loaddata data.json 입력해 전체 데이터를 load 한다.
python manage.py shell_plus 입력하면 데이터가 잘 들어갔는지 확인할 수 있다.
json 바탕으로 데이터 뿌리는 팁!
-> colab같은데에 데이터 가져와서 직접 한번 실행시켜 "어떤 데이터를 어떻게 뿌리는구나" 라고 판단하기
"model": "main.developer",
"pk": 2,
"fields": {
"name": "프론트엔드 개발자",
"count": 12,
"data": {
"title": "예술적 감각이 뛰어난 당신은!",
"features": [
"내 눈으로 결과를 확인해야 함. 바로 바로 적용되는 화면에 성취감을 느낌.",
"어렸을 때 한번쯤은 호그와트 들어가는 상상해보지 않나? 상상력이 풍부하고 새로운 도전을 즐기는 편임.",
"은근히 완벽주의자 성향이 있어서 1px이라도 비뚤어지거나 대칭이 맞지 않으면 참을 수 없음.",
"자유로운 영혼이지만 내가 지금 무엇을 해야하는지 정확히 알아야 함."
],
"descriptions": [
"FE(Front-End)라고도 부르며, 웹이나 앱을 이용할 때 눈으로 보는 모든 화면들을 만드는 개발자를 의미합니다.",
"반응형 웹이나 UI를 만들어야 하기 때문에 미적 감각이 필요합니다.",
"웹 퍼블리셔와 프론트엔드 개발자는 공동의 영역을 공유합니다. 웹 퍼블리셔는 주로 HTML, CSS를 사용하여 화면을 설계합니다. 프론트엔드 개발자는 js, jQuery, 백엔드와의 연동 등 화면 설계와 동시에 화면 구조까지 살펴볼 수 있는 사람을 말합니다. 그러나 실무에서는 이 두 가지 직업군의 롤 모두를 수행하는 사람이 많습니다."
],
"languages": {
"list": [
{
"name": "HTML",
"img": "img/lang/html.png"
},
{
"name": "CSS",
"img": "img/lang/css.png"
},
{
"name": "JavaScript",
"img": "img/lang/js.png"
}
],
"comments": [
"HTML, CSS, JavaScript 웹을 구성하는 3요소 입니다! (얼마 전 Web Assembly가 추가되어 4요소가 되었지만 처음에는 3개만 배우셔도 충분합니다!)",
"기본적인 내용이 숙지되었다면 CSS Animation, Media Query, Canvas, JSON parsing, Ajax, Node.js, jQuery를 공부해보세요!"
]
},
"lectures": [
{
"name": "HTML, CSS, JS, Python 30분 요약강좌",
"img": "img/lec/lec_30minutes.png",
"url": "https://www.inflearn.com/course/제주코딩-웹개발-30분요약"
},
{
"name": "코알못에서 웹서비스 런칭까지 : 제주 코딩 베이스캠프(Django)",
"img": "img/lec/lec_web_fullstack.png",
"url": "https://www.inflearn.com/course/web_fullstack"
},
{
"name": "제주코딩베이스캠프 Code Festival: JavaScript 100제",
"img": "img/lec/lec_js100.png",
"url": "https://www.inflearn.com/course/제주코딩-자바스크립트-100제"
}
]
}
}
},
임의로 하나의 모델의 데이터 뽑음. 이때 languages는 다른애들과 다르게 리스트형식이다. 따라서 코드 languages.list/languages.comment로 작성해야 함. 인자들은 language.name 이런식으로 리스트 안의 원소에 접근해야 한다.
<div class="explain">
<h3 class="title">그래서 어떤 언어부터 공부해야 할까?</h3>
<ul class="language_lists">
{% for language in developer.data.languages.list %}
<li>
<div class="img_wrap">
<img src="{% static '' %}{{ language.img }}" alt="{{ language.name }}">
</div>
<h3>{{ language.name }}</h3>
</li>
{% endfor %}
</ul>
{% if data.languages.comments %}
<ul>
{% for comment in developer.data.languages.comments %}
<li>
{{ comment }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>