Django 개발 (6)

RXDRYD·2021년 8월 22일
0

Category 두줄 > 한줄로

base.html : 우측 col-6 부분 삭제

<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>

admin - Categorys 철자 변경

models.py : Meta 정보추가

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'        

Detailview 의 Category 수정

views.py : get_context_data 추가

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

tests.py

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)

Category 항목 클릭 반응

models.py : slug 생성

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'

tests.py : category 테스트 부분 추가

# 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

Slug 자동 생성

admin.py : Slug 를 미리 만들어주기 위한작업

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필드에 자동으로 생성된다. (공백은 - 로 연결)

urls.py : slug 추가

urlpatterns = [
    path('category/<str:slug>/', views.PostListByCategory.as_view()),
    path('<int:pk>/', views.PostDetail.as_view()),
    path('', views.PostList.as_view()),
]

views.py :

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')

tests.py : category 수정

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

views.py 전체코드

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,
#         }
#     )

post_list.html

{% 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-->

base.html : category부분

<!-- 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>

tests.py

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)

Tag

tests.py : TAG 함수, 테스트 부분 추가

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)

models.py : TAG 부분 추가

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들을 쪽쪽 놓아주고 싶다.

admin.py : 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 생겨있다 !

Tag 눌렀을 때 이동 가능하게

post_list.html

<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_detail.html

<!-- Post content-->
    {{ object.content }}
    <br/>

    {% for tag in object.tags.all %}
        <a href="{{ tag.get_absolute_url }}">#{{ tag }}</a>
    {% endfor %}
    <hr/>

continue...

tests.py

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)

post_list.html

{% 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 %}

post_detail.html

{% 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 %}

Django Markdownx

https://neutronx.github.io/django-markdownx/installation/

Next 를 눌러 하라는대로 한다.

mysite > urls.py

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('admin/', admin.site.urls),
    path('markdownx/', include('markdownx.urls')),
]

models.py : 기존Field대신 바꿔보자

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_detail.html

<!-- Post content-->
    {{ object.get_markdown_content | safe}}
    <br/>

models.py : TAG값은 비어있어도 되며 옵션값

tags = models.ManyToManyField(Tag, null=True, blank=True)

OS 성능 개선을 위해 사진파일은 날짜폴더 밑에 저장으로 변경

settings.py

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

profile
정리데쓰

0개의 댓글