사용자가 이메일을 통해 게시물 추천을 보내, 다른 사라들과 블로그 게시물을 공유할 수 있도록 해보자.
쟝고에는 양식을 쉽게 만들 수 있는 내장 폼 프레임워크가 있다.
폼 프레임워크를 사용하면 폼의 필드를 정의하고 표시 방법을 지정하고 입력 데이터의 유효성을 검사하는 방법을 간단하게 지정할 수 있다. 쟝고 폼 프레임워크는 HTML에서 폼을 렌더링하고 데이터를 처리하는 유연한 방법을 제공한다.
쟝고에는 폼을 작성하기 위한 두 가지 클래스가 있다.
Form
ModelForm
blog 애플리케이션의 디렉토리 내에 forms.py 파일을 만들고 다음 코드를 추가하자.
from django import forms
class EmailPostForm(forms.Form):
name = forms.CharField(max_length=25)
email = forms.EmailField()
to = forms.EmailField(
comments=forms.CharField(required=False, widget=forms.Textarea)
EmailPostForm
폼은 기본 Form 클래스를 상속하고, 서로 다른 필드 유형을 사용해 그에 따라 데이터를 검증한다.
폼에는 다음 필드들이 있다.
name
CharField
의 인스턴스이다. 게시글을 보내는 사람의 이름으로 사용한다.email
EmailField
의 인스턴스이다. 게시글 추천을 보내는 사람의 이메일로 사용한다.to
EmailField
의 인스턴스이다. 게시글 추천 이메일을 받을 수신자의 이메일로 사용한다.comments
CharField
의 인스턴스이다. 게시글 추천 이메일에 포함할 코멘트로 사용한다.required
를 False로 설정해 이 필드를 선택 사항으로 만들고 필드를 렌더링할 커스텀 위젯을 지정했다.각 필드 유형에는 필드가 HTML에서 렌더링되는 방식을 결정하는 기본 위젯이 있다.
name
필드는 CharField
의 인스턴스로 HTML의 <input type="text">
엘리먼트로 렌더링된다.
위젯 속성으로 기본 위젯을 재정의할 수 있다.
comment
필드에서 Textarea
위젯을 사용해 기본 <input>
엘리먼트 대신 <textarea>
엘리먼트로 표시한다.
필드 유효성 검사는 필드의 유형에 따라 다르다. email
과 to
필드는 EmailField
이므로 두 필드 모두 유효한 이메일 주소가 필요하다. 그렇지 않으면 forms.ValidationError
예외가 발생하고 폼이 유효하지 않게 된다. 다른 필드들도 폼 필드 유효성 검사를 수행하는데, name 필드의 최대 길이는 25
또는 comment 필드는 선택 사항
과 같은 것들이다.
이메일을 통해 게시글을 추천하는 폼을 정의했으니,
이제 폼의 인스턴스를 생성하고 전송된 폼의 값들을 처리하기 위한 뷰가 필요하다.
views.py에 아래의 코드를 추가하자.
from .forms import EmailPostForm
def post_share(request, post_id):
post = get_object_or_404(Post, id=post_id, status=Post.Status.PUBLISHED)
if request.method == 'POST':
form = EmailPostForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html', {'post': post, 'form': form})
request
객체와 post_id
를 매개 변수로 사용하는 post_share
뷰를 정의했다.
get_object_or_404()
함수를 사용해 id로 게시된 게시글을 조회한다.
폼을 표시하고 수신된 데이터를 처리하는데 모두 동일한 뷰를 사용한다.
HTTP 요청 메서드를 사용하면 폼의 값들이 수신된 것인지 구분할 수 있다.
GET
요청은 빈 폼이 사용자에게 표시되어야 함을 나타내고, POST
요청은 폼의 값들이 수신되었음을 나타낸다.
request.method == ‘POST’
를 사용해 두 조건을 구분한다.
폼을 표시하고 수신된 폼 값을 처리하는 프로세스는 아래와 같다.
GET
요청을 받는다. 이 경우 새로운 EmailPostForm
인스턴스가 생성되어 forms 변수에 저장된다. 이 폼 인스턴스틑 템플릿에 비어 있는 폼을 표시하는데 사용된다.form = EmailPostForm()
POST
를 통해 전송하면 request.POST
에 포함된수신 데이터를 사용해 폼 인스턴스가 생성된다.form = EmailPostForm(request.POST)
is_valid()
메서드를 사용해 유효성 검사를 수행한다.is_valid()
는 False를 반환한다.forms.errors
로 얻을 수 있다.form.cleaned_data
로 조회할 수 있다.폼을 표시하고 전송된 폼 데이터를 처리하는 뷰를 구현했으니, 이제 쟝고에서 이메일을 보내는 방법을 알아보고 그 기능을 post_share
뷰에 추가하자.
쟝고로 이메일을 보내려면 로컬 SMTP 서버가 있거나, 이메일 서비스 공급자와 같은 외부 SMTP 서버에 액세스해야 한다. 다음 설정을 사용하면 쟝고로 이메일을 보내는 SMTP 구성을 정의할 수 있다.
EMAIL_HOST
EMAIL_PORT
EMAIL_HOST_USER
EMAIL_HOST_PASSWORD
EMAIL_USE_TLS
EMAIL_USE_SSL
나는 GMAIL 계정과 함께 Google의 SMTP 서버를 사용할 것이다.
GMAIL 계정이 있다면, settings.py 파일을 열고 아래 코드를 추가하자.
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'gmail 주소'
EMAIL_HOST_PASSWORD = 'gmail 앱 비밀번호'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
Gmail 대신 SendGrid 또는 Amazon Simple Email Service와 같이 사용자 고유의 도메인을 사용해 SMTP를 통해 이메일을 보낼 수 있는 전문 이메일 서비스를 사용할 수도 있다.
두 서비스 모두 도메인과 보낸 사람의 이메일 계정을 검증하고 이메일을 보낼 수 있는 SMTP 자격 증명을 제공한다. 쟝고 애플리케이션 django-sendgrid
와 django-ses
는 프로젝트에 SendGrid, Amazone SES를 추가하는 작업을 단순화한다.
만약 SMTP 서버를 사용할 수 없다면, settings.py 파일에 아래의 설정을 추가해 쟝고가 콘솔에 이메일을 출력하도록 지시할 수 있다.
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
이 설정을 사용하면 쟝고는 이메일을 보내는 대신 콘솔에 출력한다.
이는 aSMTP 서버 없이 애플리케이션을 테스트하는데 유용하다.
설정이 완료됐으면 파이썬 쉘에서 아래의 코드를 통해 테스트해보자.
>>> from django.core.mail import send_mail
>>> send_mail('Django mail',
... '쟝고로 보낸 이메일',
... 'gmail 주소@gmail.com',
... ['gmail 주소@gmail.com'],
... fail_silently=False)
send_mail()
함수는 subject, message, sender, recipients
목록을 필수 매개 변수로 사용한다.
선택 매개 변수 fail_silently
는 False로 설정해 이메일을 보낼 수 없는 경우 예외를 발생시키도록 지시한다.
표시되는 출력이 1이면 이메일이 성공적으로 전송된 것이다.
받은 편지함을 확인해보면 아래와 같은 메일이 온 것을 확인할 수 있다.
views.py 파일의 post_share 뷰를 편집해보자.
from django.core.mail import send_mail
def post_share(request, post_id):
post = get_object_or_404(Post, id=post_id, status=Post.Status.PUBLISHED)
sent = False
if request.method == 'POST':
form = EmailPostForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
post_url = request.build_absolute_uri(post.get_absolute_url())
subject = f"{cd['name']} recommends you read {post.title}"
message = f"Read {post.title} at {post_url}\n\n{cd['name']}\'s comments: {cd['comments']}"
send_mail(subject, message, '쥐메일주소 @gmail.com', [cd['to']])
sent = True
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html', {'post': post, 'form': form, 'sent': sent})
console.EmailBackend
대신 SMTP 서버를 사용하는 경우 이메일 주소를 실제 이메일 계정으로 변경해야 한다. 앞의 코드에서 초기 값이 False인 sent
변수를 선언했는데, 이메일이 전송된 후 이 변수를 True로 설정한다.
나중에 템플릿에서 보낸 변수를 사용해 폼이 성공적으로 전송되면 성공 메세지를 표시한다.
이메일에 게시글에 대한 링크를 포함해야 해서 get_absolute_url()
메서드를 사용해 게시글의 경로를 조회한다. 이 경로를 request.build_absoulte_uri()
의 입력으로 사용해 HTTP 스키마와 호스트 이름을 가진 완전한 URL을 만든다.
검증된 폼의 정리된 데이터를 사용해 이메일의 제목과 메시지 본문을 작성하고, 폼의 필드에 있는 이메일 주소로 메일을 보낸다.
뷰가 완료되었으니 새로운 URL 패턴을 추가해보자.
blog의 urls.py 파일을 열어 post_share URL 패턴을 추가하자.
path('<int:post_id>/share/', views.post_share, name='post_share'),
blog/template/blog/post/ 디렉터리에 새로운 파일 share.html을 만들자.
새로운 share.html 템플릿에 아래의 코드를 추가한다.
{% extends "blog/base.html" %}
{% block title %}Share a post{% endblock %}
{% block content %}
{% if sent %}
<h1>메일이 성공적으로 보내졌습니다.</h1>
<p>
"{{ post.title }}" 포스트가 {{ form.cleaned_data.to }} 에게 성공적으로 보내졌습니다.
</p>
{% else %}
<h1>"{{ post.title }}" 포스트를 메일로 공유하기.</h1>
<form method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="메일 보내기">
</form>
{% endif %}
{% endblock content %}
이메일을 통해 게시글들을 공유하기 위한 폼을 표시하고 이메일이 전송되고 나면 성공 메시지를 표시하는 데 사용하는 템플릿이다. {% if sent %}
를 통해 두 가지 경우를 구분한다.
폼을 표시하기 위해 POST 메서드를 사용해 제출하도록 HTML form 엘리먼트를 정의했다.
<form method="post">
{{ form.as_p }}
로 폼 인스턴스를 포함시켰는데, as_p
메서드를 사용해 HTML 단락을 나타내는 <p>
엘리먼트를 사용해 폼 필드들을 렌더링하도록 한다. 또한 as_ul
을 사용해 정렬되지 않은 목록으로 폼을 렌더링하거나 as_table
을 사용해 HTML 테이블로 렌더링 할 수도 있다.
다른 방법으로는 아래와 같이 폼 필드를 반복해서 각 필드를 렌더링하는 방법도 있다.
{% for field in form %}
<div>
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
템플릿 태그 {% csrf_token %}
를 추가했다.
이 태그는 CSRF(교차 사이트 요청 위조) 공격을 방지하기 위해 자동 생성된 토큰이 숨겨진 필드를 도입했다.
템플릿 태그 {% csrf_token % }
은 아래와 같이 렌더링 되는 숨겨진 필드를 생성한다.
<input type='hidden' name='csrfmiddlewaretoken' value='1q2w3e4r5t6y7u8i9o0p' />
기본적으로 쟝고는 모든 POST 요청에서 CSRF 토큰을 확인한다. 따라서 POST를 통해 제출되는 모든 폼에 csrf_token 태그를 포함해야 한다.
게시글 상세 페이지에 공유 페이지로 가는 링크를 추가하자.
{{ post.body|linebreaks }}
<p>
<a href="{% url 'blog:post_share' post.id %}">
메일로 공유하기
</a>
</p>
post_share
의 URL 링크를 추가했다. URL은 쟝고에서 제공하는 템플릿 태그 {% url %}
을 사용해 동적으로 만들어진다.
이제 브라우저에서 사이트에 접속해 게시글의 상세 페이지로 이동해보자.
아래 사진과 같이 메일로 공유하기 링크가 생겼다.
메일로 공유하기를 클릭하면 아래와 같이 이 게시글을 이메일로 공유하기 위한 폼이 있는 페이지가 표시된다.
필드에 유효한 데이터를 포함해 메일을 전송해보자.
이렇게 성공했다는 화면을 확인하고, 받은 메일함에서 메일을 확인해보자.
메일이 성공적으로 도착한 것을 확인할 수 있다.
잘못된 데이터를 포함한 폼을 제출하게 되면 폼이 모든 유효성 검사 오류와 함께 다시 렌더링된다.
대부분의 최신 브라우저는 비어 있거나 잘못된 필드가 있는 폼을 제출하지 못하도록 한다.
이는 브라우저가 폼을 제출하기 전에 속성을 기반으로 필드의 유효성을 검사하기 때문이다.
이 경우 폼은 제출되지 않으며 브라우저는 잘못된 필드에 대한 오류 메시지를 표시한다.
최신 브라우저를 사용해 쟝고의 폼 유효성 검사를 테스트하려면
와 같이novalidate
속성을 추가해 브라우저의 폼 유효성 검사를 건너뛸 수 있다.테스트를 마친 후에는 novalidate 속성을 제거해 브라우저의 폼 유효성 검사를 원래대로 되돌려 놓자.