멋쟁이 사자처럼 - django 강의
부트스트랩 사용하기
[Examples] 페이지
페이지 맨 위에 보면 [Download source code]가 있습니다.
일단 바탕화면에 다운로드받고, 압축 해제합니다.
[ bootstrap-5.0.0-beta1 - bootstrap-5.0.0-beta1 - site - content - docs - 5.0 - examples - cover ]
폴더로 돌아가서 빈 곳에 [Shift]를 누른 상태로 마우스 오른쪽 클릭을 합니다.
[여기에 PowerShell 창 열기]를 누릅니다. [PowerShell]창이 뜨면 다음 명령어를 입력합니다.
code .
<head>, <body> 부분을 작성한다.

{% load static %}
<head>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js" integrity="sha384-q2kxQ16AaE6UbzuKqyBE9/u/KzioAlnx2maXQHiDX9d4/zp8Ok3f+M7DPm+Ib6IU" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.min.js" integrity="sha384-pQQkAEnwaBkjpqZ8RU1fF1AKtTcHJwFl3pblpTlHXybJjHpMYo79HY3hIi4NKxyj" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/cover.css' %}">
</head>
static file 관리
[blogapp]에서 오른쪽 클릭을 합니다. [blogapp]-[New]-[Directory]를 눌러줍니다. static 이라고 입력하고 [OK]버튼.
[static] 디렉토리에서 오른쪽 클릭을 합니다. [static]-[New]-[Directory]를 클릭. css를 입력하고 [OK]버튼.
저번 부트스트랩의 [cover.css] 파일을 복사 후 [css]디렉토리에 붙여넣기 합니다.
[firstProject]-[settings.py]를 클릭.
맨 밑에 다음을 붙여 넣기 합니다.
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / 'blogapp' / 'static'
]
STATIC_ROOT = BASE_DIR / 'static'
작업이 끝났으면 터미널을 열어 static file들을 한 곳으로 모아주는 명령어 입력.
python manage.py collectstatic
작업한 부트스트랩 index파일을 [templates]-[index.html]에 붙여 넣기 합니다.
python manage.py runserver 실행하여 확인해봅니다.
데이터베이스
<모델 설계>
[models.py]에서 데이터베이스를 설계할 수 있습니다.
class Blog(models.Model):
title = models.CharField(max_length=100)
pub_date = models.DateTimeField()
body = models.TextField()
- 제목은 CharField.
이름에서 쉽게 알 수 있듯이 문자로 구성되어 있는 항목이라는 것을 알 수 있습니다.
max_length는 최대 글자수를 지정합니다. - 날짜, 내용도 마찬가지로 이름에서 기능을 알 수 있습니다.
이처럼 장고는 데이터베이스를 자세히 알지 않아도 직관적으로 모델을 설계할 수 있도록 제공하고 있습니다.
<모델 데이터베이스 적용>
python manage.py makemigrations
python manage.py migrate
관리자 계정
관리자 계정 만들기
python manage.py createsuperuser
'''
Username (leave blank to use 'choi'): admin
Email address: 이메일@이메일.com
Password:
Password (again):
Superuser created successfully.
'''
하지만, 블로그 모델은 어디에서도 확인할 수 없습니다.
모델을 admin 페이지에 등록을 안해주었기 때문입니다.
이를 위해 [admin.py]에 등록해주도록 합시다.
[blogapp]-[admin.py]
from django.contrib import admin
from .models import Blog
# Register your models here.
admin.site.register(Blog)
forms.py :: 글쓰기 기능 만들기
[blogapp]-[New]-[Python File]-[forms.py] 생성
from django import forms
from .models import Blog
class CreateBlog(forms.ModelForm):
class Meta:
model = Blog
fields = ['title', 'pub_date', 'body']
장고에서 기본적으로 지원하는 [forms]를 import합니다.
또한 Blog 모델을 가져옵니다.
블로그를 생성할 것이기 때문에 이름을 [CreateBlog]로 하였습니다.
장고에서 Meta 클래스는 내부 클래스로 활용되며, 이는 기본 필드의 값을 재정의할 때 사용합니다.
[Blog]로 부터 모델을 가져오고 그 중 'title', 'pub_date', 'body'를 가져온다는 의미입니다.
createBlog.html
[blogapp]-[templates]-[New]-[HTML File]-[createBlog] 생성
[blogapp]-[views.py]에서 뷰를 생성해주도록 합니다.
def createBlog(request):
return render(request, 'createBlog.html')
[firstProject]-[urls.py]에서 url을 추가해주도록 합니다.
path('blogMain/createBlog/', blogapp.views.createBlog, name='createBlog'),
[blogMain.html]에서 [글쓰기] url 연결해줍니다.

뷰 수정
[createBlog] 함수에서 폼을 불러들이도록 합시다.
[blogapp]-[views.py]에서 [createBlog] 함수를 다음과 같이 수정합니다.

from .forms import CreateBlog
def createBlog(request):
form = CreateBlog()
return render(request, 'createBlog.html', {'form': form})
createBlog.html 수정
딕셔너리 자료형으로 [form]이라는 객체를 전달하였으므로 키 값 'form'을 이용하여 출력해봅시다.html 문서에서
출력할 때에는 장고 템플릿 변수를 이용하여 출력합니다.
형태는 {{ }} 와 같은 형식을 이용합니다.

템플릿 - DB 연동
createBlog.html 수정
<input type="submit" value="저장"/>

createBlog() 수정
[blogapp]-[views.py]로 돌아와서 createBlog() 함수를 수정하여 제대로 DB에 저장하도록 합시다.
from django.shortcuts import render, redirect
def createBlog(request):
if request.method == 'POST':
form = CreateBlog(request.POST)
if form.is_valid():
form.save()
return redirect('blogMain')
else:
return redirect('index')
else:
form = CreateBlog()
return render(request, 'createBlog.html', {'form': form})
# form = CreateBlog()
#
# return render(request, 'createBlog.html', {'form': form})

redirect() 함수를 추가로 import 해줍니다.
이는 render() 함수와 비슷하지만 템플릿에 값을 전달하는 목적이 아닌 단순히 특정 url 혹은 프로젝트 내의 문서로 이동시키고자 할 때 사용합니다.
[createBlog.html]에서 [저장]을 누르면 데이터들이 POST 방식으로 넘어옵니다.
만약 POST 방식으로 넘어오지 않았으면, 그냥 단순히 [글쓰기] 버튼을 눌러서 들어온 것이 됩니다.
POST 방식으로 넘어오면 CreateBlog() 폼에 값을 전달한 상태로 [form] 객체를 만듭니다.
그 폼 데이터들이 올바른 형식이면(form.is_valid()) 데이터베이스에 저장을 합니다. (form.save())
그 후에 블로그 메인 화면으로 이동시킵니다.(redirect('blogMain'))
csrf_token
[createBlog.html]
{% csrf_token %}
장고에서는 POST 데이터 전달 과정에서 보안을 위해 [csrf_token]을 반드시 사용해야 합니다.
참고)CSRF(Cross-Site Request Forgery)란? https://ko.wikipedia.org/wiki/사이트_간_요청_위조

템플릿 상속 : 글쓰기 시에 위의 nav바를 상속 받자.
[firstProject]-[New]-[Directory]-[templates]-[New]-[HTML File]-[base]를 생성한다.

자, 이제 blogMain.html, createBlog.html을수정합시다.
[blogMain.html] 안에 있는 내용 전부를 복사해서 [base.html]에 붙여넣기 합니다.
스크롤을 내려 밑으로 내려오면 </nav>가 보입니다.
<main> </main> 태그를 지우고 다음 코드를 추가하여 작성합니다.
{% block content %}
{% endblock %}
우리가 필요한 것은 [navbar]이므로 [navbar]만 남기고 나머지는 삭제합니다.
{% block content %} 와 {% endblock %} 사이에는 나머지 상속받는 템플릿의 내용이 들어갑니다.
즉, base.html 문서를 상속받는 템플릿이라면 [navbar]는 공통으로 상속받고, [content] 내용만 바뀌어서 나타나게 되는 것입니다.

다시 [blogMain.html]로 돌아옵니다.
이제 [navbar] 내용은 상속을 받으니 <main>만 남기고 나머지는 삭제하여 줍니다.

[firstProject]-[settings.py]를 엽니다.
[TEMPLATES]를 다음과 같이 수정하여 줍니다.
# 'DIRS': [],
'DIRS': [BASE_DIR / 'pjt_MyInTime' / 'templates'],

다시 [blogMain.html]로 돌아와서 다음을 추가해줍니다.
{% extends 'base.html' %}
{% block content %}
{% endblock %}

위와 같은 방식으로 [createBlog.html]도 수정합니다.

모델 재설계
관리자 페이지에 접속하여 모든 블로그 객체 삭제 후
[blogapp]-[migrations]에서 [__init__.py]를 제외한 모든 파일을 지워주세요.
python manage.py makemigrations
python manage.py migrate --fake
# models.py 수정
from django.db import models
from django.contrib.auth.models import User
class Blog(models.Model):
title = models.CharField(max_length=100)
pub_date = models.DateTimeField(auto_now_add=True) # 현재시간에 맞춰서 자동 입력
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, default=1)
body = models.TextField()
# forms.py 수정
fields = ['title', 'author', 'body']
python manage.py migrate
글쓰기 기능 꾸미기 : CKEditor
pip install django-ckeditor
settings.py 에서 앱 등록.

models.py 수정.

python manage.py makemigrations
python manage.py migrate
python manage.py collectstatic # yes 입력
이미지파일 업로드
[firstProject]-[settings.py]에서 다음을 추가합니다.
CKEDITOR_UPLOAD_PATH = "uploads/"
[settings.py] 맨 아래에 다음 2줄을 추가해 줍니다.
MEDIA_URL = '/media/'
MEDIA_ROOT = 'media/'
[firstProject]-[urls.py]에서 url을 추가해주도록 합니다.
from django.conf.urls import include
path('ckeditor/', include('ckeditor_uploader.urls')),
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

[models.py]에서 파일 업로드 필드로 적용시키기 위해 다음과 같이 수정합니다.
from ckeditor_uploader.fields import RichTextUploadingField
body = RichTextUploadingField()
python manage.py makemigrations
python manage.py migrate
글쓰기 페이지 꾸미기 : CKEditor
[forms.py] 수정합니다.
from ckeditor_uploader.widgets import CKEditorUploadingWidget
widgets = {
'title': forms.TextInput(
attrs={'class': 'form-control', 'style': 'width: 100%', 'placeholder': '제목을 입력하세요.'}
),
'author': forms.Select(
attrs={'class': 'custom-select'},
),
'body': forms.CharField(widget=CKEditorUploadingWidget()),
}
'widgets'을 지정하면, 장고에서 자체적으로 제공하는 폼의 형태를 빌려올 수 있습니다.
createBlog.html 수정합니다.
{% extends 'base.html' %}
{% block content %}
<br>
<div class="container">
<h1>글쓰기</h1>
<hr>
<br><br>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<input type="submit" class="btn btn-dark" value="저장"/>
</form>
</div>
{% endblock %}
여기에서 중요한 부분은 form 태그의 'enctype'과 {{ form.media }}입니다.
enctype="multipart/form-data"를 설정해주어야 문자 이외의 이미지 파일 같은 것들도 같이 묶여서 전송이 됩니다.
또한 {{ form..media }}를 넣어 주어야 CKEditor의 폼이 적용이 됩니다.
블로그 메인화면, 시간 설정, 이미지 설정
블로그 메인 화면 출력
[blogapp]-[views.py]에서 blogMain()함수를 다음과 같이 수정하여 줍니다.
from .models import Blog
def blogMain(request):
blogs = Blog.objects.all()
return render(request, 'blogMain.html', {'blogs': blogs})
[blogMain.html]을 다음과 같이 수정합니다.
{% extends 'base.html' %}
{% block content %}
<br>
{% for blog in blogs %}
<main role="main" class="container">
<div class="jumbotron">
<h1>제목 : {{ blog.title }}</h1>
<br>
<h4>작성일 : {{ blog.pub_date }}</h4>
<br>
<h4>작성자 : {{ blog.author }}</h4>
<br>
<p class="lead">{{ blog.body }}</p>
<br>
<a class="btn btn-lg btn-primary"
href="{{ site.baseurl }}/docs/{{ site.docs_version }}/components/navbar/"
role="button">View navbar docs »</a>
</div>
</main>
{% endfor %}
{% endblock %}
시간 설정
[settings.py] 수정합니다.
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ko-kr'
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Seoul'
이미지 수정
[blogMain.html] 수정합니다.
{{ blog.body | safe }}
장고는 기본적으로 <, >, &, ", ' 등의 문자를 자동적으로 'auto-escape' 합니다.
장고 템플릿 필터 중 'safe'를 사용하여 해결합니다.
블로그 세부화면 구성
세부화면 구성
세부화면 부트스트랩 다운, 압축풀기 -> index.html 확인합니다.
https://startbootstrap.com/previews/blog-post/
[blogapp]-[templates]-[New]-[HTML File]- [detail] 만들어서 index.html 복사 붙여넣기 합니다.
css 파일 붙여넣을 새 폴더를 하나 더 생성 합니다. [blogapp]-[static]-[css]-[detail]
다음의 두 폴더를 복사해 붙여 넣습니다.

static 파일들을 추가하였으니 모아주도록 합니다.
python manage.py collectstatic
yes
detail.html 수정
{% load static %}
<link href="{% static 'css/detail/vendor/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'css/detail/css/blog-post.css' %}" rel="stylesheet">

맨 밑도 수정해 줍니다.
<script src="{% static 'css/detail/vendor/jquery/jquery.min.js' %}"></script>
<script src="{% static 'css/detail/vendor/bootstrap/js/bootstrap.bundle.min.js' %}"></script>

view 작성
[blogapp]-[views.py]
def detail(request):
return render(request, 'detail.html')

urls.py 수정
[bloapp]-[firstProject]-[urls.py]에서 다음 코드를 추가하여 작성합니다.
path('blogMain/detail/<int:blog_id>/', blogapp.views.detail, name='detail'),
'blog_id'는 뒤에 views.detail 함수에 넘길 블로그 글 번호입니다.
이와 같이 하면 'blogMain/detail/1'은 블로그 글 1번에 대한 detail 페이지가 나오게 되는 것입니다.
from django.shortcuts import render, redirect, get_object_or_404
def detail(request, blog_id):
blog_detail = get_object_or_404(Blog, pk=blog_id)
return render(request, 'detail.html', {'blog_detail': blog_detail})

blogMain.html 수정
<a class="btn btn-lg btn-primary" href="{% url 'detail' blog.id %}" role="button">View navbar docs »</a>

detail.html 수정

댓글 기능
모델을 설계해야되니 [blogapp]-[models.py]를 열고, 모델 클래스를 생성합니다.
class Comment(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.SET_NULL, null=True)
comment_date = models.DateTimeField(auto_now_add=True)
comment_user = models.TextField(max_length=20)
comment_thumbnail_url = models.TextField(max_length=300)
comment_textfield = models.TextField()
python manage.py makemigrations
python manage.py migrate
[blogapp]-[admin.py] 파일에서 모델을 다음과 같이 등록해줍니다.
from .models import Comment
admin.site.register(Comment)
객체를 참조하기 어려워 보이는 상황에서 필요한 것이 바로 '필터'입니다.
'필터'에 대해 알아보기 위해 이와 관련된 데이터베이스 지식을 같이 공부하고 넘어가도록 합시다.
데이터베이스에서는 테이블을 조회할 때 SQL문에서 'SELECT'라는 것을 사용하여 조회를 합니다.
테이블 안의 모든 데이터를 조회하고자 할 때에는 SELECT 문에서는 '*'를 활용하여 조회를 하게 되는데 이를 장고에서는 '모델클래스.objects.all()'와 같이 사용합니다.
하지만 특정 조건에 해당되는 데이터를 조회하고자 할 때에는 SELECT 문과 WHERE 조건절을 사용하여 조회합니다. 이를 장고에서는 '모델클래스.objects.filter()'와 같이 사용하게 되는 것입니다.
우리가 만들었던 Comment 모델은 Blog 모델의 주키를 외래키로 참조하고 있습니다.
따라서 필터와 외래키를 활용하면 우리는 각각의 블로그 글에 해당되는 댓글을 가져올 수가 있는 것입니다.
views.py 수정
필터를 적용하기 위해 [blogapp]-[views.py]에서 [detail] 함수를 수정하도록 하겠습니다.
from .models import Blog, Comment
def detail(request, blog_id):
blog_detail = get_object_or_404(Blog, pk=blog_id)
comments = Comment.objects.filter(blog_id=blog_id)
context = {
'blog_detail': blog_detail,
'comments': comments
}
return render(request, 'detail.html', context)

detail.html 수정
위의 view에서 'comments'를 보냈으니 값을 받아서 댓글을 출력할 수 있도록 수정해보겠습니다.
필요없는 코드를 모두 지우고 작성해 줍니다.
{% for comment in comments %}
<!-- Single Comment -->
<div class="media mb-4">
<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
<div class="media-body">
<h5 class="mt-0">{{ comment.comment_user }}</h5>
<h5 class="mt-0">{{ comment.comment_date }}</h5>
<h5 class="mt-0">{{ comment.comment_thumbnail_url }}</h5>
<h5 class="mt-0">{{ comment.comment_textfield }}</h5>
</div>
</div>
{% endfor %}

Django - 멋쟁이 사자처럼 at 한국교통대학교 7기
- 본 강의는 '멋쟁이 사자처럼 at 한국교통대학교' 멤버들에게 제공되는 장고 강의입니다. - 본 강의는 장고 관련 문법 사항보다는 프로젝트 구현에 초점을 맞추어 진행합니다. - 본 강의는 장
opentutorials.org