Django 기초 강의 정리
2020년04월20일패스트캠퍼스 장고 기초 강의 정리
Mac 기준으로 작성했습니다.
웹 프레임워크에 대한 이해
프레임워크란
- 자주 사용되는 코드를 체계화하여 쉽게 사용할 수 있도록 도와주는 코드 집합
- 라이브러리와 혼동될 수 있지만 좀 더 규모가 크고 프로젝트의 기반이 됨
- 건축에 비유하면 구조를 만드는 골자가 프레임 워크, 그 외 자재들이 라이브러리
우리가 건물을 지으면서 구조를 잡으면 다시 바꾸기 어렵듯이 프레임워크도 그러하고, 자재들은 변경이 용이하듯 라이브러리는 쉽게 변경할 수 있다. 어찌보면 프레임워크를 사용하는 것이 의존성이 높아 보일 수도 있지만 그만큼 틀이 잡혀있고 제공해주는것이 많아 사용하기 좋다.
웹 프레임워크
웹 개발에 필요한 기본적인 구조와 코드(클래스, 함수 등)가 만들어져있다.
제공해주는 기능
- URL 파싱
- 요청 파싱
- 응답 생성
- 세션 관리
- 데이터베이스 연동
- 관리자 페이지
개발자가 해야할 일
- 비즈니스 로직 구성: 어떻게 동작할지 구성해야함.
- 데이터 정의
웹 프레임워크로써 Django
Django는 MTV 방법론을 기반했다.
- Model계층: 비즈니스에 사용할 데이터들을 구성하여 데이트베이스에 연동하는 부분. Django에서 제공하는 기능을 사용해서 쉽게 데이터들을 만들고 원하는 sql을 사용하여, 프로그래밍적으로 제어할 수 있다. 심화해서도 사용할 수 있다.
- View계층: 비즈니스 로직에 해당.(우리가 만들어야 할 부분), URL 파싱이라던가, 결과값 반환에 대한 구현되어있고, 사용하면 된다.
- Template계층: HTML코드에 대한 부분, 템플릿 언어를 제공하여 반복문, 제어문 등을 사용하여 데이터를 유연하게 다룰 수 있다.
Django 프로젝트 구성
python 가상환경으로 프로젝트를 구분해주자. 가상환경을 쓰는 이유는 A라는 프로젝트는 x버전의 a패키지가 필요하고, B라는 프로젝트는 y버전의 a 패키지가 필요할 수 있다. 이때, 그냥 설치된 python을 사용한다면, 버전 충돌 문제도 있을 수 있기에 가상환경을 만들어 버전별로 관리해주는 것이 좋다! pyenv 정리 보기
django 설치
pip install django
django 프로젝트 생성
django-admin startproject 프로젝트명
django 앱 생성
django-admin startapp 앱명
생성한 앱들은 프로젝트의 settings.py
의 INSTALLED_APPS에 등록을 해줘야 동작한다.
INSTALLED_APPS += [
'앱1',
'앱2',
]
Django MTV
Model 만들기
앱 디렉토리에서 models.py에서 만들어준다.
from django.db import models
# 모델 만들 때 models의 Model 클래스를 상속받는다.
class Fcuser(models.Model):
username = models.CharField(max_length=64,
verbose_name='사용자명') # 관리자 페이지에서 보일 이름
password = models.CharField(max_length=64,
verbose_name='비밀번호')
# 등록시간 지정, auto_now_add를 사용하면 자동으로 시간이 기록된다.
registered_dttm = models.DateTimeField(auto_now_add=True,
verbose_name='등록시간')
# Meta 클래스를 사용해서 db 테이블 명을 지정할 수 있다.
class Meta:
db_table = 'fastcampus_fcuser' # DB 테이블 명 지정하기
DB생성
makemigrations으로 변경점이나 추가된 부분에 대한 파일을 만든다.
python manage.py makemigrations
migrate 명령어로 데이터베이스에 반영한다.
python manage.py migrate
sqlite3 확인 방법
sqlite db.sqlite3
.tables
.schema fastcampus_fcuser
.q
admin 생성
모델을 관리하기 쉽게 관리자 페이지를 제공한다.
admin 계정 만들기
python manage.py createsuperuser
django에서 제공하는 서버 실행
python manage.py runserver
브라우저에 127.0.0.1:8000(기본) 접속 후 만든 계정으로 접속
admin 페이지에 앱의 모델들도 보이게 설정
앱/admin.py 설정
from django.contrib import admin
# 관리할 모델 등록
from .models import Fcuser
# 클래스 생성
class FcuserAdmin(admin.ModelAdmin):
# 해당 모델 페이지에서 보여줄 필드명 지정
list_display = ('username', 'password')
# admin 사이트에 등록하기
admin.site.register(Fcuser, FcuserAdmin)
models.py
일부 추가
# 클래스를 문자열로 변환했을 때 표현할 값
def __str__(self):
return self.username
class Meta: # 아래 두 라인 추가
# admin 페이지에서 모델 명 대신 사용자 지정으로 표시
verbose_name = '패스트캠퍼스 사용자'
# Django는 끝에 s가 붙어 복수형으로 보여주는데,
# 우리는 한글이니 복수형태도 변경해준다.
verbose_name_plural = '패스트캠퍼스 사용자'
Templates 만들기
부트스트랩을 이용해서 실습해 보았다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="row mt-5">
<div class="col-12 text-center">
<h1>회원가입</h1>
</div>
</div>
<div class="row mt-5">
<div class="col-12">
{{ error }}
</div>
</div>
<div class="row mt-5">
<div class="col-12">
<form method="POST" action=".">
{% csrf_token %}
<div class="form-group">
<label for="username">사용자 이름</label>
<input type="text" class="form-control" id="username" placeholder="사용자 이름" name="username">
</div>
<div class="form-group">
<label for="useremail">사용자 이메일</label>
<input type="email" class="form-control" id="usermail" placeholder="사용자 이메일" name="useremail">
</div>
<div class="form-group">
<label for="password">비밀번호</label>
<input type="password" class="form-control" id="password" placeholder="비밀번호" name="password">
</div>
<div class="form-group">
<label for="re-password">비밀번호 확인</label>
<input type="password" class="form-control" id="re-password" placeholder="비밀번호 확인" name="re-password">
</div>
<button type="submit" class="btn btn-primary">등록</button>
</form>
</div>
</div>
</div>
</body>
</html>
- error: views.py에서 선언한 error 메세지를 템플릿 엔진에서 변환해줌.
- csrf_token: CSRF공격을 방지하기 위해 form태그 아래에 들어가야함.
CSRF공격이란 Cross Site Request Forgery의 준말로 웹 어플리케이션 취약점 중 하나인데, 인터넷 사용자가 자신의 의지와 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 만드는 공격이라한다.
CDN과 static 파일 관리
CDN(Contens Delivery Network)
사용자에게 웹 콘텐츠를 효율적으로 제공할 수 있는 서버의 분산 네트워크. 최종 사용자와 가까운 POP(Point-Of-Presence)의 위치의 Edge 서버에 캐시된 콘텐츠를 저장하여 대기 시간을 최소화한다.
쉽게 말해, 지리, 물리적으로 떨어져 있는 사용자에게 컨텐츠를 더 빠르게 제공할 수 있고, 느린 응답속도/다운로드 시간을 최소화해준다. CDN 정리 잘되어있는 사이트
static 파일
웹페이지에 쓰이는 CSS, JS코드 파일들. 이 파이들을 효율적으로 관리할 수 있도록 Django에서 static 폴더를 생성해서 사용할 수 있다.
- 프로젝트 파일에 static 폴더 생성
- 폴더 안에 css or js 파일 삽입
- settings.py 하단에 다음 입력
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ]
- html파일에 링크 추가 ```
## 세션
브라우저에 있는 쿠키를 통해 데이터를 유지
서버는 헤더에 쿠키정보를 제공
클라이언트 (요청 `쿠키없이`)-> 서버
클라이언트 <-(응답 `쿠키포함`) 서버: 서버는 DB에 쿠키값을 만들고, 그 값을 클라이언트 응답 헤더에 포함하여 보내준다. 응답을 받은 클라이언트는 자신의 쿠키 저장소에 "서버 - 쿠키값" 들을 저장한다.
클라이언트 (요청 `쿠키 포함`)-> 서버: 클라이언트는 서버의 쿠키값을 같이 보낸다.
클라이언트 <-(응답 `쿠키 포함`) 서버: 이 쿠키값으로 클라이언트를 인지하고 적절한 값을 돌려준다.
#### 로그인 페이지 구현
```python
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
username = request.POST.get('username', None)
password = request.POST.get('password', None)
res_data = {}
if not (username and password):
res_data['error'] = '모든 값을 입력야합니다.'
else:
fcuser = Fcuser.objects.get(username=username)
if check_password(password, fcuser.password):
request.session['user'] = fcuser.id # user = id 세션값 생성
return redirect('/')
else:
res_data['error'] = '비밀번호 틀렸습니다.'
return render(request, 'login.html', res_data)
- check_password를 통해 비밀번호까지 맞으면,
request.session['user'] = fcuser.id
를 통해 세션 생성
홈 이동 및 로그아웃 구현
def home(request):
user_id = request.session.get('user')
if user_id:
fcuser = Fcuser.objects.get(pk=user_id)
return HttpResponse(fcuser.username)
return HttpResponse("Home!")
# 세션을 삭제해주면 된다.
def logout(request):
if request.session.get('user'):
del(request.session['user'])
return redirect('/')
Template 상속
반복되는 코드를 줄이기 위해 틀을 만들어 주고 페이지별 내용을 따로 관리하기 위해 사용한다.
Form 활용하기
forms를 활용해서 input태그를 만들 수 있다.
forms 파일 생성
from django import forms
from .models import Fcuser
from django.contrib.auth.hashers import check_password
class LoginForm(forms.Form):
username = forms.CharField(
error_messages={
'required': '아이디를 입력해주세요.'
},
max_length=32, label="사용자 이름")
password = forms.CharField(
error_messages={
'required': '비밀번호를 입력해주세요.'
},
widget=forms.PasswordInput, label="비밀번호")
def clean(self):
cleaned_data = super().clean()
# print(cleaned_data)
username = cleaned_data.get('username')
password = cleaned_data.get('password')
if username and password:
fcuser = Fcuser.objects.get(username=username)
if not check_password(password, fcuser.password):
self.add_error('password', '비밀번호를 틀렸습니다.')
else:
self.user_id = fcuser.id
views login 함수 변경
def login(request):
if request.method == "POST":
# print(request.POST)
form = LoginForm(request.POST)
if form.is_valid():
request.session['user'] = form.user_id
return redirect('/')
else:
form = LoginForm()
return render(request, 'login.html', {'form':form})
로그인 파일 수정
게시판 만들기
urls 파일 생성
from django.urls import path
from .import views
urlpatterns = [
path('detail/<int:pk>', views.board_detail),
path('list/', views.board_list),
path('write/', views.board_write),
]
모델 정의
from django.db import models
class Board(models.Model):
title = models.CharField(max_length=128,
verbose_name='제목') # 관리자 페이지에서 보일 이름
contents = models.TextField(verbose_name='내용')
writer = models.ForeignKey('fcuser.Fcuser', on_delete=models.CASCADE, verbose_name='작성자')
registered_dttm = models.DateTimeField(auto_now_add=True,
verbose_name='등록시간')
# 클래스를 문자열로 변환했을 때 표현할 값
def __str__(self):
return self.title
class Meta:
db_table = 'fastcampus_board' # DB 테이블 명 지정하기
verbose_name = '패스트캠퍼스 게시글'
verbose_name_plural = '패스트캠퍼스 게시글'
form 정의
from django import forms
class BoardForm(forms.Form):
title = forms.CharField(
error_messages={
'required': '제목을 입력해주세요.'
},
max_length=128, label="제목")
contents = forms.CharField(
error_messages={
'required': '내용을 입력해주세요.'
},
widget=forms.Textarea, label="내용")
views 정의
from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from django.http import Http404
from .models import Board
from fcuser.models import Fcuser
from .forms import BoardForm
def board_detail(request, pk):
try:
board = Board.objects.get(pk=pk)
except Board.DoesNotExist:
raise Http404('게시글을 찾을 수 없습니다.')
return render(request, 'board_detail.html', {'board':board})
def board_write(request):
if not request.session.get('user'):
return redirect('/fcuser/login')
if request.method == 'POST':
form = BoardForm(request.POST)
if form.is_valid():
user_id = request.session.get('user')
fcuser = Fcuser.objects.get(pk=user_id)
board = Board()
board.title = form.cleaned_data['title']
board.contents = form.cleaned_data['contents']
board.writer = fcuser
board.save()
return redirect('/board/list/')
else:
form = BoardForm()
return render(request, 'board_write.html', {'form': form})
def board_list(request):
all_boards = Board.objects.all().order_by('-id')
page = int(request.GET.get('p', 1))
paginator = Paginator(all_boards, 2) # 페이지당 보여줄 개수
boards = paginator.get_page(page)
return render(request, 'board_list.html', {'boards': boards})
html
{% extends "base.html" %}
{% block contents %}
<div class="row mt-5">
<div class="col-12">
<table class="table table-light">
<thead class="thead-light">
<tr>
<th>#</th>
<th>제목</th>
<th>아이디</th>
<th>일자</th>
</tr>
</thead>
<tbody class="text-dark">
{% for board in boards %}
<tr>
<th>{{ board.id }}</th>
<td>{{ board.title }}</td>
<td>{{ board.writer }}</td>
<td>{{ board.registered_dttm }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<nav>
<ul class="pagination justify-content-center">
{% if boards.has_previous %}
<li class="page-item">
<a class="page-link" href="?p={{boards.previous_page_number}}">이전으로</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">이전으로</a>
</li>
{% endif %}
<li class="page-item active">
<a class="page-link" href="">{{boards.number}} / {{ boards.paginator.num_pages }}</a>
</li>
{% if boards.has_next %}
<li class="page-item">
<a class="page-link" href="?p={{boards.next_page_number}}">다음으로</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link disabled" href="#">다음으로</a>
</li>
{% endif %}
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-primary" onclick="">글쓰기</button>
</div>
</div>
{% endblock %}
배포하기
settings 파일 변경
DEBUG = False
ALLOWED_HOSTS = [
'*',
'hsj4665.pythonanywhere.com'
]
STATICFILES_DIRS = [] # 주석 처리
# 아래의 라인을 추가하고 명령어를 통해 해당 디렉토리에 정적파일들을 모두 수집한다.
STATIC_ROOT = os.path.join(BASE_DIR, 'static')