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_APPSdjango.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

장고는 커스텀 유저 모델이 최소한 몇 가지를 충족하기를 기대한다.

  1. identification 목적을 위한 unique 필드 (기본값은 username)
  2. 사용자를 “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을 커스텀할 때, 최소한의 요구사항이 두 가지 있다.

  1. 식별(identification) 목적을 위한 unique 필드
  2. 유저의 “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

과제