Templates
- HTML을 동적으로 생성하기 위해 템플릿을 사용한다.
- 장고는 DTL이라고 부르는 장고 템플릿 시스템을 사용한다.
- Rendering은
render()
를 사용하여 템플릿에 context data를 삽입한다.
Support for template engines
Configuration
템플릿 파일을 로드 설정에는 두 가지 방법이 있다.
DIRS
: 데릭토리를 정의 (더 많이 사용하는 방법)APP_DIRS
: 설치된 app 내의 템플릿을 명시 (잘 사용하지 않음)
The Django template language (중요)
Syntax
렌더링은 변수들을 컨텍스트에 있는 값들로 바꾸고 태그를 실행한다.
Variables
- 변수는 context에서 값을 출력한다.
- 변수는 {{ }} 로 둘러싸여 있다.
- 딕셔너리 검색, 속성 검색, 리스트 인덱스 검색은 dot(.) 표기법을 사용한다.
Tags
- 태그는 제어문의 역할을 해, content를 출력할 수 있다. (예:
if
,for
) - 태그는 {% %} 로 둘러싸여 있다.
- 대부분 태그는 인자를 허용한다.
- 일부 태그는 시작과 종료 태그가 필요하다.
Filters
Comments
{# 주석은 html로 변환되지 않는다. #}
{# this won't be rendered #}
Components
Context
- Context는 현재 HttpRequest를 저장하고, 템플릿 Context Processors를 실행한다.
- Context 데이터는 일반 dict로 전달되고, 필요한 경우 현재 HttpRequest가 개별적으로 전달된다.
Render()
할 때, 전달해주는 객체
Context processors
- Context Processors는 현재 HttpRequest를 인자로 받아,
- rendering context에 추가할 데이터 dict을 반환하는 함수다.
- 주된 용도는 모든 템플릿에 코드를 반복하지 않고, 공통 데이터를 컨텍스트에 추가하는 것이다.
The Django template language
Variables
- 변수 이름은 알파벳과 underscore(_)의 조합으로 구성
- dot(.)과 공백은 변수이름으로 사용할 수 없다.
- 존재하지 않는 변수를 사용할 때, 템플릿 시스템은
string_if_invalid
옵션 값을 삽입한다. string_if_invalid
기본값은 빈 값(‘‘)이다.
Filters
- 필터를 사용해 표시할 변수를 수정할 수 있다.
- 필터를 적용하기 위해 파이프를 사용한다.
- 필터는 “연결”될 수 있다. 하나의 필터가 적용된 결과로 다음 필터를 진행한다.
- 어떤 필터들은 인자를 취한다.
장고는 60개의 내장 템플릿 필터를 제공한다. 일반적으로 많이 사용하는 필터는 다음과 같다.
default
: 변수가 False이거나 비어있으면, 주어진 default를 사용length
: 값의 길이 반환filesizeformat
: 값을 “사람이 읽을 수 있는” 파일사이즈로 포맷
Tags
장고는 24개의 내장 템플릿 태그를 제공한다. 일반적으로 많이 사용하는 태그는 다음과 같다.
for
if
,elif
,else
:if
태그 안에서 필터와 연산자(operator)를 사용할 수 있다.block
,extends
: 템플릿 상속 설정
Template inheritance
템플릿 상속은 기본 “골격” 템플릿을 만드는 것이다. 기본 골격 템플릿은 사이트의 공통 요소를 포함하고, 자식 템플릿이 재정의 할 수 있는 block
을 정의한다.
- {% extends %} : 템플릿의 처 번째 태그여야만 함
- {% block %}
- {{ block.super }} : 상위요소의 내용을 가져옴
Automatic HTML escaping
XSS(Cross Site Scripting) 공격을 피하기 위한 두 가지 방법이 있다.
- 잠재적으로 위험한 HTML 문자를 escape 필터하는 법 (Django 처음 몇 년 동안의 기본 솔루션)
- Django의 자동 HTML escaping을 사용하는 법
장고 템플릿 시스템을 사용한다면 기본적으로 두 번째 방법이 적용되어 있다.
How to turn it off
자동 escaping을 사용하고 싶지 않다면, 여러가지 방법으로 끌 수 있다.
- For individual variables :
safe
필터 - For template blocks :
autoescape
태그
Accessing method calls
템플릿은 뷰로부터 전달받은 클래스 속성(like 필드이름)과 변수에 접근 할 수 있다.
유사하게, QuerySets에서 제공하는 count()
메서드도 사용할 수 있다.
템플릿 언어에서 사용할 수 있는 논리 처리량을 제한한다. 데이터는 뷰에서 계산 된 다음 표시를 위해 템플릿으로 전달한다.
Custom tag and filter libraries
INSTALLED_APPS
에 django.contrib.humanize
를 추가한 후, 템플릿에 load
태그를 사용해 불러옵니다.
Working with forms
일방적으로 보여주고 끝나는 페이지가 아니라면 폼을 이해하고 사용해야 한다.
HTML forms
HTML에서 form은 <form> ... </ form>
내에 있는 요소 모음이다. 사용자는 폼 안에 있는 텍스트 입력, 옵션 선택 등의 동작을 수행하고, 그 데이터들은 서버로 다시 보내진다.
몇 가지 폼 요소 인터페이스는 자바스크립트와 CSS로 구현해두었다.
폼에는 다음의 두 가지를 반드시 지정해야 한다.
- where : 사용자가 입력한 데이터가 반환될 URL
- how : HTTP 메소드
폼 데이터는 <forms>
의 action
속성에 지정된 URL로 보내진다.
GET and POST
폼에서 다룰수 있는 메소드는 GET
과 POST
뿐이다.
POST
메서드는 브라우저가 인코딩한 폼 데이터를 묶어서 서버로 전송한다.
GET
메서드는 데이터를 문자열로 묶어 URL에 구성하는데 사용한다. URL에는 데이터를 전송해야하는 주소는 물론 데이터 키와 값이 포함됩니다.
시스템의 상태를 변경하는 데 사용할 수있는 요청(예: 데이터베이스 변경 요청)은 POST
를 사용한다. GET
은 시스템 상태에 영향을주지 않는 요청에만 사용한다.
GET
: 조회(읽기전용)POST
: 삭제, 수정, 생성
Django’s role in forms
굉장히 복잡한 작업이다. 그렇지만 우리가 이걸 만들었다.
Forms in Django
웹 어플리케이션의 context에서 ‘form’은
HTML <form>
이거나- 장고의
Form
클래스이거나 - 데이터구조에서 제출된 것일 수도있고
- 전송되는 모든 과정을 의미 할 수 있다.
The Django Form
장고의 모델과 폼 클래스는 긴밀하게 연결되어 작업을 수행한다.
- 폼 클래스의 필드는 HTML 폼
<input>
요소에 매핑 - 폼의 필드 자체는 클래스 (DateField, FileField)
- 폼이 제출 될 때, 폼 데이터를 관리하고 유효성 검사를 수행
- widget : 체크박스, 라디오버튼, 텍스트 필드, …
Building a form
Building a form in Django
The Form
class
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
Form
인스턴스에는 모든 필드에 대한 유효성 검사 루틴을 실행하는 is_valid()
메서드가 있다. 모든 필드에 유효한 데이터(검증된 데이터)가 포함되어 있으면 다음을 수행한다.
- return True
- 폼의 데이터를
cleaned_data
속성에 저장
The template
The view
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import NameForm
def get_name(request):
if request.method == 'POST':
# 폼 인스턴스 생성
# request의 데이터로 채움 (바인딩)
# request.POST안에서 your_name을 찾음
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# 빈 폼 생성
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
More about Django Form
classes
모든 폼 클래스는 django.forms.Form
의 서브 클래스이다. (ModelForm
포함)
Bound and unbound form instances
폼 인스턴스에 연결된 데이터가 있는지 없는지 구별하는 것
More on fields
Widgets
각각의 폼 필드에 해당하는 Widget 클래스가 있다.
Field data
폼과 함께 제출 된 데이터가 무엇이든간에 is_valid()
를 호출하여 유효성을 검사하고 유효성이 검사 된 폼 데이터는 form.cleaned_data
딕셔너리에 저장된다.
주의 : request.POST
객체를 사용할 수도 있지만, 검증된 데이터(cleaned_data
)를 사용하는 것이 좋다.
Working with form templates
Form rendering options
폼의 결과는 <form>
태그로 둘러쌓여있지 않다. 또한 폼의 submit 버튼도 없다. 작성하는 것을 잊지 마라.
각 폼 필드에는 id_<field-name>
으로 설정된 ID 속성이 있다. 함께 제공되는 label
태그에 의해 참조됩니다.
Rendering fields manually
원한다면 수동으로 처리 할 수 있다. 각 필드는 ``를 사용하여 폼의 속성으로 사용할 수 있다. (예: 부트스트랩 사용할 때)
Looping over the form’s fields
loop를 사용할 수 있다.
Customizing authentication in Django
Extending the existing User
model
기본 사용자 모델을 확장하는데 두 가지 방법이 있다.
- 프록시 모델 : 테이블은 그대로이고 동작만 새로 만들 때 (메서드가 필요할 때)
- OneToOneField : OneToOneField 필드를 사용해 추가 정보 모델을 만드는 법 (필드가 필요할 때)
Substituting a custom User
model
(사용자 모델을 대체하는 방법을 실습할 것)
세팅파일에 기본 유저 모델을 재정의해야 한다.
AUTH_USER_MODEL = 'myapp.MyUser'
Using a custom user model when starting a project
시작할 때 부터 하는 것을 강력히 추천한다.
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
AUTH_USER_MODEL
과 위의 설정을하고 마이그레이션을 진행한다.
Referencing the User
model
AUTH_USER_MODEL
설정이 변경된 경우, User를 직접 참조하면(예: 외래 키로 참조하는 경우) 코드가 작동하지 않는다.
get_user_model()
: User를 직접 참조하는 대신, ` django.contrib.auth.get_user_model()`을 사용한다. 이 메서드는 현재 활성화된 유저 모델을 리턴한다.
Specifying a custom user model
장고는 커스텀 유저 모델이 최소한 몇 가지를 충족하기를 기대한다.
- identification 목적을 위한 unique 필드 (기본값은 username)
- 사용자를 “short”형식과 “long”형식으로 다룰수 있게 한다.
사용자 커스텀 모델을 생성하는 가장 쉬운 방법은 AbstractBaseUser
를 상속하는 것이다.
class models.CustomUser
- USERNAME_FIELD
- REQUIRED_FIELDS
- is_active : 유저가 활성화되었는지 확인
- 기본값은 True
- 메일을 인증 후 가입하는 경우 사용
- get_full_name()
- get_short_name()
class models.AbstractBaseUser
- get_username()
- clean()
- is_authenticated : 사용자가 인증되었는지 여부
- 항상 True인 읽기 전용 특성
- AnonymousUser.is_authenticated는 항상 False
- is_anonymous
- set_password(raw_password)
- check_password(raw_password)
- set_unusable_password()
- has_usable_password()
- get_session_auth_hash()
커스텀 유저 모델을 위한 UserManager
를 정의해야 한다. BaseUserManager
를 확장해서 사용한다.
장고의 기본 유저모델을 가져와서 커스텀 유저 모델을 만들면 매니저를 상속받거나 오버라이드해서 사용할 수 있다.
class models.CustomUserManager
create_user(*username_field*, password=None, **other_fields)
create_superuser(*username_field*, password, **other_fields)
class models.BaseUserManager
classmethod normalize_email(email)
get_by_natural_key(username)
make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')
: 패스워드 입력이 필요없는 서비스를 제공할 때 사용
인스타그램 프로젝트
1. 로그인 폼 추가
- form.py생성
- login 메서드 이름 변경
- is_valid 조건 추가
- 에러 관리 추가
2. Custom User Model로 변경
# member/models.py
from django.contrib.auth.base_user import AbstractBaseUser
class MyUser(AbstractBaseUser):
pass
위와 같이 작성하고 cmd + AbstractBaseUser 클릭
해 base_user.py로 이동한다.
# base_user.py
class AbstractBaseUser(models.Model):
# 기본 필드 확인
# password / last_login / is_active
password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), blank=True, null=True)
is_active = True
REQUIRED_FIELDS = []
# 상속이라 데이터베이스에 생기지 않음
class Meta:
abstract = True
# ...
# 유효성 검사
def clean(self):
setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username()))
AUTH_USER_MODEL을 settings.py에 설정한다.
# instagram/settings.py
AUTH_USER_MODEL = 'member.MyUser'
여기까지 작업하고 마이그레이션을 하면 아래와 같은 에러가 발생한다.
$ ./manage.py makemigrations
Traceback (most recent call last):
File "./manage.py", line 22, in <module>
execute_from_command_line(sys.argv)
# ...
File "/usr/local/var/pyenv/versions/auth/lib/python3.4/site-packages/django/contrib/auth/checks.py", line 42, in check_user_model
if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS:
AttributeError: type object 'MyUser' has no attribute 'USERNAME_FIELD'
USERNAME_FIELD는 필수이므로 추가한다.
# member/models.py
from django.contrib.auth.base_user import AbstractBaseUser
from django.db import models
class MyUser(AbstractBaseUser):
username = models.CharField(max_length=50, unique=True)
USERNAME_FIELD = 'username'
이제 admin 페이지에 잘 접속된다. 로그인하기 위해 슈퍼유저를 생성한다. 하지만 아래와 같은 에러가 발생한다.
$ ./manage.py createsuperuser
Username: hm
Traceback (most recent call last):
File "./manage.py", line 22, in <module>
execute_from_command_line(sys.argv)
# ...
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
AttributeError: 'Manager' object has no attribute 'get_by_natural_key'
유저를 만들기 위해서는 UserManager를 생성해야 한다.
# member/models.py
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.db import models
class MyUserManager(BaseUserManager):
pass
class MyUser(AbstractBaseUser):
username = models.CharField(max_length=50, unique=True)
USERNAME_FIELD = 'username'
# 생성한 MyUserManager 인스턴스 생성
object = MyUserManager()
작성 후 다시 슈퍼유저를 생성한다.
$ ./manage.py createsuperuser
Username: hm
Password:
Password (again):
Traceback (most recent call last):
File "./manage.py", line 22, in <module>
execute_from_command_line(sys.argv)
# ...
self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
AttributeError: 'MyUserManager' object has no attribute 'create_superuser'
create_superuser()
와 create_user()
를 추가한다. 이 때, create_superuser()
는 password를 반드시 제공해야 한다.
# member/models.py
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.db import models
class MyUserManager(BaseUserManager):
def create_user(self, username, password=None):
# Manager 메서드는 self.model에 접근하여
# 모델 클래스를 가져올 수 있다.
user = self.model(
username=username
)
user.set_password(password)
user.save()
return user
def create_superuser(self, username, password):
user = self.model(
username=username
)
user.set_password(password)
user.save()
return user
유저를 생성하면 잘되는 것을 확인할 수 있다.
$ ./manage.py createsuperuser
Username: hm
Password:
Password (again):
Superuser created successfully.
admin 페이지에 접속해 로그인 하면, 아래와 같은 메시지가 뜬다.
AttributeError at /admin/login/
'MyUser' object has no attribute 'is_staff'
유저와 admin의 접근 제어를 허용하는 메서드를 사용해야 한다.
# member/models.py
class MyUserManager(BaseUserManager):
def create_user(self, username, password=None):
# ...
def create_superuser(self, username, password):
user = self.model(
username=username
)
user.set_password(password)
# admin 사이트에 접속할 수 있는 권한
user.is_staff = True
# 모든 권한을 지정
user.is_superuser = True
user.save()
return user
다시 슈퍼유저 생성 후 admin 페이지에 로그인하면 아래의 애러가 발생한다.
Exception Value: 'MyUser' object has no attribute 'has_module_perms'
쉽게 권한을 제공하는 has_perm이 있는 PermissionsMixin
을 상속 받아 처리한다.
# member/models.py
# ..
# PermissionsMixin 추가
class MyUser(PermissionsMixin, AbstractBaseUser):
# ...
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'username'
object = MyUserManager()
다시 admin 페이지에 접속하면 에러가 발생한다.
NotImplementedError at /admin/
subclasses of AbstractBaseUser must provide a get_short_name() method.
user model을 커스텀할 때, 최소한의 요구사항이 두 가지 있다.
- 식별(identification) 목적을 위한 unique 필드
- 유저의 “short” 형식과 “long” 형식의 이름을 제공
필수 메서드이므로 추가한다.
# member/models.py
# ..
class MyUser(PermissionsMixin, AbstractBaseUser):
# ...
def get_full_name(self):
return '{} ({})'.format(
self.nickname,
self.username,
)
def get_short_name(self):
return self.nickname
admin 페이지에 접속하면 접속이 된다. 하지만 권한이 없다고 나온다.
커스텀 user model을 admin 페이지에 등록해야만 한다. AbstractBaseUser
user model을 확장했다면, custom ModelAdmin
클래스를 정의해야 한다. django.contrib.auth.admin.UserAdmin
를 기본 서브클래스로 사용하는 것도 가능하다. 하지만 (django.contrib.auth.models.AbstractUser
에 있는) 관련 필드를 재정의해야만 한다.
UserAdmin
을 상속받아 처리한다.
# member/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import MyUser
class MyUserAdmin(UserAdmin):
pass
admin.site.register(MyUser, MyUserAdmin)
다음과 같은 에러가 발생한다.
<class 'member.admin.MyUserAdmin'>: (admin.E108) The value of 'list_display[2]' refers to 'first_name', which is not a callable, an attribute of 'MyUserAdmin', or an attribute or method on 'member.MyUser'.
<class 'member.admin.MyUserAdmin'>: (admin.E108) The value of 'list_display[3]' refers to 'last_name', which is not a callable, an attribute of 'MyUserAdmin', or an attribute or method on 'member.MyUser'.
<class 'member.admin.MyUserAdmin'>: (admin.E116) The value of 'list_filter[2]' refers to 'is_active', which does not refer to a Field.
다음과 같이 수정해주면, 정상적으로 접속할 수 있다.
# member/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from .models import MyUser
class MyUserAdmin(UserAdmin):
list_display = ('username', 'nickname')
list_filter = ('is_staff', 'is_superuser')
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('email', 'gender', 'nickname'),}),
)
admin.site.register(MyUser, MyUserAdmin)
createsuperuser 두번하면 만들어지면안되는됨, sqllite는 제약조건이 잘 안먹는 경우가 많다.
Postgre SQL
- https://www.pgadmin.org/
- https://www.postgresql.org/
$ brew install postgresql