static 폴더 지정
- mysite밑에 static 폴더를 생성한 후
- config 폴더 밑의 settings.py로 이동해 static_dirs 설정을 추가해 준다.
BootStrap 다운로드
- 구글창에 bootstrap 다운로드를 검색한 후 아래 All release를 클릭한다.
- stable 한 최신 버전이 이 글을 작성하는 당시에는 v5.2였음.
- compiled 된 CSS와 JS를 다운로드한다.
다운 이후
- 압축을 풀고 bootstrap.min.css와 bootstrap.min.js를 static 폴더에 붙여 넣어 준다.
question_list.html파일 수정
- bootstrap.min.css 파일 적용을 위해 link를 걸어준다.
- 그 후 css를 적용하기 위해 HTML을 수정한다.
원래 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='UTF-8'>
<link rel="stylesheet" type = "text/css" href = {% static 'bootstrap.min.css' %}>
</head>
<body>
{% if question_list %}
<ul>
{% for question in question_list %}
<!--<li><a href="/pybo/{{ question.id}}">{{question.subject}}</a></li> 원래 코드-->
<li><a href = "{% url 'pybo:detail' question.id %}">{{ question.subject }}</a></li><!-- 별칭 수정한 코드-->
{% endfor %}
</ul>
{% else %}
<p>질문이 없습니다.</p>
{% endif %}
</body>
</html>
수정된 코드
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
</head>
<body>
<div class="container my-3">
<table class="table">
<thead>
<tr class="table-dark">
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
{% if question_list %}
{% for question in question_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>
<a href="{% url 'pybo:detail' question.id %}">{{ question.subject }}</a>
</td>
<td>{{ question.create_date }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="3">질문이 없습니다.</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</body>
</html>
실행 결과
question_detail.html 수정
원래 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{ question.subject }}</h1>
<div>{{ question.content }}</div>
<hr>
<h5>{{ question.answer_set.count }}개의 답변이 있습니다.</h5>
<div>
<ul>
{% for answer in question.answer_set.all %}
<li>{{ answer.content }}</li>
{% endfor %}
</ul>
</div>
<hr>
<form action="{% url 'pybo:answer_create' question.id %}" method="post">
{% csrf_token %} <!-- CSRF 공격을 막기 위해 명시적으로 넣어줘야 실행됨.-->
<textarea name="content" id="content" cols="30" rows="10"></textarea>
<input type="submit" value = "답변등록">
</form>
</body>
</html>
수정된 코드
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
<div class="container my-3">
<!-- 질문 -->
<h2 class="border-bottom py-2">{{ question.subject }}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2">
{{ question.create_date }}
</div>
</div>
</div>
</div>
<!-- 답변 -->
<h5 class="border-bottom my-3 py-2">{{question.answer_set.count}}개의 답변이 있습니다.</h5>
{% for answer in question.answer_set.all %}
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{{ answer.content }}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2">
{{ answer.create_date }}
</div>
</div>
</div>
</div>
{% endfor %}
<!-- 답변 등록 -->
<form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
{% csrf_token %}
<div class="mb-3">
<label for="content" class="form-label">답변내용</label>
<textarea name="content" id="content" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="답변등록" class="btn btn-primary">
</form>
</div>
실행 결과
내비게이션 바 추가하기
- 내비게이션은 상속 개념으로 앞선 html 파일들에 적용한다.
base.html 코드
- templates 밑에 생성
{% load static %}
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
<title>Hello, pybo!</title>
</head>
<body>
<!-- 네비게이션바 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'pybo:index' %}">메인페이지</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#">로그인</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
{% block content %}
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 End -->
</body>
</html>
- 내비게이션을 상속받는 코드들은 block content부터 endblock 사이에 들어가게 된다.
- 그래서 html, body 태그를 작성하지 않아도 정상 작동됨을 볼 수 있다.
상속받는 코드들
- extends를 통해 base.html을 상속받고
- block content와 endblock 사이에 코드를 작성하면 된다.
{% extends 'base.html' %}
{% block content %}
{% endblock %}
변경된 question_list.html
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
<table class="table">
<thead>
<tr class="table-dark text-center">
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
{% if question_list %}
{% for question in question_list %}
<tr class="text-center">
<td>{{ forloop.counter }}</td>
<td>
<a href="{% url 'pybo:detail' question.id %}">{{ question.subject }}</a>
</td>
<td>{{ question.create_date }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="3">질문이 없습니다.</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
{% endblock %}
실행 결과
- question_detail.html 파일도 동일한 형태로 수정해 줍니다.
질문 등록 기능 추가하기
1. question_list.html에 질문등록 버튼 추가
- a태그를 사용하여 버튼을 만들어준다.
<a href="{% url "pybo:question_create" %}" class = "btn btn-primary">질문 등록</a>
2. urls.py 설정
- a태그를 누르면 이동할 pybo:question_create URL 매핑이 필요하다.
- 아래 코드 추가
#질문 등록에 대한 URL 매핑
path("question/create/", views.question_create, name = "question_create"),
3. mysite/pybo/forms.py 생성
- 폼 형태를 처음 만드는 것이기 때문에 파이썬 코드 생성
- QuestionForm 클래스는 질문을 등록하기 위해 사용하는 장고의 폼이다.
- ModelForm 클래스를 상속받아서 만듦.
from django import forms
from .models import Question
# forms 라이브러리의 ModelForm 상속받아서 사용
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # models.py에서 정의한 클래스(모델)
fields = ["subject", "content"] # models.py에서 정의한 필드들(날짜는 입력받는게 아니므로 제외(create_date)
4. views.py 설정
- POST로 request 요청이 들어오면 DB에 저장하고 Get으로 request요청이 들어오면 단순하게 질문등록 페이지를 요청함.
def question_create(request):
if request.method =="POST": #post로 들어오면 입력한 값들을 전달받아DB에 저장
form = QuestionForm(request.POST)
if form.is_valid():
question = form.save(commit = False)
question.create_date = timezone.now()
question.save()
return redirect("pybo:index") # main 화면으로 보냄
else: #Get 요청일 때는 단순하게 질문등록 페이지 요청
form = QuestionForm()
context = {"form" : form}
return render(request, "pybo/question_form.html",context)
5. question_form.html 파일 생성
- action을 안 쓰는 이유 : 장고의 기능 중 하나로 action을 사용하지 않으면 기본 페이지 URL이 default로 설정된다.
- form.as_p는 form의 형태를 보고 as_p는 p태그로 만들어 달라는 장고의 명령어임
- 다만 css 적용에 좋지 않기 때문에 자주 사용되지는 않는다.
{% extends 'base.html' %}
{% block content %}
<div class = "container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form method = "post">
{% csrf_token %}
{{ form.as_p }} <!-- form 형태대로 html 코드를 만들어주는 함수-->
<button type = "submit" class = "btn btn-primary">질문 저장</button>
</form>
</div>
{% endblock %}
실행 결과
forms.py 수정하여 꾸미기
from django import forms
from .models import Question
# forms 라이브러리의 ModelForm 상속받아서 사용
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # models.py에서 정의한 클래스(모델)
fields = ["subject", "content"] # models.py에서 정의한 필드들(날짜는 입력받는게 아니므로 제외(create_date)
widgets = {
'subject': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10})
}
labels = {
'subject': '제목명',
'content': '질문의 내용'
}
question_form.html 파일 bootstrap 적용
- as_p로 작성한 코드는 지운다.
{% extends "base.html" %}
{% block content %}
<div class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form method="post">
{# Django에서 form 태그내에서 action 속성을 사용하지 않으면, 기본 페이지 URL이 Default로 설정 #}
{# <form method="post" action="{% url "pybo:question_create" %}">로 사용해도 되고, 안해도 무관 #}
{% csrf_token %}
<!-- 오류표시 Start -->
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field in form %}
{% if field.errors %}
<div>
<strong>{{ field.label }}</strong>
{{ field.errors }}
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
<!-- 오류표시 End -->
<div class="mb-3">
<label for="subject" class="form-label">제목</label>
<input type="text" class="form-control" name="subject" id="subject"
value="{{ form.subject.value|default_if_none:'' }}">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea class="form-control" name="content"
id="content" rows="10">{{ form.content.value|default_if_none:'' }}</textarea>
</div>
<button type="submit" class="btn btn-primary">질문저장</button>
</form>
</div>
{% endblock %}
- HTML 수정 코드는 에러 시에 오류표시를 함을 볼 수 있다.
Answer도 form 형식으로 변경하기
- question의 form 형식을 만드는 것과 동일
- 한번 스스로 만들어 보시길 바랍니다.
힌트
- forms.py에서 AnswerForm 클래스가 필요합니다.
- views.py에서 answer_create함수를 변경해 주어야 합니다.
- question_detail.html 코드를 수정하여 에러 출력부를 붙여 넣어야 합니다.
'Back > Django' 카테고리의 다른 글
4. 게시판 페이징 기능 추가하기 (0) | 2023.04.19 |
---|---|
3.5 Django 흐름 정리 (0) | 2023.04.19 |
2. 장고의 기본 요소 Admin, 렌더링, 동적URL매핑,DB연동 (0) | 2023.04.13 |
1. 장고의 기본 요소 Url, Database 연동, ORM (0) | 2023.04.13 |
0. 장고 개발 준비 (0) | 2023.04.12 |