10장에서는 템플릿 파일에서 if문과 템플릿 필터를 사용하는 방법에 대하여 알아본다. 이후 해당 기능을 통해 포트스 목록, 상세 페이지에서 아쉬웠던 부분을 개선한다.
대표 이미지가 없는 포스트가 하나라도 있으면 포스트 목록 페이지에서 오류 발생
포스트 목록 페이지에서 각 포스트의 본문 내용을 모두 노출. 즉, 미리보기 내용 부재.
위에서 언급한 바와 같이 1. 대표 이미지가 없고, 2. 임의의 긴 글이 추가된 포스트를 하나 만들자.
이후 웹 페이지에 접속해 보면 다음과 같은 에러가 발생한다.
즉, 헤드이미지가 없는 포스트가 있기 때문에 발생하는 오류다
템플릿(HTML)에서 if문을 사용해야 하는 방법은 {% if 조건A %}
으로 시작해서 {% endif %}
로 끝내는 것 이다.
else문과 elif도 조건문 사이에서 {% else %}
, {% elif 조건B %}
와 같은 방식으로 사용하면 된다.
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 클래스의 메소드가 추가될 때는 마이그레이션 안해도 됨. 하지만, 클래스 변수가 추가될 때는 마이그레이션 해야함.