[Do it 장고 + 부트스트랩] 10장. 페이지 구성 개선하기

정재욱·2023년 6월 10일
0
post-thumbnail
post-custom-banner

10장에서는 템플릿 파일에서 if문과 템플릿 필터를 사용하는 방법에 대하여 알아본다. 이후 해당 기능을 통해 포트스 목록, 상세 페이지에서 아쉬웠던 부분을 개선한다.

포스트 목록 페이지의 문제 파악하기

  1. 대표 이미지가 없는 포스트가 하나라도 있으면 포스트 목록 페이지에서 오류 발생

  2. 포스트 목록 페이지에서 각 포스트의 본문 내용을 모두 노출. 즉, 미리보기 내용 부재.

문제 살펴보기

위에서 언급한 바와 같이 1. 대표 이미지가 없고, 2. 임의의 긴 글이 추가된 포스트를 하나 만들자.
이후 웹 페이지에 접속해 보면 다음과 같은 에러가 발생한다.

즉, 헤드이미지가 없는 포스트가 있기 때문에 발생하는 오류다


템플릿 파일에서 if문 사용하기

템플릿(HTML)에서 if문을 사용해야 하는 방법{% if 조건A %}으로 시작해서 {% endif %}로 끝내는 것 이다.
else문과 elif도 조건문 사이에서 {% else %}, {% elif 조건B %}와 같은 방식으로 사용하면 된다.

if-else 문으로 조건에 따라 이미지 보여주기

이미지가 있을 경우

post_list.html 에서 <!-- Blog Post --> 부분을 찾자. 그리고 다음과 같이 {% if p.head_image %}{% endif %}<img> 태그를 감싸고, alt = "{{ p }} head image"로 수정하자.

html의 <img>태그에서 alt는 이미지를 보여줄 수 없을 때 이미지 대신 나타나게 하는 텍스트를 의미.

(..생략..)
{% for p in post_list %}
<!-- Blog post-->
<div class="card mb-4">
  {% if p.head_image %}
  <img class="card-img-top" src="{{ p.head_image.url }}" alt="{{ p }} head image" />
  {% endif %}
  <div class="card-body">
(..생략..)

이제 웹 브라우저에서 127.0.0.1:8000/blog/을 열어보면 이미지가 없는 포스트도 오류 없이 잘 나타난다.

이미지가 없을 경우

이미지가 없을 경우 임의의 이미지를 보여주는 기능을 구현해보자.

Lorem Picsum이라는 웹 사이트(picsum.photos/)를 사용할것이다.

해당 웹 사이트는 URL로 http://picsum.photos/가로 픽셀수/세로 픽셀수를 입력하면 임의로 지정한 크기의 이미지를 보내준다.

하지만 http://picsum.photos/가로 픽셀수/세로 픽셀수만 사용하면, 새로고침할 때 마다 이미지가 계속 바뀌는 문제가 있다.

그렇기 때문에 http://picsum.photos/seed/id 값/가로 픽셀수/세로 픽셀수를 사용하면, 해당 id 값을 가지는 위치에 매번 동일한 이미지를 나타낼 수 있다.
여기서 id 값에 포스트 레코드마다 고유한 pk 값을 넣어주면, 포스트 레코드의 pk 값이 들어가면서 포스트 마다 서로 다른 고정된 이미지를 넣을 수 있다.

post_list.html을 다음과 같이 수정하자.

(..생략..)
{% for p in post_list %}
<!-- Blog post-->
<div class="card mb-4">
  {% if p.head_image %}
  <img class="card-img-top" src="{{ p.head_image.url }}" alt="{{ p }} head image" />
  {% else %}
  <img class="card-img-top" src="http://picsum.photos/seed/{{ p.pk }}/800/200" alt="random image">
  {% endif %}
  <div class="card-body">
(..생략..)

첨부 파일이 있는 경우 다운로드 버튼 만들기

앞서 9장에서 포스트에 파일을 첨부하는 기능을 구현했었다.

하지만 해당 기능은 관리자 페이지에서만 보일 뿐, 방문자는 첨부 파일에 접근할 수 없었다.

첨부 파일 다운로드 버튼 만들기

if문을 사용해 첨부 파일이 있는 경우에는 버튼이 보이도록 post_detail.html을 수정해보자.

버튼은 부트스트랩 공식 웹싸이트의 Documentation > Components > Buttons로 들어가 마음에 드는 버튼을 선택한다.

그리고 소스를 복사하여 본문이 출력되는 위치 아래에 배치했다.

마지막으로 <a> 태그 안에 Download를 꼭 명시하자!!! 이렇게 하지 않으면 파일을 내려 받지 않고 웹 브라우저에서 바로 열려버릴 수도 있다.

(..생략..)
<!-- Post Content -->
<p>{{ post.content }}</p>

{% if post.file_upload %}
	<a href="{{ post.file_upload.url }}" class="btn btn-outline-info" role="button" download>DownLoad</a>
{% endif %}

<hr>
(..생략..)

첨부 파일명과 확장자 아이콘 나타내기

<Download> 버튼에 첨부된 "파일 이름"과 "첨부 파일 확장자"를 알려주는 아이콘을 구현해보자.

blog/models.py에서 다음과 같은 새로운 함수를 생성하자.

from django.db import models
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 = models.TextField()  # TextField : 문자열의 길이 제한이 없는 필드

    head_image = models.ImageField(upload_to='blog/images/%Y/%m/%d/', blank=True)
    file_upload = models.FileField(upload_to='blog/files/%Y/%m/%d/', blank=True)

    created_at = models.DateTimeField(auto_now_add=True)  # DateTimeField : 월, 일, 시, 분, 초까지 기록하게 해주는 필드
    updated_at = models.DateTimeField(auto_now=True)

    # author: 추후 작성 예정, 외래키를 구현할 시 다룰 것.

    def __str__(self):
        return f'[{self.pk}]{self.title}'

    def get_absolute_url(self):
        return f'/blog/{self.pk}'

	# 파일 경로는 제외하고 파일명만 나타내는 함수
    def get_file_name(self):
        return os.path.basename(self.file_upload.name)
	
    # 확장자를 찾아내는 함수
    def get_file_ext(self):
        return self.get_file_name().split(".")[-1]

사용할 아이콘 이미지는 Font awesome 웹 사이트에서 가져왔다.

이 웹 사이트를 사용하기 위해 먼저 post_detail.html의 <head> 태그 안에 Font awesome에서 보내준 자바스크립트 링크를 추가해야한다.

(..생략..)
<head>

  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">

  <title>{{ post.title }} - Blog</title>

  <!-- Bootstrap core CSS -->
  <link rel="stylesheet" href="{% static 'blog/bootstrap/bootstrap.min.css' %}" media="screen">

  <!-- Custom styles for this template -->
  <link rel="stylesheet" href="{% static 'blog/css/blog-post.css' %}" media="screen">
  
  <!-- 추가!! *은 kit code로 각자 다름 -->
  <script src="https://kit.fontawesome.com/**********.js" crossorigin="anonymous"></script>

</head>
(..생략..)

첨부 파일 확장자에 맞는 아이콘을 검색하여 그 소스를 <Download> 버튼에 다음과 같이 추가하자.

필자는 csv, excel, word, pdf 아이콘을 추가했고, 나머지는 일반적인 파일 아이콘을 사용했다.

확장자마다 다른 아이콘을 사용하기 위해 if문을 사용했다. 그리고 마지막으로 {{ post.get_file_name }}으로 파일명을 출력했다.

템플릿에서는 함수를 사용할 때 괄호를 쓰지 않는다는 것을 기억하자!!!

(..생략..)
{% if post.file_upload %}
	<a href="{{ post.file_upload.url }}" class="btn btn-outline-info" role="button" download>
  		DownLoad:
  
  		{% if post.get_file_ext == 'csv' %}
  			<i class="fas fa-file-csv"></i>
  		{% elif post.get_file_ext == 'xlsx' or post.get_file_ext == 'xls' %}
  			<i class="fas fa-file-excel"></i>
  		{% elif post.get_file_ext == 'docx' or post.get_file_ext == 'doc' %}
	  		<i class="fas fa-file-word"></i>
	  	{% elif post.get_file_ext == "pdf" %}
	  		<i class="fa-solid fa-file-pdf"></i>
	  	{% else %}
	  		<i class="far fa-file"></i>
	  	{% endif %}

  		{{ post.get_file_name }}
	</a>
{% endif %}
(..생략..)

이제 포스트마다 각각 다른 형식의 파일을 업로드하고 페이지를 새로고침 해보면 다음과 같이 파일의 확장자마다 다른 아이콘이 보이고 파일명도 출력된다.


템플릿 필터 사용하기

이제 포스트 목록 페이지에서 포스트의 본문이 길 경우 요약하여 보여주는 미리보기 기능을 추가해보자.

포스트 미리보기 기능 구현하기

장고는 이런 문제를 쉽게 해결하는 방법을 제공하고 있다.

템플릿에서 truncatewords 또는 truncatechars를 사용하면 된다.

  • truncatewords : 문자열을 단어 수 기준으로 자름
  • truncatechars : 문자열을 글자 수 기준으로 자름

출력 글자 수 제한하기

다음처럼 post_list.html에서 p.content 뒤에 | truncatewords:45를 입력하여 앞에서부터 45개 단어만 출력하도록 설정했다. 여기서 | 은 버티컬바(Vertical Bar, Shift+역슬래시) 이다

(..생략..)
<div class="card-body">
  <h2 class="card-title h4">{{ p.title }}</h2>
  <p class="card-text">{{ p.content | truncatewords:45 }}</p>
  <a class="btn btn-primary" href="{{ p.get_absolute_url }}">Read more →</a>
</div>
(..생략..)

요약문 필드 만들기

여기에 더하여 포스트의 요약문을 보여주는 hook_text 라는 새로운 필드를 만들어 적용해봤다.

뉴스 웹 사이트가 기사 제목 아래에 사람들의 관심을 끄는 메시지를 보여주는 방식과 비슷하다!

models.py를 열어 hook_text 필드가 비어 있지 않을 때는 hook_text 필드 값을 보여주도록 했다. CharFiedl를 사용했고, blank=True로 하여 필수 목록이 아니도록 설정했다.

from django.db import models
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 = models.TextField()  # TextField : 문자열의 길이 제한이 없는 필드


    head_image = models.ImageField(upload_to='blog/images/%Y/%m/%d/', blank=True)
    file_upload = models.FileField(upload_to='blog/files/%Y/%m/%d/', blank=True)

    created_at = models.DateTimeField(auto_now_add=True)  # DateTimeField : 월, 일, 시, 분, 초까지 기록하게 해주는 필드
    updated_at = models.DateTimeField(auto_now=True)


    # author: 추후 작성 예정, 외래키를 구현할 시 다룰 것.

당연하게 모델을 변경했으니, 마이그레이션을 하자!!!!

템플릿 파일 수정하기

이제 post_list.html을 수정하여 요약문이 템플릿으로 보이게 해야한다.

class="card-body"div요소를 찾아 다음과 같이 수정하면 된다. 이때 태그 안에서 class="text-muted"를 추가하면 글씨가 회색으로 된다. 이는 부트스트랩에서 미리 class의 스타일을 지정해놨기 때문에 가능하다.

(..생략..)
<div class="card-body">
  <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.content | truncatewords:45 }}</p>
  <a class="btn btn-primary" href="{{ p.get_absolute_url }}">Read more →</a>
</div>
(..생략..)

또한 post_detail.html도 같은 방식으로 수정한다. 주의할점은 p.hook_text가 아니라 post.hook_text를 사용해야 한다는 것이다.

(..생략..)
<!-- Title -->
<h1 class="mt-4">{{ post.title }}</h1>
{% if post.hook_text %}
	<h5 class="text-muted">{{ post.hook_text }}</h5>
{% endif %}
(..생략..)

이제 관리자 페이지에서 포스트를 작성해보면 다음과 같이 hook_text가 나오는 것을 볼 수 있다. 여기서 내용을 작성하고 나서 웹 페이지를 확인해보자.


정리

  • 템플릿 파일에서 if문과 템플릿 필터를 사용하는 방법을 알았다.

  • 템플릿에서는 함수를 사용할 때 괄호를 쓰지 않는다는 것을 기억하자!!!

  • 항상 장고의 MTV 패턴을 상기하면서 학습하기.

  • model 클래스의 메소드가 추가될 때는 마이그레이션 안해도 됨. 하지만, 클래스 변수가 추가될 때는 마이그레이션 해야함.

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

0개의 댓글