장고에서는 로그인, 로그아웃 등과 같은 로직에 대한 기능들이 이미 settings.py에 앱으로 등록되어 있어 이것을 활용하면 간편하게 로그인, 로그아웃, 회원가입까지 구현할 수 있다.

# Application definition

INSTALLED_APPS = [
    'django.contrib.auth',
]

로그인, 로그아웃과 같은 기능들은 하나의 프로젝트 안에서 다른 앱들도 공용으로 사용될 여지가 충분하기 때문에 하나의 앱안에 기능을 구현하는 것은 부적절하다. 그렇기 때문에 common과 같은 앱을 따로 생성하여 다른 앱들과 공용으로 사용할 수 있게 구성한다.

 

1. common 앱 생성

django-admin startapp common

 

2. settings.py에 생성한 앱 등록

INSTALLED_APPS = [
    #생략
    'common.apps.CommonConfig',

]

3. project/urs.py 경로 추가

urlpatterns = [
   #생략
    path('common/',include('common.urls'))
]

 

3. common/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
app_name = 'common'

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(template_name='common/login.html'),name='login'),
    path('logout/', auth_views.LogoutView.as_view(),name='logout'),
    path('signup/', views.signup, name='signup'),
]

4. common/forms.py 회원가입 폼 만들기

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class UserForm(UserCreationForm):
    email = forms.EmailField(label="이메일")

    class Meta:
        model = User
        fields = ("username", "email")

 

 

5..common/views.py

from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect

# Create your views here.

# 계정생성
from common.forms import UserForm


def signup(request):
    if request.method == "POST":
        form = UserForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            raw_password = form.cleaned_data.get('password1')
            user = authenticate(username=username, password = raw_password)
            login(request,user)
            return redirect('index')
    else:
        form = UserForm()
    return render(request, 'common/signup.html', {'form': form})

 

6. 템플릿 파일 생성

 

# common/login.html

{% extends "base.html" %}
{% block content %}
<div class="container my-3">
<!-- ------------------------------- 로그인 타이틀 , 회원가입 버튼 -------------------------------- -->
    <div class="row">
        <div class="col-4">
            <h4>로그인</h4>
        </div>
        <div class="col-8 text-right">
            <span>또는 <a href="{% url 'common:signup' %}">계정을 만드세요.</a></span>
        </div>
    </div>
<!-- ----------------------------------------------------------------------- -->
    <form method="post" class="post-form" action="{% url 'common:login' %}">
        {% csrf_token %}
        {% include "common/form_errors.html" %}
        <div class="form-group">
            <label for="username">사용자ID</label>
            <input type="text" class="form-control" name="username" id="username"
                   value="{{ form.username.value|default_if_none:'' }}">
        </div>
        <div class="form-group">
            <label for="password">비밀번호</label>
            <input type="password" class="form-control" name="password" id="password"
                   value="{{ form.password.value|default_if_none:'' }}">
        </div>
        <button type="submit" class="btn btn-primary">로그인</button>
    </form>
</div>
{% endblock %}

 

# form_errors.html

{% if form.errors %}
    {% for field in form %}
        {% for error in field.errors %}  <!-- 필드 오류를 출력한다. -->
            <div class="alert alert-danger">
                <strong>{{ field.label }}</strong>
                {{ error }}
            </div>
        {% endfor %}
    {% endfor %}
    {% for error in form.non_field_errors %}   <!-- 넌필드 오류를 출력한다. -->
        <div class="alert alert-danger">
            <strong>{{ error }}</strong>
        </div>
    {% endfor %}
{% endif %}

# signup.html

{% extends "base.html" %}
{% block content %}
<div class="container my-3">
    <div class="row my-3">
        <div class="col-4">
            <h4>계정생성</h4>
        </div>
        <div class="col-8 text-right">
            <span>또는 <a href="{% url 'common:login' %}">로그인 하세요.</a></span>
        </div>
    </div>
    <form method="post" class="post-form">
        {% csrf_token %}
        {% include "common/form_errors.html" %}
        <div class="form-group">
            <label for="username">사용자 이름</label>
            <input type="text" class="form-control" name="username" id="username"
                   value="{{ form.username.value|default_if_none:'' }}">
        </div>
        <div class="form-group">
            <label for="password1">비밀번호</label>
            <input type="password" class="form-control" name="password1" id="password1"
                   value="{{ form.password1.value|default_if_none:'' }}">
        </div>
        <div class="form-group">
            <label for="password2">비밀번호 확인</label>
            <input type="password" class="form-control" name="password2" id="password2"
                   value="{{ form.password2.value|default_if_none:'' }}">
        </div>
        <div class="form-group">
            <label for="email">이메일</label>
            <input type="text" class="form-control" name="email" id="email"
                   value="{{ form.email.value|default_if_none:'' }}">
        </div>
        <button type="submit" class="btn btn-primary">생성하기</button>
    </form>
</div>
{% endblock %}

 

7. 로그인, 로그아웃 후 리다이렉트 처리하기

# settings.py (홈으로 이동)

LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'

 

 

출처: wikidocs.net/book/4223 (점프 투 장고)

 

- 게시판 임시 데이터 만들기

 

#파이썬 쉘 진입 및 라이브러리 추가

>python manage.py shell
# 파이썬 쉘 진입
>>> from pybo.models import Question
>>> from django.utils import timezone  

 

#반복문을 이용하여 데이터 삽입

>>> for i in range(300):
...    q = Question(subject='테스트 데이터:[%03d]' % i, content='컨텐츠 내용', create_date=timezone.now())
...    q.save() 
...    
>>>

 

# ListView를 이용한 간단한 페이징 구현 (project/main/views.py)

class index(ListView):
    model = Question
    # 템플릿 파일 지정
    template_name = 'main/index.html'
    # 화면에 표시될 데이터의 개수
    paginate_by = 10

	# 추가로 보낼 데이터
    def get_context_data(self, **kwargs):
        context = super(index, self).get_context_data()
        context['page_title'] = '질문과 답변'
        return context
	# request를 받으면 실행되는 Query
    def get_queryset(self):
        return self.model.objects.order_by('-create_date')

❖ paginate_by = 한 페이지에 보여질 데이터 개수 로 값을 설정하면 한 페이지에 대한 데이터들은 기본적으로 page_obj라는 변수에 들어가게 된다.

 

# 템플릿 구현 (project/main/templates/index.html)

{% extends 'base.html' %}
{% load custom_filter %}
{% block page_title %} {{ page_title }} {% endblock %}
{% block content %}
    <div class="container my=3">
    <table class="table">
        <thead>
        <tr class="thead-dark">
            <th>번호</th>
            <th>제목</th>
            <th>작성일시</th>
        </tr>
        </thead>
        <tbody>
        {% if page_obj %}
            {% for question in page_obj %}
                <tr>
                    <td>{{ paginator.count }}</td>
                    <td>
                        <a href="{% url 'main:detail' question.id %}">{{ question.subject }}</a>
                        {% if question.answer_set.count > 0 %}
                            <span class="text-danger small ml-2">{{ question.answer_set.count }} </span>
                        {% endif %}
                    </td>
                    <td>{{ question.create_date }}</td>
                </tr>
            {% endfor %}
        {% else  %}
            <tr>
                <td colspan="3">질문이 없습니다.</td>
            </tr>
        {% endif %}
        </tbody>
    </table>
    <!-- page nav -->
    <ul class="pagination justify-content-center">
       <!-- 이전 페이지-->
        {% if page_obj.has_previous %}
        <li class="page-item">
            <a class="page-link" href="?page={{ page_obj.previous_page_number }}">이전</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">이전</a>
        </li>
        {% endif %}
        <!-- 페이지 리스트 -->
        {% for page_number in paginator.page_range %}
        {% if page_number == page_obj.number %}
            <li class="page-item active" aria-current="page">
                <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
            </li>
            {% else %}
            <li class="page-item">
                <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
            </li>
            {% endif %}

        {% endfor %}
        <!-- 다음 페이지 -->
        {% if page_obj.has_next %}
        <li class="page-item">
            <a class="page-link" href="?page={{ page_obj.next_page_number }}">다음</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
        </li>
        {% endif %}
    </ul>
    <a href="{% url 'main:question_create' %}" class="btn btn-primary">질문 등록</a>
    </div>
{% endblock %} #}

 

 

- 결과 화면

 

☑︎ 문제점

• 1. 각 페이지 번호가 1~10로 고정 되어 있는 점

• 2. 페이지 번호가 너무 많아져 정상적으로 표기가 안되는 점

 

# 사용자 지정 템플릿 필터 만들기 (sub: 빼기 연산을 위한 필터)

1. 템플릿 필터 폴더 만들기: 템플릿 폴더와 같은 위치에 templatetags 폴더 생성

2. filter.py 생성

from django import template

register = template.Library()


@register.filter
def sub(value, arg):
    return value - arg

 

3. 적용할 템플릿 상단에 {% load 'filter 파일명' %} 작성

 

{% extends 'base.html' %}
{% load custom_filter %}
{% block page_title %} {{ page_title }} {% endblock %}
{% block content %}
    <div class="container my=3">
    <table class="table">
        <thead>
        <tr class="thead-dark">
            <th>번호</th>
            <th>제목</th>
            <th>작성일시</th>
        </tr>
        </thead>
        <tbody>
        {% if page_obj %}
            {% for question in page_obj %}
                <tr>
                	<!-- 1. 문제점 해결책 각 페이지 번호 -->
                    <!-- 번호 = 전체 건수 - 시작인덱스 - 현재인덱스 +1 -->
                    <td>{{ paginator.count | sub:page_obj.start_index|sub:forloop.counter0|add:1 }}</td>
                    <td>
                        <a href="{% url 'main:detail' question.id %}">{{ question.subject }}</a>
                        {% if question.answer_set.count > 0 %}
                            <span class="text-danger small ml-2">{{ question.answer_set.count }} </span>
                        {% endif %}
                    </td>
                    <td>{{ question.create_date }}</td>
                </tr>
            {% endfor %}
        {% else  %}
            <tr>
                <td colspan="3">질문이 없습니다.</td>
            </tr>
        {% endif %}
        </tbody>
    </table>
    <!-- page nav -->
    <ul class="pagination justify-content-center">
       <!-- 이전 페이지-->
        {% if page_obj.has_previous %}
        <li class="page-item">
            <a class="page-link" href="?page={{ page_obj.previous_page_number }}">이전</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">이전</a>
        </li>
        {% endif %}
        <!-- 페이지 리스트 -->
        {% for page_number in paginator.page_range %}
        <!-- 2. 문제점 해결 페이지 버튼 수 조정 -->
        <!-- 페이지 수 조정 -->
        {% if page_number >= page_obj.number|add:-5 and page_number <= page_obj.number|add:5 %}
            {% if page_number == page_obj.number %}
            <li class="page-item active" aria-current="page">
                <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
            </li>
            {% else %}
            <li class="page-item">
                <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
            </li>
            {% endif %}
        {% endif %}
        {% endfor %}
        <!-- 다음 페이지 -->
        {% if page_obj.has_next %}
        <li class="page-item">
            <a class="page-link" href="?page={{ page_obj.next_page_number }}">다음</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
        </li>
        {% endif %}
    </ul>
    <a href="{% url 'main:question_create' %}" class="btn btn-primary">질문 등록</a>
    </div>
{% endblock %} #}

 

- 결과 화면

 

 

 

출처: wikidocs.net/book/4223 (점프 투 장고)

프로젝트의 전반적인 설정을 담당하는 곳은 settings.py 이고 각 앱마다 필요한 항목의 대한 설정을 할 수 있는 곳은 apps.py 파일이다.

장고 공식 문서를 보면 apps.py 파일의 용도로 앱 이름에 대한 별칭(verbose_name)을 정의하거나 시그널(signal) 수신자를 등록하는 예시를 보여주고 있다.

 

현재 실습하고 있는 polls, books 라는 앱 이름을 하드코딩하고 있는데 apps.py를 활용하면 이 부분을 개선할 수 있다.

3가지 파일(apps.py, view.py, 템플릿 파일)을 수정한다.

 

- app.py

from django.apps import AppConfig


class BooksConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'books'
    verbose_name = 'Book-Author-Publisher App' #추가

 

- views.py

# views.py
from django.http import  HttpResponse
from django.views.generic import TemplateView
from django.apps import apps

class HomeView(TemplateView):
    template_name = "home.html"

    def get_context_data(self, **kwargs):
        context = super(HomeView, self).get_context_data(**kwargs)
        #context['app_list'] = ['polls', 'books']
        dicVerbose = {}
        for app in apps.get_app_configs():
            if 'site-packages' not in app.path:
                dicVerbose[app.label] = app.verbose_name
        context['verbose_dict'] = dicVerbose
        return context

if 'site-package' not in app.path :

외부 라이브러의 물리적 디렉토리 구조는 'site-packages'를 포함하고 있으므로, 내가 추가로 구성한 앱만을 딕셔너리에 추가하기 위함이다.

 

- home.html

{% extends "base.html" %}

{% block content %}
    <h2>Django Applications</h2>
    <ul>
        {% for key, value in verbose_dict.items %}
            <li><a href="{% url key|add:':index' %}">{{ value }}</a></li>
        {% endfor %}
    </ul>
{% endblock content %}

 

'Django' 카테고리의 다른 글

17. 로그인, 로그아웃, 회원가입 기능 구현  (0) 2021.04.15
16. 게시판 페이징 기능 구현하기  (0) 2021.04.15
14. 폼 클래스  (0) 2021.04.08
13. 템플릿 상속  (0) 2021.04.08
12. Template 주석, HTML 이스케이프  (0) 2021.04.08

- polls/forms.py

from django import forms

class NameForm(forms.Form):
    your_name = forms.CharField(label='너의 이름은', max_length=100)

 

- polls/views.py

def get_name(request):
    # POST 방식이면, 데이터가 담긴 제출된 폼으로 간주합니다.
    if request.method == 'POST':
        # request에 담긴 데이터로, 클래스 폼을 생성합니다.
        form = NameForm(request.POST)
        # 폼에 담긴 데이터가 유효한지 체크합니다.
        if form.is_valid():
            # 폼 데이터가 유효하면, 데이터는 clean_data로 복사됩니다.
            new_name = form.cleaned_data['your_name']
            # 로직에 따라 추가적인 처리를 합니다.
            print(new_name)
            # 새로운 URL로 리다이렉션시킵니다.
            return HttpResponseRedirect(reverse('polls:index'))
    # POST 방식이 아니면(GET 요청임).
    # 빈 폼을 사용자에게 보여줍니다.
    else:
        form = NameForm()

    return render(request, 'polls/name.html', {'form': form})

 

- polls/urls.py

path('yourname/', views.get_name, name='yourname')

 

- polls/template/polls/name.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="{% url 'polls:yourname' %}" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="제출">

</form>

</body>
</html>

 

- 결과 화면

 

출처: 한빛미디어, Django로 배우는 쉽고 빠른 웹 개발 파이썬 웹 프로그래밍

템플릿 상속을 통해서 템플릿 코드를 재사용 할 수 있다. 부모 템플릿은 템플릿의 뼈대를 만들어주고 {% block %} 태그를 통해 하위로 상속해줄 부분을 지정해주면, 자식 템플릿은 부모 템플릿의 뼈대는 그대로 재사용하고 이 때는 {% extends "부모 템플릿.html" %} 태그를 사용하고 {% block %} 부분만 채워주면 된다.

 

block.super는 부모 그대로 사용한다는 의미이다.

 

- base.html

<!-- base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    {#  {% load static %} #}
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
    <title>{% block title %}My Amazing Site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Project_Home</a></li>
            <li><a href="/admin/">Admin</a></li>
        </ul>
        {% endblock %}
        <br>
    </div>

    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

 

- base_books.html

<!-- base_books.html -->
{% extends "base.html" %}

{% block title %}Books Application Site{% endblock %}

{% block sidebar %}
{{ block.super }}
<ul>
    <li><a href="/books/">Books_Home</a></li>
</ul>
{% endblock %}

 

- index.html

<!-- book/index.html -->
{% extends "base_books.html" %}

{% block content %}
    <h2>Books Management System</h2>
    <ul>
        {% for modelname in model_list %}
{% with "books:"|add:modelname|lower|add:"_list" as urlvar %}
            <li><a href="{% url urlvar %}">{{ modelname }}</a></li>
{% endwith %}
        {% endfor %}
    </ul>
{% endblock content %}

 

-결과 화면

 

 

출처: 한빛미디어, Django로 배우는 쉽고 빠른 웹 개발 파이썬 웹 프로그래밍

템플릿 주석을 작성하는 방법은 2가지 방법이 있다.

1번째는 한줄 주석 2번째는 여러줄 주석

 

- 한줄 주석

{# Hello #}world
{# {% if foo %}bar {% else %} #} 

 

- 여러줄 주석

{% comment "note" %}
<p>Commented out text here</p>
{% endcomment %}

 

- HTML 이스케이프

템플릿 코드를 렌더링하여 HTML 텍스트를 만들 때, 템플릿 변수에 HTML 태그가 들어 있는 경우에 그대로 렌더링하면 원하는 결과가 나오지 않을 수 있다. 그리고 이점을 이용하여 XSS 공격이 이루어진다.

 

장고는 자동 이스케이프 기능을 제공하고 있다.

  • <  -> &lt;
  • >  -> &gt;
  • ' (single quote)  -> &#39;
  • " (double quote)  -> &quot;
  • &  -> &amp;

그렇기 때문에 자동 이스케이프 기능을 비활성화 할 수 있는 2가지 방법이 있다.

 

- safe 필더를 이용

This will not be escaped: {{ data|safe }}

safe 필터는 템플릿 변수에만 영향을 미친다.

 

- {% autoescape %} 태그를 이용

{% autoescape off %}
Hello {{ name }}
{% endautoescape %}

autoescape 태그 영역안에서는 자동 이스케이프 기능을 비활성화 한다.

추가적으로 필터의 인자에 사용되는 스프링 리터럴에는 자동 이스케이프 기능이 적용되지 않는다.

 

출처: 한빛미디어, Django로 배우는 쉽고 빠른 웹 개발 파이썬 웹 프로그래밍

'Django' 카테고리의 다른 글

14. 폼 클래스  (0) 2021.04.08
13. 템플릿 상속  (0) 2021.04.08
11. Template Tag(템플릿 태그)  (0) 2021.04.08
10. Template 구성 - 파일 구조, 템플릿 필터  (0) 2021.04.08
9. View 구성 - 함수형 뷰 구성  (0) 2021.04.08

템플릿 태그는 {% tag %} 형식을 가진다.

 

- {% for %} 태그

<ul>
{% for athlete in athlete_list %}
	<li>{{ athlete.name }}<li>
{% endfor %}
</ul>

리스트에 담겨 있는 항목들을 순회하면서 출력

변수명 설명
forloop.counter 현재까지 루프를 실행한 루프 카운트(1부터 카운트함)
forloop.counter() 현재까지 루프를 실행한 루프 카운트(0부터 카운트함)
forloop.revcounter 루프 끝에서 현재가 몇 번째인지 카운트한 숫자(1부터 카운트)
forloop.revcounter() 루프 끝에서 현재가 몇 번째인지 카운트한 숫자(0부터 카운트)
forloop.first 루프에서 첫 번째 실행이면 True 값을 가짐
forloop.last 루프에서 마지막 실행이면 True 값을 가짐
forloop.parentloop 중첩된 루프에서 현재의 루프 바로 상위의 루프를 의미함

 

- {% if %} 태그

{% if athlete_list %}
	Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
	Athletes should be out of the locker room soon!
{% else %}
	No athletes.
{% endif %}

변수를 평가하여 조건이 True이면 바로 아래의 문장이 출력된다. 

{% if %} 태그에는 불린 연산자를 사용할 수 있다. ex) and, or, not, and not, ==, !=, <, >, <=, >=, in, not in

 

- {% csrf_token %} 태그

<form action="." method="post">
	{% csrf_token %}

POST 방식의 <form>을 사용하는 템플릿 코드에서는 CSRF 공격을 방지하기 위해 이 태그를 사용한다.

이 태그를 사용하면 내부적으로 CSRF 토크값의 유효성을 검증하여 만일 토큰 검증에 실패하면 사용자에게 403에러는 보여준다.

한 가지 주의할 점은 CSRF 토큰값이 유출될 수도 있으므로, 외부 URL로 보내는 <form>에는 사용하지 않도록 한다.

 

※CSRF 공격이란

CSRF(Cross-Site Request Forgery)는 사이트 간 요청 위조 공격이라는 뜻이다. 특정 웹 사이트에서 이미 인증을 받은 사용자를 이용하여 공격을 시도한다. 인증을 받은 사용자가 공격 코드가 삽입된 페이지를 열면 공격 대상이 되는 웹 사이트는 위조된 공격 명령이 믿을 수 있는 사용자로부터 발송된 것으로 판단하게 되어 공격을 받게 된다.

 

- {% url %} 태그

{% url 'namespace:view-name' arg1 arg2 %}
  • namespace : urls.py 파일의 include() 함수 또는 app_name 변수에 정의한 이름 공간
  • view-name : urls.py 파일에서 정의한 URL 패턴 이름
  • argN: 뷰 함수에서 사용하는 인자로, 없을 수도 있고 여러 개인 경우 공백으로 구분

 

<form action={% url 'polls:vote' question.id %} method="post">

주된 목적은 템플릿 코딩을 할때 URL을 하드코딩하는 것을 방지 하기 위해서이다. 이 코드는 다음의 코드와 유사한 코드이다.

 

<form action="/polls/3/vote/" method="post">

 

 - {% with %} 태그

{% with total=business.employess.count %}
	{{ total }} people works at business
{% endwith %}

 

with 태그는 with 영역에서만 유효한 변수를 정의 하여 사용할 수 있다. 이 태그는 DB를 조회하는 것과 같은 부하가 큰 동작을 결과를 저장해 둠으로써 동일한 동작에 대한 부하를 줄이기 위해 사용한다.

 

- {% load %} 태그

{% load somelibrary package.otherlibrary %}

사용자 정의 태그 및 필터를 로딩해준다.

 

출처: 한빛미디어, Django로 배우는 쉽고 빠른 웹 개발 파이썬 웹 프로그래밍

template 파일 구성

Template File은 다른 동일한 이름의 리소스와의 구분하기 위해서 [app_name]/templates/[app_name]의 디렉토리 하위에 존재한다.

 

 

- detail.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>상세 페이지</title>
</head>
<body>
<h1>{{ question.question_text}}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p> {% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"/>
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
    {% endfor %}
<input type="submit" value="투표하기">
</form>

</body>
</html>

 

-템플릿 변수

{{ variable }}

{{}} 문자로 변수를 감싸 해당 변수값을 출력할 수 있다. 변수명은 일반 프로그램밍의 변수 조건과 유사하게 문자, 숫자, 밑줄을 사용하여 이름을 정의할 수 있다.

 

장고의 템플릿 시스템에서 도트(.)는 파이썬 언어와는 조금 다르다. 템플릿 문법에서 도트(.)를 만나면 장고는 다음의 순서를 따른다.

ex) book.name

  • 사전 타입인지 확인 book['name']
  • 속성 값인지 확인 book.name
  • 리스트인지 확인 book[name]

-템플릿 필터

{{ name|lower}}

name의 변수값을 소문자로 변환하여 출력

 

{{ text|escape|linebreaks }}

text 변수값 중에서 특수 문자를 제거하고 , 그 결과에 <p>를 붙여준다.

 

{{ bio|truncatewords:30 }}

bio의 변수값 중에서 앞에 30개의 단어만 보여주고, 줄 바꿈 문자는 모두 제거

 

{{ list|join:" // " }}

필터의 인자에 빈칸이 있는 경우 따옴표로 묶어준다. list가 ['a','b','c']라면 결과는 "a // b // c"가 된다.

 

{{ value|default:"nothing"}}

value 변수값이 False이거나 없는 경우 "nothing"으로 출력

 

{{ value|length }}

value 값의 길이를 반환한다. 변수 값이 스트링이나 리스트인 경우도 가능하다.

 

{{ value|striptags }}

value 변수값에서 HTML 태그를 모두 없애줍니다. 그러나 완벽하지 않다.

 

{{ value|pluralize }}

value 변수값이 복수명 접미사 s를 붙여준다. 

 

{{ value|pluralize:"es" }} 또는 {{ value|pluralize:"ies" }}

value 변수값이 복수이면 접미사 es 또는 ies를 붙여준다.

 

{{ value|add:"2" }}

변수에 더하기를 할 수 있습니다. 데이터 타입에 따라 결과가 다르므로 주의해야 한다.

 

{{ first|add:second }}
  • first="python", second="django"라면 결과는 "pythondjango"
  • first=[1,2,3], second=[4,5,6] 이라면 결과는 [1,2,3,4,5,6]
  • first="5", second="10"이라면 결과는 15

 

 

출처: 한빛미디어, Django로 배우는 쉽고 빠른 웹 개발 파이썬 웹 프로그래밍

+ Recent posts