<div class="col-sm-6">
<ul class="list-unstyled mb-0">
<li><a href="#!">미분류 ({{ posts_without_category }})</a></li>
{% for category in category_list %}
<li><a href="#!">{{ category.name }} ({{ category.post_set.count }})</a></li>
{% endfor %}
</ul>
</div>
class Category(models.Model):
# Category 명은 중복되면 안되므로 unique
name = models.CharField(max_length=25, unique=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name
# 메타정보를 입력할 수 있게 되어있다
class Meta:
verbose_name_plural = 'categories'
class PostDetail(DetailView):
model = Post
def get_context_data(self, *, object_list=None, **kwargs):
context = super(PostDetail, self).get_context_data(**kwargs)
context['category_list'] = Category.objects.all()
context['posts_without_category'] = Post.objects.filter(category=None).count()
return context
check_right_side 함수로 분리
post_detail 에 post_001 추가해주어야함
main_div 테스트는 post_list에만 필요
class TestView(TestCase):
...
def check_right_side(self, soup):
# category card에서 미분류 (1), 정치/사회 (1) 있어야함
category_card = soup.find('div', id='category-card')
self.assertIn('미분류 (1)', category_card.text)
self.assertIn('정치/사회 (1)', category_card.text)
...
def test_post_list_with_post(self):
...
# category card에서 미분류 (1), 정치/사회 (1) 있어야함
self.check_right_side(soup)
### main_div 에는 '정치/사회', '미분류' 있어야함
# 이부분은 post_list 에만 있으면됨. post_detail에는 필요없음
main_div = soup.find('div', id='main_div')
self.assertIn('정치/사회', main_div.text)
self.assertIn('미분류', main_div.text)
def test_post_detail(self):
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
)
post_001 = create_post(
title='The second Post',
content='Second Second Second',
author=self.author_000,
category=create_category(name='정치/사회')
)
self.check_right_side(soup)
class Category(models.Model):
# Category 명은 중복되면 안되므로 unique
name = models.CharField(max_length=25, unique=True)
description = models.TextField(blank=True)
# 독립성을 원할때 pk 를 이용했었는데, 이번에는 slug 를 이용해보자
# 한글이 들어갈 수 있기 때문에 unicode 허용
slug = models.SlugField(unique=True, allow_unicode=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return '/blog/category/{}/'.format(self.slug)
# 메타정보를 입력할 수 있게 되어있다
class Meta:
verbose_name_plural = 'categories'
# category 를 테스트
def test_post_list_by_category(self):
category_politics = create_category(name='정치/사회')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
)
post_001 = create_post(
title='The second Post',
content='Second Second Second',
author=self.author_000,
category=category_politics
)
response = self.client.get(category_politics.get_absolute_url())
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
self.assertEqual('Blog - {}'.format(category_politics.name), soup.title.text)
main_div = soup.fine('div', id='main-div')
self.assertNotIn('미분류', main_div.text)
self.assertIn(category_politics.name, main_div.text)
Model에 Slug필드를 새로 생성해주었기 때문에 마이그레이션 필요
기존에 두개 포스트가 있었는데 이 포스트들에 대한 Slug 필드를 어떻게 채울건지를 물어봄. 일단 'abcd'로 채워넣고 , admin 페이지에 들어가서 삭제
ᐅ python manage.py makemigrations
You are trying to add a non-nullable field 'slug' to category without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 'abcd'
ᐅ python manage.py migrate
from django.contrib import admin
from .models import Post, Category
#
class CategoryAdmin(admin.ModelAdmin):
# slug를 미리 만들어줘라
prepopulated_fields = {'slug': ('name', )}
admin.site.register(Post)
admin.site.register(Category, CategoryAdmin)
category 추가할때 , Name 에 값을 입력시 url을 사용할 수 있도록 slug필드에 자동으로 생성된다. (공백은 - 로 연결)
urlpatterns = [
path('category/<str:slug>/', views.PostListByCategory.as_view()),
path('<int:pk>/', views.PostDetail.as_view()),
path('', views.PostList.as_view()),
]
class PostListByCategory(PostList):
def get_queryset(self):
# url 패턴에서 정의한 slug str 을 받아옴
slug = self.kwargs['slug']
category = Category.objects.get(slug=slug)
# slug가 정치사회라면, 정치사회에 해당하는 post만 가져와서 보여줌
return Post.objects.filter(category=category).order_by('-created')
def create_category(name='life', description=' '):
# Category 가 생성되어 있을경우 가져오고(get), 아니면 인자값으로 만들어라 (create)
category, is_created = Category.objects.get_or_create(
name=name,
description=description
)
# test만으로는 admin에서의 slug를 받아오지 못하므로 추가
category.slug = category.name.replace(' ', '-').replace('/', '')
# 바뀐 slug를 저장
category.save()
return category
from django.shortcuts import render
from .models import Post, Category
from django.views.generic import ListView, DetailView
class PostList(ListView):
model = Post
def get_queryset(self):
return Post.objects.order_by('-created')
def get_context_data(self, *, object_list=None, **kwargs):
context = super(PostList, self).get_context_data(**kwargs)
context['category_list'] = Category.objects.all()
context['posts_without_category'] = Post.objects.filter(category=None).count()
return context
class PostDetail(DetailView):
model = Post
def get_context_data(self, *, object_list=None, **kwargs):
context = super(PostDetail, self).get_context_data(**kwargs)
context['category_list'] = Category.objects.all()
context['posts_without_category'] = Post.objects.filter(category=None).count()
return context
class PostListByCategory(ListView):
def get_queryset(self):
# url 패턴에서 정의한 slug str 을 받아옴
slug = self.kwargs['slug']
if slug == '_none':
category = None
else:
category = Category.objects.get(slug=slug)
# slug가 정치사회라면, 정치사회에 해당하는 post만 가져와서 보여줌
return Post.objects.filter(category=category).order_by('-created')
def get_context_data(self, *, object_list=None, **kwargs):
context = super(type(self), self).get_context_data(**kwargs)
context['category_list'] = Category.objects.all()
context['posts_without_category'] = Post.objects.filter(category=None).count()
slug = self.kwargs['slug']
if slug == '_none':
context['category'] = '미분류'
else:
category = Category.objects.get(slug=slug)
context['category'] = category
# context['title'] = 'Blog - {}'.format(category.name)
return context
# def post_detail(request, pk):
# blog_post = Post.objects.get(pk=pk)
#
# return render(
# request,
# 'blog/post_detail.html',
# {
# 'blog_post': blog_post,
# }
# )
# def index(request):
# posts = Post.objects.all()
#
# return render(
# request,
# 'blog/post_list.html',
# {
# 'posts': posts,
# 'a_plus_b': 1 + 3,
# }
# )
{% extends 'blog/base.html' %}
{% block content %}
<h1>Blog {% if category %}<small class="text-muted">: {{ category }}</small>{% endif %}</h1>
{% if object_list.exists %}
{% for p in object_list %}
<!-- Featured blog post-->
<!-- Categories widget-->
<div class="card mb-4" id="category-card">
<div class="card-header">Categories</div>
<div class="card-body">
<div class="row">
<div class="col-sm-6">
<ul class="list-unstyled mb-0">
{% for category in category_list %}
<li><a href="{{ category.get_absolute_url }}">{{ category.name }} ({{ category.post_set.count }})</a></li>
{% endfor %}
<li><a href="/blog/category/_none/">미분류 ({{ posts_without_category }})</a></li>
</ul>
</div>
</div>
</div>
</div>
def test_post_list_no_category(self):
category_politics = create_category(name='정치/사회')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
)
post_001 = create_post(
title='The second Post',
content='Second Second Second',
author=self.author_000,
category=category_politics
)
response = self.client.get('/blog/category/_none/')
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
# self.assertEqual('Blog - {}'.format(category_politics.name), soup.title.text)
main_div = soup.find('div', id='main-div')
self.assertIn('미분류', main_div.text)
self.assertNotIn(category_politics.name, main_div.text)
def create_tag(name='some tag'):
tag, is_created = Tag.objects.get_or_create(
name=name
)
tag.slug = tag.name.replace(' ', '-').replace('/', '')
tag.save()
return tag
...
...
class TestModel(TestCase):
def test_tag(self):
tag_000 = create_tag(name='bad_guy')
tag_001 = create_tag(name='america')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000
)
post_000.tags.add(tag_000)
post_000.tags.add(tag_001)
post_000.save()
post_001 = create_post(
title='Stay Fool, Stay Hungry',
content='Story about Steve Jobs',
author=self.author_000
)
post_001.tags.add(tag_001)
post_001.save()
self.assertEqual(post_000.tags.count(), 2) # post는 여러개의 tag를 가질 수 있다.
self.assertEqual(tag_001.post_set.count(), 2) # 하나의 tag는 여러개의 post 에 붙을 수 있다.
self.assertEqual(tag_001.post_set.first(), post_000) # 하나의 tag는 자신을 가진 post들을 불러올 수 있다.
self.assertEqual(tag_001.post_set.last(), post_001)
class Tag(models.Model):
name = models.CharField(max_length=40, unique=True)
slug = models.SlugField(unique=True, allow_unicode=True)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
head_image = models.ImageField(upload_to='blog/%Y/%m/%d/', blank=True)
created = models.DateTimeField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
tags = models.ManyToManyField(Tag)
def __str__(self):
return '{} :: {}'.format(self.title, self.author)
def get_absolute_url(self):
return '/blog/{}/'.format(self.pk)
url 지정을 안했으므로 에러 당연히 남
Read more 위에 Tag들을 쪽쪽 놓아주고 싶다.
from django.contrib import admin
from .models import Post, Category, Tag
#
class CategoryAdmin(admin.ModelAdmin):
# slug를 미리 만들어줘라
prepopulated_fields = {'slug': ('name', )}
class TagAdmin(admin.ModelAdmin):
# slug를 미리 만들어줘라
prepopulated_fields = {'slug': ('name', )}
admin.site.register(Post)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Tag, TagAdmin)
add Tag 로 Korea 추가 하고 ,
두번째 포스트에 해당 Tag 지정
포스트 글 아래에 Tag 생겨있다 !
<p class="card-text">{{ p.content | truncatewords:50 }}</p>
{% for tag in p.tags.all %}
<a href="{{ tag.get_absolute_url }}">#{{ tag }} </a>
{% endfor %}
<br/>
<br/>
<!-- Post content-->
{{ object.content }}
<br/>
{% for tag in object.tags.all %}
<a href="{{ tag.get_absolute_url }}">#{{ tag }}</a>
{% endfor %}
<hr/>
from django.test import TestCase, Client
from bs4 import BeautifulSoup
from .models import Post, Category, Tag
from django.utils import timezone
from django.contrib.auth.models import User
def create_category(name='life', description=' '):
# Category 가 생성되어 있을경우 가져오고(get), 아니면 인자값으로 만들어라 (create)
category, is_created = Category.objects.get_or_create(
name=name,
description=description
)
# test만으로는 admin에서의 slug를 받아오지 못하므로 추가
category.slug = category.name.replace(' ', '-').replace('/', '')
# 바뀐 slug를 저장
category.save()
return category
def create_tag(name='some tag'):
tag, is_created = Tag.objects.get_or_create(
name=name
)
tag.slug = tag.name.replace(' ', '-').replace('/', '')
tag.save()
return tag
def create_post(title, content, author, category=None):
blog_post = Post.objects.create(
title=title,
content=content,
created=timezone.now(),
author=author,
category=category,
)
return blog_post
class TestModel(TestCase):
def setUp(self):
self.client = Client()
self.author_000 = User.objects.create_user(username='smith', password='nopassword')
def test_category(self):
category = create_category()
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
category=category
)
# category 에 연결된 Class 인 post 를 불러올 수 있다. (소문자 .post로 가져옴)
self.assertEqual(category.post_set.count(), 1)
def test_tag(self):
tag_000 = create_tag(name='bad_guy')
tag_001 = create_tag(name='america')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000
)
post_000.tags.add(tag_000)
post_000.tags.add(tag_001)
post_000.save()
post_001 = create_post(
title='Stay Fool, Stay Hungry',
content='Story about Steve Jobs',
author=self.author_000
)
post_001.tags.add(tag_001)
post_001.save()
self.assertEqual(post_000.tags.count(), 2) # post는 여러개의 tag를 가질 수 있다.
self.assertEqual(tag_001.post_set.count(), 2) # 하나의 tag는 여러개의 post 에 붙을 수 있다.
self.assertEqual(tag_001.post_set.first(), post_000) # 하나의 tag는 자신을 가진 post들을 불러올 수 있다.
self.assertEqual(tag_001.post_set.last(), post_001)
def test_post(self):
category = create_category()
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
category=category
)
class TestView(TestCase):
# Client 의 역할을 제공해줌
def setUp(self):
self.client = Client()
self.author_000 = User.objects.create_user(username='smith', password='nopassword')
self.user_obama = User.objects.create_user(username='obama', password='nopassword')
# Navigation Bar를 체크하는 함수
def check_navbar(self, soup):
navbar = soup.find('div', id='navbar')
self.assertIn('Blog', navbar.text)
self.assertIn('About me', navbar.text)
def check_right_side(self, soup):
# category card에서 미분류 (1), 정치/사회 (1) 있어야함
category_card = soup.find('div', id='category-card')
self.assertIn('미분류 (1)', category_card.text)
self.assertIn('정치/사회 (1)', category_card.text)
# test_ 로 시작하는게 국룰. 왠만하면 변경하지 말기
def test_post_list_no_post(self):
response = self.client.get('/blog/')
self.assertEqual(response.status_code, 200)
# title 태그의 값을 잘 읽어오는지 테스트
soup = BeautifulSoup(response.content, 'html.parser')
title = soup.title
self.assertIn(title.text, 'Blog')
# navigation bar가 잘 동작하는지 Blog, About me가 있는지 확인
# post_list.html의 navigation 영역에 id="navbar" 추가
self.check_navbar(soup)
# 게시글이 없는경우 = object 의 갯수가 하나도 없는경우
self.assertEqual(Post.objects.count(), 0)
self.assertIn('아직 게시물이 없습니다', soup.body.text)
def test_post_list_with_post(self):
tag_america = create_tag(name='america')
# blog_post 를 create 하는 부분을 함수로 만들자
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
)
post_000.tags.add(tag_america)
post_000.save()
# 미분류, 정치/사회 category만들어야하므로 post 하나 더 만들기
post_001 = create_post(
title='The second Post',
content='Second Second Second',
author=self.author_000,
category=create_category(name='정치/사회')
)
post_001.tags.add(tag_america)
post_001.save()
# 게시글이 있는경우 = 게시글 수가 0보다 큰 경우
self.assertGreater(Post.objects.count(), 0)
# refresh를 했을때 게시물이 없는 상태로 가면 안되므로...
response = self.client.get('/blog/')
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
body = soup.body
# 게시글이 있는 상태인데, 아직 게시물이 없다고 하면 안되므로 NotIn 사용
self.assertNotIn('아직 게시물이 없습니다', body.text)
# 있는 게시글 post_000 의 타이틀을 내놓으면 됨
self.assertIn(post_000.title, body.text)
# read more 버튼 클릭
post_000_read_more_btn = body.find('a', id='read-more-post-{}'.format(post_000.pk))
self.assertEqual(post_000_read_more_btn['href'], post_000.get_absolute_url())
# category card에서 미분류 (1), 정치/사회 (1) 있어야함
self.check_right_side(soup)
### main_div 에는 '정치/사회', '미분류' 있어야함
# 이부분은 post_list 에만 있으면됨. post_detail에는 필요없음
main_div = soup.find('div', id='main-div')
self.assertIn('정치/사회', main_div.text)
self.assertIn('미분류', main_div.text)
# Tag
# post 목록 중에서 각 post 가 Tag 를 포함하는지 여부 확인
post_card_000 = main_div.find('div', id='post-card-{}'.format(post_000.pk))
# america Tag가 있는지 확인
self.assertIn('#america', post_card_000.text)
# 여기에도 Nav bar가 있나 테스트하기
def test_post_detail(self):
category_politics = create_category(name='정치/사회')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
category=category_politics
)
tag_america = create_tag(name='america')
post_000.tags.add(tag_america)
post_000.save()
post_001 = create_post(
title='The second Post',
content='Second Second Second',
author=self.author_000,
)
self.assertGreater(Post.objects.count(), 0)
# getAbsolute URL
post_000_url = post_000.get_absolute_url()
self.assertEqual(post_000_url, '/blog/{}/'.format(post_000.pk))
# 페이지 열어봐야지
response = self.client.get(post_000_url)
self.assertEqual(response.status_code, 200)
# {} - Blog 의 형태로 보여주고 싶다
soup = BeautifulSoup(response.content, 'html.parser')
title = soup.title
self.assertEqual(title.text, '{} - Blog'.format(post_000.title))
self.check_navbar(soup)
body = soup.body
# main div 검사
main_div = body.find('div', id='main-div')
self.assertIn(post_000.title, main_div.text)
self.assertIn(post_000.author.username, main_div.text)
self.assertIn(post_000.content, main_div.text)
self.check_right_side(soup)
# Tag
# america Tag가 있는지 확인
self.assertIn('#america', main_div.text)
# category가 main_div 에있다
self.assertIn(category_politics.name, main_div.text)
# edit 버튼이 로그인하지 않는 경우 보이지 않는다.
self.assertNotIn('EDIT', main_div.text)
# 로그인을 한 경우에는 보여야한다
login_success = self.client.login(username='smith', password='nopassword')
self.assertTrue(login_success)
response = self.client.get(post_000_url)
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
main_div = soup.find('div', id='main-div')
self.assertEqual(post_000.author, self.author_000)
self.assertIn('EDIT', main_div.text)
# 다른 사람으로 로그인한 경우에는 보이지 말아야 한다
login_success = self.client.login(username='obama', password='nopassword')
self.assertTrue(login_success)
response = self.client.get(post_000_url)
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
main_div = soup.find('div', id='main-div')
self.assertEqual(post_000.author, self.author_000)
self.assertNotIn('EDIT', main_div.text)
# category 를 테스트
def test_post_list_by_category(self):
category_politics = create_category(name='정치/사회')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
)
post_001 = create_post(
title='The second Post',
content='Second Second Second',
author=self.author_000,
category=category_politics
)
response = self.client.get(category_politics.get_absolute_url())
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
# self.assertEqual('Blog - {}'.format(category_politics.name), soup.title.text)
main_div = soup.find('div', id='main-div')
self.assertNotIn('미분류', main_div.text)
self.assertIn(category_politics.name, main_div.text)
def test_post_list_no_category(self):
category_politics = create_category(name='정치/사회')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000,
)
post_001 = create_post(
title='The second Post',
content='Second Second Second',
author=self.author_000,
category=category_politics
)
response = self.client.get('/blog/category/_none/')
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
# self.assertEqual('Blog - {}'.format(category_politics.name), soup.title.text)
main_div = soup.find('div', id='main-div')
self.assertIn('미분류', main_div.text)
self.assertNotIn(category_politics.name, main_div.text)
def test_tag_page(self):
tag_000 = create_tag(name='bad_guy')
tag_001 = create_tag(name='america')
post_000 = create_post(
title='The first Post',
content='Hello world. We are the world',
author=self.author_000
)
post_000.tags.add(tag_000)
post_000.tags.add(tag_001)
post_000.save()
post_001 = create_post(
title='Stay Fool, Stay Hungry',
content='Story about Steve Jobs',
author=self.author_000
)
post_001.tags.add(tag_001)
post_001.save()
response = self.client.get(tag_000.get_absolute_url())
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
main_div = soup.find('div', id='main-div')
blog_h1 = main_div.find('h1', id='blog-list-title')
self.assertIn('#{}'.format(tag_000.name), blog_h1.text)
self.assertIn(post_000.title, main_div.text)
self.assertNotIn(post_001.title, main_div.text)
{% extends 'blog/base.html' %}
{% block content %}
<h1 id="blog-list-title">
Blog
{% if category %}<small class="text-muted">: {{ category }}</small>{% endif %}
{% if tag %}<small class="text-muted">: #{{ tag }}</small>{% endif %}
</h1>
{% if object_list.exists %}
{% for p in object_list %}
<!-- Featured blog post-->
<div class="card mb-4" id="post-card-{{ p.pk }}">
{% if p.head_image %}
<a href="#!"><img class="card-img-top" src="{{ p.head_image.url }}" alt="..." /></a>
{% else %}
<a href="#!"><img class="card-img-top" src="https://picsum.photos/id/1000/750/300" alt="..." /></a>
{% endif %}
<div class="card-body">
{% if p.category %}
<span class="badge bg-primary" style="float:right">{{ p.category }}</span>
{# <span class="badge bg-primary float-right">{{ p.category }}</span>#}
{% else %}
<span class="badge bg-primary" style="float:right">미분류</span>
{# <span class="badge bg-primary float-right">미분류</span>#}
{% endif %}
<div class="small text-muted">{{ p.created }} by {{ p.author }}</div>
<h2 class="card-title">{{ p.title }}</h2>
<p class="card-text">{{ p.content | truncatewords:50 }}</p>
{% for tag in p.tags.all %}
<a href="{{ tag.get_absolute_url }}">#{{ tag }} </a>
{% endfor %}
<br/>
<br/>
<a class="btn btn-primary" href="{{ p.get_absolute_url }}" id="read-more-post-{{ p.pk }}">Read more →</a>
</div>
</div>
{% endfor %}
{% else %}
<h3>아직 게시물이 없습니다</h3>
{% endif %}
{% endblock %}
{% extends 'blog/base.html' %}
{% block title %}{{ object.title }} - Blog{% endblock %}
{% block content %}
<!-- Post content-->
<!-- Post header-->
<h1>{{ object.title }}</h1>
{% if object.category %}
<span class="badge bg-primary" style="float:right">{{ object.category }}</span>
{# <span class="badge bg-primary float-right">{{ p.category }}</span>#}
{% else %}
<span class="badge bg-primary" style="float:right">미분류</span>
{# <span class="badge bg-primary float-right">미분류</span>#}
{% endif %}
<!-- Author -->
<p class="lead">
by
<a href="#">{{ object.author.username }}</a>
</p>
{% if request.user == object.author %}
<button type="button" class="btn btn-sm btn-outline-secondary" style="float:right">EDIT</button>
{% endif %}
<hr>
<!-- Date/Time -->
<p>Posted on {{ object.created }}</p>
<hr>
<!-- Preview image figure-->
{% if object.head_image %}
<img class="img-fluid rounded" src="{{ object.head_image.url }}" alt="{{ object.title }}" />
{% endif %}
<!-- Post content-->
{{ object.content }}
<br/>
{% for tag in object.tags.all %}
<a href="{{ tag.get_absolute_url }}">#{{ tag }}</a>
{% endfor %}
<hr/>
<!-- Comments section-->
<section class="mb-5">
<div class="card bg-light">
<div class="card-body">
<!-- Comment form-->
<form class="mb-4"><textarea class="form-control" rows="3" placeholder="Join the discussion and leave a comment!"></textarea></form>
<!-- Comment with nested comments-->
<div class="d-flex mb-4">
<!-- Parent comment-->
<div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
<div class="ms-3">
<div class="fw-bold">Commenter Name</div>
If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.
<!-- Child comment 1-->
<div class="d-flex mt-4">
<div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
<div class="ms-3">
<div class="fw-bold">Commenter Name</div>
And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors.
</div>
</div>
<!-- Child comment 2-->
<div class="d-flex mt-4">
<div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
<div class="ms-3">
<div class="fw-bold">Commenter Name</div>
When you put money directly to a problem, it makes a good headline.
</div>
</div>
</div>
</div>
<!-- Single comment-->
<div class="d-flex">
<div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
<div class="ms-3">
<div class="fw-bold">Commenter Name</div>
When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence.
</div>
</div>
</div>
</div>
</section>
{% endblock %}
https://neutronx.github.io/django-markdownx/installation/
Next 를 눌러 하라는대로 한다.
urlpatterns = [
path('blog/', include('blog.urls')),
path('admin/', admin.site.urls),
path('markdownx/', include('markdownx.urls')),
]
from markdownx.models import MarkdownxField
from markdownx.utils import markdown
...
class Post(models.Model):
title = models.CharField(max_length=30)
content = MarkdownxField()
...
...
def get_markdown_content(self):
return markdown(self.content)
마크다운언어를 쓸 수 있는 입력field가 보임.
이미지도 drag&drop 으로 바로 삽입 가능하다
<!-- Post content-->
{{ object.get_markdown_content | safe}}
<br/>
tags = models.ManyToManyField(Tag, null=True, blank=True)
OS 성능 개선을 위해 사진파일은 날짜폴더 밑에 저장으로 변경
MARKDOWNX_MEDIA_PATH = datetime.now().strftime('markdownx/%Y/%m/%d')
https://www.inflearn.com/course/%ED%8C%8C%EC%9D%B4%EC%8D%AC/questions