📌폼 모양 꾸미기

15장에서 CreateViewUpdateView를 사용하여 포스트 작성 페이지와 포스트 수정 페이지를 만들었다. 하지만 아래 사진처럼 입력 폼이 한쪽으로 치우쳐 있어서 모양이 예쁘진 않았다.

이러한 문제는 django-crispy-forms을 사용하여 아주 쉽게 해결할 수 있다.

공식문서는 아래 링크를 통해 볼 수 있다. 해당 포스트에 나오지 않은 더 자세한 내용이 궁금하면 들어가보자.

https://django-crispy-forms.readthedocs.io/en/latest/

📖django-crispy-forms 설치하기

우선 django-crispy-forms을 다음 명령어를 통해서 설치하자.

pip install django-crispy-forms

설치가 완료됐으면 settings.pyINSTALLED_APPS에 등록을 하자. 다음과 같이 crispy_formscrispy_bootstrap4를 추가하자. 그리고 settings.py의 맨 아래에 CRISPY_TEMPLATE_PACK = 'bootstrap4'를 추가하자.

# settings.py

(..생략..)
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',
    
    # 추가
    'crispy_forms',
    'crispy_bootstrap4',

    'blog',
    'single_pages',
    
(..생략..)

CRISPY_TEMPLATE_PACK = 'bootstrap4'

(..생략..)

📖crispy_forms 적용하기

crispy_forms를 적용하기 위해 post_form.html의 맨 위에 {% load crispy_forms_tags %}를 추가한다. 그리고 {{ form }}으로 되어 있는 것을 {{ form | crispy }}로 바꿔준다.

{% extends 'blog/base_full_width.html' %}
{% load crispy_forms_tags %}
{% block head_title %}Create Post - Blog{% endblock %}

{% block main_area %}
    <h1>Create New Post</h1>
    <hr/>
    <form method="post" enctype="multipart/form-data">{% csrf_token %}
    	<table>  
      	{{ form | crispy }}
        	<tr>
            	<label for="id_tags_str">Tags:</label>
            	<input type="text" name="tags_str" id="id_tags_str">
		        <tr>
      	<table>
        <button type="submit" class="btn btn-primary float-right">Submit</button>
    </form>
{% endblock %}

그러면 웹 브라우저를 열어 포스트 작성 페이지에 들어가보면 다음과 같이 폼 입력란이 바뀐 것을 볼 수 있다.

하지만 아래 Tags는 바뀌지 않았다. 페이지 소스를 통해 form 태그가 어떻게 구성되어있는지 봐보자.

crispy-form이 적용된 field 들은 div로 묶여있는 것을 볼 수 있다. 이처럼 Tags 입력란도 crispy-form처럼 바꾸기 위해서는 div로 똑같이 묶어주고, id도 똑같은 형식으로 바꿔주면 된다. 또한 input의 class도 바꿔준다.

{% extends 'blog/base_full_width.html' %}
{% load crispy_forms_tags %}
{% block head_title %}Create Post - Blog{% endblock %}

{% block main_area %}
    <h1>Create New Post</h1>
    <hr/>
    <form method="post" enctype="multipart/form-data">{% csrf_token %}
        {{ form | crispy }}
        <div id="div_id_tags_str">
            <label for="id_tags_str">Tags:</label>
            <input type="text" name="tags_str" id="id_tags_str" class="textinput textInput form-control">
        </div>
        <br/>
        <button type="submit" class="btn btn-primary float-right">Submit</button>
    </form>
{% endblock %}

그러면 다음과 같이 Tag 입력란도 crispy-form이 적용된다.




📌마크다운 적용하기

django-markdownx를 설치하여 마크다운 문법을 적용해보자.

django markdownx에 대한 자세한 내용은 공식 웹사이트를 참고하자. https://neutronx.github.io/django-markdownx/

django markdownx 설치

터미널에 다음과 같이 입력하여 django markdownx를 설치하자.

pip install django-markdownx

이후 settings.py를 열어 INSTALLED_APPS에 markdownx를 추가한다.

# settingspy

(..생략..)
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',
    'crispy_forms',
    'crispy_bootstrap4',

	'markdownx',

    'blog',
    'single_pages',
]
(..생략..)

django markdownx는 urls.py에 경로를 추가해야 원활하게 작동한다. 다음과 같이 프로젝트 폴더의 urls.py를 열어 경로를 추가하자. 여기서 주의할 점은 app이름/urls.py가 아니라 장고 프로젝트 폴더/urls.py에 추가해야한다.

from django.contrib import admin
from django.urls import path, include

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
    path('', include('single_pages.urls')),
    path('markdownx/', include('markdownx.urls')), # 추가!!
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

이제 Post 모델의 content 필드를 MarkdownxField로 바꿔준다.

from django.db import models
from django.contrib.auth.models import User
from markdownx.models import MarkdownxField
import os

(..생략..)

class Post(models.Model):  # models 모듈의 Model 클래스를 확장하여 만든 클래스
    """
    포스트의 형태를 정의하는 Post 모델
    제목(title), 내용(content), 작성일(created_at), 작성자 정보(author) 등등
    """
    title = models.CharField(max_length=30)  # CharField : 문자를 담는 필드
    hook_text = models.CharField(max_length=100, blank=True)
    content = MarkdownxField() # <<<
    (..생략..)
    

모델의 필드 형식이 변경되었으니 마이그레이션을 하여 데이터베이스에 등록해준다.

이후 post_form.htmlpost_update_form.html{{ form.media }}를 추가한다. 이는 장고가 form에 필요한 css 및 JS 파일을 포함하는 HTML 태그를 자동으로 생성하게끔 해준다. 즉, form field에 마크다운 편집기를 제공하는 Markdownx를 사용하는 경우, 편집기가 작동하기 위한 미디어 파일이 필요한데, form.media는 필요한 미디어 파일을 자동으로 생성 및 제공한다.

<!-- post_form.html -->

{% extends 'blog/base_full_width.html' %}
{% load crispy_forms_tags %}
{% block head_title %}Create Post - Blog{% endblock %}

{% block main_area %}
   <h1>Create New Post</h1>
   <hr/>
   <form method="post" enctype="multipart/form-data">{% csrf_token %}
       {{ form | crispy }}
       <div id="div_id_tags_str">
           <label for="id_tags_str">Tags:</label>
           <input type="text" name="tags_str" id="id_tags_str" class="textinput textInput form-control">
       </div>
       <br/>
       <button type="submit" class="btn btn-primary float-right">Submit</button>
   </form>
   {{ form.media }}
{% endblock %}

포스트 작성 페이지에서 내용을 입력해보면 form.media에 의해 실시간을 마크다운이 적용되어 렌더링된 모습이 아래에 나타난다.

📖Submit시 마크다운 문법으로 보이도록 하기

하지만 아직 위 화면에서만 마크다운 내용이 보이는 것이지, 제출시에는 마크다운 문법으로 적용된 모습이 보이지 않는다.

이를 해결하기 위해선 화면에 포스트를 렌더링할 때 마크다운 문법으로 작성된 content 필드 값을 HTML로 변환하는 작업이 필요하다. 해당 기능 역시 markdownx에서 제공한다.

Post 클래스에 새로운 메서드를 다음과 같이 만들자.

# blog/models.py

from django.db import models
from django.contrib.auth.models import User
from markdownx.models import MarkdownxField

from markdownx.utils import markdown # 추가

import os

(..생략..)

class Post(models.Model):  # models 모듈의 Model 클래스를 확장하여 만든 클래스
    """
    포스트의 형태를 정의하는 Post 모델
    제목(title), 내용(content), 작성일(created_at), 작성자 정보(author) 등등
    """
    title = models.CharField(max_length=30)  # CharField : 문자를 담는 필드
    hook_text = models.CharField(max_length=100, blank=True)
    content = MarkdownxField() # <<<
    (..생략..)
    
    def get_content_markdown(self): # 추가
        return markdown(self.content)
    

새로운 메서드인 get_content_markdown()은 Post 레코드의 content 필드에 저장되어 있는 텍스트를 마크다운 문법을 적용하여 HTML로 만들어주는 역할을 한다.

이후 이를 템플릿에 반영해주자. post_detail.html에서 content를 그대로 가져오던 부분 post.contentpost_list.html에서 p.content로 되어있는 부분을 다음과 같이 수정한다.

이렇게 해야 content 필드에 마크다운 문법에 따라 저장되어 있는 텍스트를 HTML로 변환한 후 가져온다. | safe를 넣는 이유는 HTML 이스케이핑을 방지하기 위함이다.

<!-- post_detail.html -->


(..생략..)
 <!-- Post Content -->

	<!-- 수정 -->
    <p>{{ post.get_content_markdown | safe }}</p>
	<!-- -->

    {% if post.tags.exists %}
    	<i class="fas fa-tags"></i>
    	{% for tag in post.tags.iterator %}
    		<a href="{{ tag.get_absolute_url }}"><span class="badge badge-pill badge-light">{{ tag }}</span></a>
    	{% endfor %}
    	<br/>
    	<br/>
    {% endif %}

(..생략..)
<!-- post_list.html -->

(..생략..)
    <div class="card-body">
        {% if p.category %}
        	<span class="badge badge-secondary float-right">{{ p.category }}</span>
        {% else %}
        	<span class="badge badge-secondary float-right">미분류</span>
        {% endif %}
        	<h2 class="card-title h4">{{ p.title }}</h2>
        {% if p.hook_text %}
        	<h5 class="text-muted">{{ p.hook_text }}</h5>
        {% endif %}
        
      	<!-- 수정 -->
      	<p class="card-text">{{ p.get_content_markdown | truncatewords_html:45 | safe }}</p>
      	<!--   -->
        
      	{% if p.tags.exists %}
        	<i class="fas fa-tags"></i>
        	{% for tag in p.tags.iterator %}
        	<a href="{{ tag.get_absolute_url }}"><span class="badge badge-pill badge-light">{{ tag }}</span></a>
        	{% endfor %}
        	<br/>
        	<br/>
        {% endif %}
        <a class="btn btn-primary" href="{{ p.get_absolute_url }}">Read more →</a>
    </div>

(..생략..)

post_list.html에서는 조금 더 추가적으로 수정해야한다. 이전과 달리 정보가 HTML로 넘어오기 때문에 truncateworkdstruncateworkds_html로 추가 수정한다.

그러면 포스트 상세페이지와 포스트 목록 페이지에서 content 내용이 마크다운이 적용된 모습을 볼 수 있다.





📌 회원가입과 로그인 기능 구현

django-allauth를 통해서 이메일을 통한 회원가입 및 로그인, 구글, 카카오 로그인을 쉽게 구현할 수 있다.

공식 웹 사이트에서 더 자세한 내용과 추가적인 템플릿을 제공하니 참고하자.
https://django-allauth.readthedocs.io/en/latest/

📖django-allauth 설치

pip install django-allauth를 통해 설치하자.

이후 마찬가지로 settings.py를 열어 INSTALLED_APPS에 내용을 다음과 같이 추가한다.

필자는 구글 로그인을 사용할거라서 allauth.socialaccount.providers.google를 추가적으로 넣었다.

또한 AUTHENTICATION_BACKENDS 설정과 SITE_ID 설정 등을 다음과 같이 맨 아래에 추가하자.


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',
    'crispy_forms',
    'crispy_bootstrap4',
    'markdownx',
    
     # 추가
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google', # 구글 로그인을 사용.
     
     
    'blog',
    'single_pages',
]

(..생략..)

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
)  # 추가

SITE_ID = 1 # 추가
ACCOUNT_EMAIL_REQUIRED = True   # 회원가입시 이메일 필요 여부, 추가
ACCOUNT_EMAIL_VERIFICATION = 'none'  # 이메일 검증 여부, 추가
SOCIALACCOUNT_LOGIN_ON_GET = True  # 추가
LOGIN_REDIRECT_URL = '/blog/'  # 로그인 후 리다이렉트될 경로

이후 프로젝트 폴더의 urls.py를 열어 django-allauth가 사용할 수 있는 URL 경로를 추가한다.

from django.contrib import admin
from django.urls import path, include

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
    path('', include('single_pages.urls')),
    path('markdownx/', include('markdownx.urls')),  
    path('accounts/', include('allauth.urls')),  # 추가
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

django-allauth를 사용하려면 데이터베이스에도 반영을 해줘야한다. 간단히 터미널에서 python manage.py migrate만 입력하자.

📖django-allauth 로 구글 로그인 구현

1. 구글 개발자콘솔에서 새 프로젝트와 클라이언트 만들기

구글 개발자 콘솔에 접속한 후 새 프로젝트를 만들자. console.cloud.google.com

프로젝트 이름은 원하는 이름으로 하면 된다. 이후 OAuth 동의 화면에서 User Type을 "외부"로 선택하고 만들기를 누른다.

그러면 "앱 등록 수정" 페이지가 나오는데, "앱 이름", "사용자 지원 이메일", "개발자 연락처 정보"를 채우고 "저장 후 계속" 버튼을 누른다.

이후 왼쪽 메뉴에서 "사용자 인증 정보"를 선택한 다음 오른쪽 상단에서 "+ 사용자 인증 정보 만들기" -> "OAuth 클라이언트 ID"를 클릭한다.

애프리케이션 유형으로 "웹 애플리케이션"을 선택하고, 이름과 URI를 지정해야한다. 아직 서버도 없고 도메인도 없으니 로컬서버 http://127.0.0.1:8000를 "승인된 자바스크립트 출처 URI"에 입력한다. 그리고 "승인된 리디렉션 URI"에는 http://127.0.0.1:8000/accounts/google/login/callback/을 입력하고 만들기 버튼을 클릭한다.

그러면 클라이언트 ID와 클라이언트 보안 비밀번호를 알려줄텐데, 이를 기억해두자.

이제 로컬 서버의 관리자 페이지를 열어보면 다음과 같이 "SITES"와 "SOCIAL ACCOUNTS" 메뉴가 추가되어있다.

"SITES -> Sites"로 들어가서 "example.com"을 수정하자. 현재는 SITE_ID=1에 해당하는 도메인이 "example.com"이라는 뜻이므로, 이를 로컬 서버 경로인 127.0.0.1:8000으로 수정한다.

2. Log in with Google 버튼 활성화

navbar.html을 열어 {% load socialaccount %}를 추가한다.

이후 구글 로그인 버튼에 해당하는 HTML 코드를 찾아 다음과 같이 수정한다. <button> 태그를 <a> 태그로 바꾸고, href에 링크 주소를 명시한다.

{% load socialaccount %}

(..생략..)

<div class="col-md-6">
	<a role="button" class="btn btn-outline-dark btn-block btn-sm" href="{% provider_login_url 'google' %}"><i class="fab fa-google"></i>&nbsp&nbsp Log in with Google</a>

이후 관리자 페이지에서 "SOCIAL ACCOUNTS" -> "Social applications"로 들어가 "ADD SOCIAL APPLICATION" 버튼을 클릭하여 새로운 social application을 추가해야한다.

Clined id와 Secret Key는 앞서 구글이 부여한 클라이언트 ID와 보안 비밀번호를 입력한다.

그럼 끝!

3. 로그인 상태에서 Log Out 버튼 추가

현재 상태는 다음 그림과 같다. 로그인을 했지만, 로그인 했는지 모르는 상태... (로그아웃 버튼이 없음)

로그인되어 있다면 내비게이션 바에 Log In 버튼이 아니라 로그인한 계정의 username이 나오게 하고, Dropdown link로 Log Out 버튼을 구현해보자.

navbar.html에 있는 Dropdown link를 잘라내어 Log In 버튼에 대한 코드 바로 위에 붙여넣는다. 버튼에 들어갈 텍스트는 {{ user.username }}으로 수정하여 로그인한 user의 username이 출력되도록 한다.

드롭다운 메뉴로 Log Out 버튼 하나만 필요하므로 기존 메뉴 3개중 2개는 삭제한다. 남은 하나의 텍스트는 "Log Out"으로 바꾸고, href 경로도 /accounts/logout/으로 바꾸자. 그러면 django-allauth에 의해 해당 경로로 가게되면 자동으로 로그아웃이 된다.

마지막으로 로그인 상태에 따라 다른 버튼을 보여줘야한다. if문을 사용하자.

  • 로그인 X : 로그인 버튼
  • 로그인 O : UserName 및 LogOut 드롭다운 버튼

위와 같이 수정하고 웹 브라우저에 가서 구글 아이디로 로그인 하면 다음과 같이 로그인 한 상태의 내비게이션 바가 바뀌게 된다.


📖이메일 로그인과 회원가입 버튼 활성화

구글 로그인이 아닌, "이메일로 회원가입"과 "이메일로 로그인" 기능을 구현해보자. 해당 기능들 또한 django-allauth에서 제공한다.

마찬가지로 navbar.html을 수정해야 한다.

아래 사진과 같이 "Log in with E-mail" 버튼은 <a> 태그로 수정하고, href="/accounts/login/"로 로그인 페이지 링크를 추가한다.

"Sign Up with E-mail" 역시 <a> 태그로 수정하고 href="/accounts/signup/"으로 회원가입 페이지 경로를 추가한다.

그러면 이제 로그인 모달의 어떤 버튼을 클릭해도 정상적으로 작동한다.


📌로그인 페이지 커스텀하기 (작성예정)

위 사진을 보면 로그인과 회원가입, 그리고 사진엔 없지만 로그인 모달과 로그아웃 페이지의 양식이 배포를 하여 서비스 하기엔 너무 구리다(?).....

Django-allauth 커스텀 탬플릿을 적용하면 해결할 수 있다.

해당 내용은 차차 포스트 하도록 하겠다...


📌정리

  • 장고의 외부 라이브러리 활용방법

  • django-crispy-forms : 장고 폼 페이지 꾸미기

  • django-markdownx : 장고에 마크다운 적용

  • django-allauth : 장고 로그인 기능 구현

profile
AI 서비스 엔지니어를 목표로 공부하고 있습니다.

0개의 댓글