TIL_20230309_Django 프레임워크 보강 03

창고·2023년 3월 9일
0

3. 장고 핵심 기능 - Template

(1) 템플릿 설정 항목

  • DTL(Django Template Engine) : 장고의 코어 템플릿 엔진. Jinja 엔진도 기본적으로 지원
  • settings.py 에 TEMPLATES 항목에 있음
  • BACKEND : 사용할 템플릿 엔진을 지정
  • DIRS : 항목은 프로젝트 템플릿 파일이 위치한 디렉터리를 지정
  • APP_DIRS : 템플릿 파일을 찾을 때 애플리케이션 내의 템플릿 디렉터리에서도 찾을지 여부를 지정. 기본값은 false, startproject 명령에 의해 생성될 경우에는 true로 설정됨
  • OPTIONS : 템플릿 엔진에 따라 해당하는 옵션 항목들을 설정
    • context_processors : 웹 요청에 들어있는 파라미터들을 인자로 받아 컨텍스트 데이터로 사용될 사전을 만드는 호출 가능한 객체를 지정, 보통은 함수로 정의됨. 이 함수들이 반환하는 사전은 최종 컨텍스트 데이터를 만들 때 추가, 디폴트는 빈 리스트
    • debug : 템플릿 디버그 모드를 설정. true 설정 시 템플릿 렌더링 과정에서 에러 발생 시 템플릿 파일 내에서 에러가 발생한 줄을 다른 색으로 표시, 디폴트는 다른 설정 항목인 DEBUG 항목의 값을 따름
    • loaders : 템플릿 로더 클래스를 지정. 템플릿 파일을 찾아 메모리로 로딩하는 역할
    • string_if_invalid : 템플릿 변수가 잘못된 경우 대신 사용할 문자열을 지정, 디폴트는 공백 문자열
    • file_charset : 템플릿 파일을 읽어 디코딩할 때 사용하는 문자셋 지정. 디폴트는 utf-8

(2) 템플릿 내부 처리 과정

  • 사용할 템플릿 엔진 및 관련 옵션들을 결정해 Engine 객체를 생성
    • TEMPLATES 설정 항목에 지정된 값들로 Engine 객체를 생성할 때 인자로 사용
    • Engine 객체 뿐만이 아닌 Engine 객체에 소속된 Loader 객체도 같이 생성됨
  • Engine 객체에 지정된 템플릿 로더는 하나 또는 여러 개로 나뉜 템플릿 파일들을 찾고 그 내용, 즉 템플릿 코드들을 하나로 모아 Template 객체를 생성
    • 설정 옵션에 Loaders가 지정되지 않을 경우 디폴트 로더로 아래 2개의 클래스를 사용. 특별한 경우가 아닐 경우 디폴트 로더 2개를 변경 없이 사용하는 것이 일반적
      • django.template.loaders.filesystem.Loader : DIRS 항목에 지정된 디렉터리 검색
      • django.template.loaders.app_directories.Loader : 각 INSTALLED_APPS 하위의 templates/ 디렉터리를 검색
    • 템플릿 파일 찾는 순서는 loaders 디폴트 설정에 filesystem.Loader가 먼저 나오므로 DIRS 항목에 지정된 디렉터를 가장 먼저 찾게 됨
    • Loader 객체는 템플릿 파일들을 찾은 후 Template 객체를 생성
    • 템플릿 파일을 찾은 결과는 보통 하나의 파일이나 {% extends %}, {% include %} 태그가 있는 경우 여러 개의 파일을 찾게 됨
  • Template 객체의 render() 메서드를 호출, 컨텍스트 데이터와 요청(request) 데이터를 템플릿 코드에 대입하고 렌더링 결과로 최종 텍스트 파일을 만듬
    • 렌더링을 위해 템플릿 코드와 컨텍스트 데이터가 필요
    • 템플릿 코드는 앞선 단계에서, 컨텍스트 데이터는 뷰 함수에서 만들어져 템플릿 시스템으로 전달됨
    • 웹 요청 객체인 HttpRequest 객체에 들어 있는 데이터도 컨텍스트 데이터로사 용됨
    • 뷰에서 전달된 데이터만으로 최종 컨텍스트 데이터를 만들 때는 Context, HttpRequest 데이터를 포함해 최종 컨텍스트 데이터를 만들 때는 RequestContext 객체를 사용
    • startproject 명령으로 프로젝트 생성 시 다음과 같은 컨텍스트 프로세서가 생성됨
      • django.template.context_processors.debug : 현재 실행 환경의 DEBUG 모드를 지칭하는 debug 변수 및 웹 요청 처리 과정에 사용된 SQL 쿼리 정보를 담은 sql_queries 변수 2가지가 최종 컨텍스트 데이터에 추가됨
      • django.template.context_processors.request : 현 요청의 HttpRequest를 지칭하는 request 변수가 최종 컨텍스트 데이터에 추가
      • django.contrib.auth.context_processors.auth : 로그인 사용자를 지칭하는 user 변수 및 그 사용자의 권한을 지칭하는 perms 변수가 최종 컨텍스트 데이터에 추가됨
      • django.contrib.messages.context_processors.messages : 메시지 리스트를 지칭하는 messages 변수와 메시지 레벨을 지칭하는 DEFAULT_MESSAGE_LEVELS 변수가 최종 컨텍스트에 추가됨
      • django.template.context_processors.csrf : {% csrf_toekn %} 템플릿 태그 처리에 필요한 토큰이 최종 컨텍스트 데이터에 추가됨

(3) 제네릭 뷰의 디폴트 템플릿

  • 모델을 대상으로 로직을 처리하는 제네릭 뷰 대부분은 디폴트 템플릿명을 가지고 있음 (template_name 속성을 지정하지 않는 겨우 사용되는 이름)
<app_label><model_name 소문자>_<template_name_suffix>.html
blog/post_list.html # 모델명이 Post일 경우 예시

(4) {% include %} 태그

  • DRY 원칙 (Don't Repeat Yourself) 에 따라 코드의 중복을 줄일 수 있는 여러 가지 기능을 제공
  • {% extends %} 태그 : 템플릿 상속 기능이 가장 대표적임
  • {% include %} 태그 : 공통적으로 사용할 수 있는 템플릿 파일을 따로 만들어둔 다음 해당 태그로 공통 파일을 가져와 사용. 다른 템플릿 파일을 현재의 템플릿에 포함시킴
<!--템플릿 파일명을 따옴표로 묶음-->
{% include "foo/bar.html" %}

<!--키워드 인자로 변수를 지정할 수도 있음-->
{% include "foo/bar.html" with person="Jane" greeting="Hello" %}

<!--변수를 고정할 수 있음-->
{% include "foo/bar.html" with greeting="Hi" only %}

<!--foo/bar.html-->
{{ greeting }}, {{ person|default:"friend" }}

(5) {% static %} 템플릿 태그

  • 이미지, JS, CSS 파일들이 static 파일
  • {% static %} 태그를 통해 정적 파일을 쉽게 처리할 수 있음

(6) staticfiles 애플리케이션 기능

  • 정적 파일을 처리하기 위한 staticfiles 애플리케이션을 제공
  • 다만 이는 개발 환경에서 사용되며 상용 환경에서는 사용하지 않음
  • runserver를 실행시키고 정적 파일 처리가 필요할 경우 staticfiles 앱을 사용해 정적 파일을 처리

4. 장고 핵심 기능 - Form

(1) 장고 Form 클래스 이해

  • 장고의 폼 동작은 Form 클래스로 폼을 정의하고 정의된 폼을 뷰에서 사용하며 최종적으로 템플릿 엔진에 의해 HTML 텍스트로 렌더링되는 절차를 거쳐 사용자에게 보여짐
  • 장고에서 폼은 기본적으로 Form 클래스를 상속받아 정의
  • 폼의 종류
    • 일반 폼 : Form 클래스를 상속 받아 정의
    • 모델 폼 : ModelForm 클래스를 상속 받아 정의. 폼 필드의 구성을 데이터베이스 모델 정의 기반으로 폼을 정의하는 경우에 사용. modelform_factory() 함수를 사용해 모델 폼을 정의할 수도 있음
    • 폼셋 : 일반 폼을 여러 개 묶어서 한 번에 보여줌. formset_factory() 함수를 사용해 폼셋을 정의
    • 모델 폼셋 : 데이터베이스 모델에 기초해서 만든 모델 폼을 여러 개 묶은 폼셋. modelformset_factory() 함수를 사용해 모델 폼셋을 정의
    • 인라인 폼셋 : 두 모델 간의 관계가 1:N인 경우 N 모델에 기초해서 만든 모델 폼을 여러 개 묶은 폼셋, inlineformset_Factory() 함수를 사용해 인라인 폼셋을 정의

(2) 일반 폼 정의

class PhotoForm(forms.Form):

	album = forms.ModelChoiceField(queryset=Album.objects.all())
    title = forms.CharField(label = 'TITLE', max_length=30)
    description = forms.CharField(label = 'Photo Description', widget=forms.Textarea)
    image = ImageField(label='IMAGE')
    upload_dt = forms.DateTimeField(label = 'UPLOADATE') # auto_now_add
  • 일반 폼으로 작성하기 위해서는 직접 폼 필드를 지정해야 하므로 모델의 필드와 폼의 필드 간 매핑 룰이 필요
    • 모델의 ForeignKey 필드는 폼의 ModelChoiceField 필드로 매핑, 선택 항목들은 queryset 속성으로 지정)
    • 모델의 CharField 필드는 폼의 CharField로 매핑됨, 모델의 verbose_name 속성은 폼의 label 속성으로 매핑. max_length 속성도 그대로 매핑
    • 모델의 TextField 필드는 폼의 CharField로 매핑, widget 속성을 forms.Textarea로 지정, 모델 정의에서 blank=True 일 경우 폼 필드는 required=False가 됨
    • 모델의 ImageField는 폼의 ImageField로 매핑

(3) 모델 폼 정의

  • 단순하게 이름을 입력하는 폼 등은 모델과 무관하며 이 경우에는 일반 폼을 사용
  • 모델 정의를 기초로 모델의 테이블에 새로운 레코드를 생성하거나 변경을 하려면 모델 폼을 사용
  • 폼 필드를 정의하지 않아도 장고가 알아서 폼을 정의해주며, 모델 폼을 만드는 방법은 세 가지가 있음
  • ModelForm 클래스 방식
    • 장고에서 기본적으로 제공하는 ModelForm 클래스는 모델에 정의한 필드를 참조해서 모델 폼을 만듬
    • ModelForm 클래스를 상속 받아 정의하면 되므로 작업이 매우 간단
class PhotoForm(forms.ModelForm):

	class Meta:
    	model = Photo
        fields = ['title', 'image', 'description'] # 특정 필드를 지정
        # fields = '__all__' # 모델에 정의된 모든 필드를 폼에 포함
        # exclude = ['description'] 지정된 필드를 제외한 모든 필드를 폼에 포함
  • modelform_factory 함수 방식
    • modelform_factory 함수를 사용해 위에서 설정한 모델 ㅍ모을 함수를 사용해 정의할 수 있음
    • forms.py에 해당 내용을 적용
# forms.py

PhotoForm = modelfrom_factory(Photo, fields='__all__')
# modelform_factory() 함수 설명
modelform_factory(model, form=ModelFrom, fields=None, exclude=None, formfield_callback=None, widgets=None, localized_fields=None, labels=None, help_texts=None, error_messages=None, field_classes=None)

# fields : 리턴하는 ModelForm에 포함될 필드를 지정
# exclude : 리턴하는 ModelForm에 제외될 필드를 지정
# formfield_callback : 모델의 필드를 받아 폼 필드를 리턴하는 콜백 함수를 지정
# widgets : 모델 필드와 위젯을 매핑한 사전
# localized_fields : 로컬 지역값이 필요한 필드를 리스트로 지정
# labels : 모델 필드와 레이블을 매핑한 사전
# help_texts : 모델 필드와 설명 문구를 매핑한 사전
# error_messages : 모델 필드와 에러 메시지를 매핑한 사전
# field_classes : 모델 필드와 폼의 필드 클래스를 매핑한 사전
  • 제네릭 뷰에서의 폼 정의
    • 제네릭 뷰 중 CreateView와 UpdateView는 ModelForm의 기능을 내부에 포함하고 있음
    • Meta 클래스를 사용하지 않고 간단하게 model과 fields 속성을 정의하면 됨
    • 제네릭 뷰 내부적으로 적절한 모델 폼을 만들고 관련 뷰 처리를 함
class PhotoCreateView(CreateView):

	model = Photo
    fields = '__all__'
    
class PhotoUpdateView(UpdateView):

	model = Photo
    fields = '__all__'

(4) 폼셋 정의

  • 폼셋은 폼의 집합이며 일반 폼을 여러 개 묶어서 하나의 폼으로 취급
  • 폼셋을 정의할 때 BaseFormSet 클래스를 상속 받아 작성할 수도 있으나 보통은 formset_factory() 함수를 사용
  • formset_factory 함수
PostSearchFormSet = formset_factory(PostSearchForm)

#formset_factory() 함수 설명
forset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None, validate_max=False, min_num=None, validate_min=False)

# form : 폼셋을 만들 때 베이스가 되는 폼을 지정
# formset : 폼셋을 만들 때 상속받기 위한 부모 클래스를 지정, 보통은 BaseFormSet 클래스를 변경 없이 사용, 변경이 필요한 경우 해당 클래스를 오버라이딩해 기능을 변경한 후 사용
# extra : 폼셋을 보옂루 떄 빈 폼을 몇 개 포함할 지 지정, 디폴트는 1개
# can_order : 폼셋에 포함된 폼들의 순서를 변경할 수 있는지 여부
# can_delete : 폼셋에 포함된 폼들의 일부를 삭제할 수 있는 여부
# max_num : 폼셋을 보여줄 때 포함될 폼의 최대 개수 지정, 디폴트는 None(1000개)
# validate_max : True일 경우 폼셋에 대한 유효성 검사 시 max_num에 대한 검사 실시, 즉 삭제 표시가 된 폼을 제외한 폼의 개수가 max_num보다 작거나 같아야 유효성 검사 통과
# min_num : 폼셋을 보여줄 때 포함될 폼의 최소 개수 지정
# validate_min : True일 경우 폼셋에 대한 유효성 검사 시 min_num에 대한 검사 실시, 즉 삭제 표시가 된 폼을 제외한 폼의 개수가 min_num보다 크거나 같아야 유효성 검사 통과
  • 폼셋의 관리폼(ManagementForm)
    • form-TOTAL_FORMS : 폼의 총 개수 지정
    • form-INITIAL_FORMS : 폼의 초기 데이터가 들어 있는 폼의 개수 지정
    • form-MAX_NUM_FORMS : 폼셋의 max_num 값을 지정
    • form-MIN_NUM_FORMS : 폼셋의 min_num 값을 지정

(5) 모델 폼셋 정의

  • 모델 폼과 폼셋의 특징을 둘 다 가지고 있는 폼, 즉 모델 폼을 여러 개 묶은 것
  • modelformset_factory() 함수를 사용
PhotoFormSet = modelformset_factory(Photo, fields='__all__')
  • 해당 함수는 내부적으로 modelform_factory()와 formset_factory() 함수를 호출

(6) 인라인 폼셋 정의

  • 메인 폼에 종속된 폼셋이며 주종 관계는 테이블의 관계가 1:N 관계에서 외래 키로 연결된 경우로부터 비롯 됨
    • 메인 폼 : 1:N 관계에서 1 테이블에 대한 폼
    • 인라인 폼셋 : N 테이블에 대한 폼
  • 보통은 inlineformset_factory() 함수를 사용

(7) 파일 업로드 폼

  • 폼을 정의할 때 FileField 또는 ImageField 필드가 있으면 주의가 필요
  • 파일 업로드 폼을 다룰 때 다음을 유의해야 함
    • 인코딩 속성을 멀티 파트로 지정해야 함
    <form enctype="multipart/form-data" method="post" action="/foo/">
    • 폼에 데이터를 바인딩할 때 폼 데이터 뿐만 아니라 파일 데이터도 같이 바인딩해야 함
    # 웹 요청에 들어 있는 데이터로 폼을 바인딩하는 경우
    f = ContactFormWithMugshot(request.POST, request.FILES)
    • 해당 폼이 멀티파트 폼인지 확인 필요
    f = ContactFormWithMugshot()
    f.is_multipart() # 멀티파트 폼 유무 확인, boolean
  • 템플릿 파일 예제
{% if form.is_multipart %}
<form enctype="multipart/form-data" method="post" action="/foo/">
{% else %}
<form method="post" action="/foo/">
{% csrf_token %}
{% endif %}
  {{ form }}
  </form>
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글