프로젝트/장고 웹페이지 구축

[Django] 포스트 작성 페이지 구현하기 - (2)

eunjuu 2023. 10. 8. 21:32
728x90

🚧 어찌저찌 굴러가게 된 작성 페이지 (feat. 마크다운)

나름 대거 수정이 있었다. 마크다운을 적용한 과제 작성 페이지도 구축하고, 페이지들끼리 연결도 했다. 그 외에도 조금씩 수정을 해보았는데, 이제 제법 게시판의 기능을 하는 것 같다! 


💬 이런저런 주요 수정 사항! 일단 전체적인 구성을 먼저 소개하고, 자세한 코드는 더 밑에 적혀있습니닿

1. 게시판 리스트

1-1. No. 부분

→ 전에는 advancedlist.html에 {{ post.id }} 로 적어서 No.를 표현했는데, 이렇게 하니까 문제 발생. 예를 들어 post.id가 2인 게시물을 삭제하면 No.에 2번이 없어진다. (post id가 2번인 포스트가 삭제되었기 때문에 영원히 2번은 사라짐…) 그럼 1번 다음 바로 3번이 되는 문제가 생김. 그래서 그냥 post.id가 아니라 순서대로 번호 넘버링 하는 코드로 수정 !

<td style="color: DarkSeaGreen;">{{ page.start_index|default:0|add:forloop.counter0 }}</td>

 

1-2. 또 전에는 최근 작성한 게시물이 계속 기존에 있던 게시물 뒤로 쌓였다면, views.py에 '-created_at' 이렇게 앞에 '-' 추가해서 새 게시물이 맨 앞에서 계속 추가되게 함

→ 위에 캡쳐 사진 보면 가장 최근에 작성한 게시물이 맨 위에 올라온 것을 확인할 수 있다.

def AdvancedBlog(request):
    # AdvancedAssignment 모델의 모든 게시물을 가져와서 'created_at' 필드를 기준으로 내림차순 정렬합니다.
    postlist2 = AdvancedAssignment.objects.all().order_by('-created_at')
		...

 

2. 게시판 상세 페이지

게시판 리스트의 Title 눌렀을 때 보이는 게시물 html&css 변경

좀 깔끔한가요,,, 게시물 페이지에 마크다운이 적용되는데!! 아무튼 이렇게 잘 적용되는 것을 확인할 수 있었다. 댓글 파트는… 일단 구성은 했는데 좀 생각해보고 다른 기능으로 대체할까도 고민중이다.(예를 들어 관리자만 체크박스를 누를 수 있게 해 과제 제출 여부 판단 가능하게 하는…)

 

3. 게시물 작성 페이지

게시판 리스트 왼쪽 상단에 있는 WRITE 버튼을 누르면 게시판 작성 페이지로 넘어간다. 그리고 작성 페이지에 마크다운을 적용해보았다! (그건 밑에서 더 설명하겠다)

 


⌨️ 코드 구현 !

📎 전체적으로 참고한 사이트 : https://velog.io/@wodnr0710/Do-it-장고-부트스트랩-16장.-외부-라이브러리-활용하기

 

[Do it 장고 + 부트스트랩] 16장. 외부 라이브러리 활용하기

장고의 외부 라이브러리를 활용하여 폼 모양 꾸미기, 마크다운 적용, 로그인 기능 구현하기

velog.io

 

주요 설치 항목

🥣 django-crispy-forms

폼을 이용해 작성 페이지를 구현한다. 그냥 구현하면 왼쪽 사진처럼 입력 폼이 한 쪽으로 치우친다. 이걸 오른쪽 사진처럼 바꾸기 위해서는 django-crispy-forms를 이용한다.

 

🖍️ django-markdownx

이렇게 마크다운 페이지 완성 !! 여기서 또 주목하면 좋은 점은,, 제목 부분에 눈 모양 기호와 뷰(View) 수!! 일단 대충 이렇게 구성해두었고 세부적인 사항은 차차 수정하도록 하겠다.

 

  • 패키지 설치
pip install django-crispy-forms
pip install django-markdownx

 

  • settings.py에서 활성화

# Application definition

INSTALLED_APPS = [
	...
    
    'markdownx',
    'crispy_forms',
    'crispy_bootstrap5'
]

CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"

MARKDOWNX_MARKDOWN_EXTENSIONS = [
    'markdown.extensions.extra',
    'markdown.extensions.codehilite',
    'markdown.extensions.toc',
]

 

django markdownx는 urls.py에 경로를 추가해야 원활하게 작동한다. 다음과 같이 프로젝트 폴더의 urls.py를 열어 경로를 추가하자. 여기서 주의할 점은 app이름/urls.py가 아니라 장고 프로젝트 폴더/urls.py에 추가해야한다.

 

 

urlpatterns = [
   	...
    
    path('markdownx/', include('markdownx.urls')), # 추가!!
]

 

📂 코드는 심화 과제 페이지를 예시로 들어보겠다. (기초 과제 페이지도 동일하게 적용)

 

models.py

마크다운을 적용했기 때문에 models.py를 조금 수정했다.  

from django.db import models
from django.conf import settings
from markdownx.models import MarkdownxField # 추가
from markdownx.utils import markdown # 추가

# author은 settings 의 User 테이블 사용

class AdvancedAssignment(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
    content = MarkdownxField()  # 추가
    file_upload = models.FileField(upload_to='uploads/', null=True, blank=True, default='default_value.pdf') # 추가
    created_at = models.DateTimeField(auto_now_add=True)
    view_count = models.PositiveIntegerField(default=0)

    def get_content_markdown(self): # 추가
        return markdown(self.content) # 추가

 

forms.py

forms.py 파이썬 파일을 새로 만들었다.

 

AdvancedAssignment 모델을 위한 Django 폼 클래스 **AdvancedAssignmentForm**을 만들었습니다. 이 폼은 폼에 포함하려는 필드를 지정하고 각 필드의 HTML 렌더링을 사용자 정의하는 위젯을 제공합니다.

이 폼을 사용하여 AdvancedAssignment 모델의 인스턴스를 만들거나 업데이트하거나 표시하는 데 이제 사용할 수 있습니다. 예를 들어, 이 폼을 사용하여 템플릿에서 폼을 렌더링하고 뷰 함수에서 폼 제출을 처리할 수 있습니다.

 

from django import forms
from .models import AdvancedAssignment, BasicAssignment

class AdvancedAssignmentForm(forms.ModelForm):
    class Meta:
        model = AdvancedAssignment
        fields = ['title', 'content', 'file_upload']
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control'}),
            'content': forms.Textarea(attrs={'class': 'form-control'}),
            'file_upload': forms.FileInput(attrs={'class': 'form-control'}),
        }

 

views.py

from django.urls import reverse_lazy 
from django.shortcuts import render, get_object_or_404
from django.views.generic.edit import CreateView 
from .models import BasicAssignment, AdvancedAssignment
from .forms import AdvancedAssignmentForm, BasicAssignmentForm # 추가
from django.core.paginator import Paginator

def AdvancedBlog(request):
    # AdvancedAssignment 모델의 모든 게시물을 가져와서 'created_at' 필드를 기준으로 내림차순 정렬합니다.
    postlist2 = AdvancedAssignment.objects.all().order_by('-created_at')

    # Paginator 객체를 생성하고 한 페이지당 10개의 아이템을 표시합니다.
    paginator = Paginator(postlist2, 10)
    # 클라이언트 요청에서 현재 페이지 번호를 가져옵니다.
    page_number = request.GET.get('page')
    # 요청된 페이지 번호로 페이지 객체를 가져옵니다.
    page = paginator.get_page(page_number)
    return render(request, 'assignments/advancedlist.html', {'page': page})
    

def AdvancedPosting(request, post_id):
    # 게시글(Post) 중 pk(primary_key)를 이용해 하나의 게시글(post)를 검색
    post = get_object_or_404(AdvancedAssignment, id=post_id)

    # 조회 횟수 증가
    post.view_count += 1
    post.save()

    # 작성자 정보 가져오기
    author = post.author

    # 페이지를 열 때, 찾아낸 게시글(post)을 post라는 이름으로 가져옴
    return render(request, 'assignments/post.html', {'post': post, 'author': author})
    
class AdvancedCreate(CreateView):
    model = AdvancedAssignment
    form_class = AdvancedAssignmentForm  # 사용할 폼 클래스 설정
    template_name = 'assignments/submit.html'  # 기본 템플릿 설정
    success_url = reverse_lazy('advanced')  # 성공 시 리디렉션할 URL 지정

 

urls.py

from django.urls import path
from .views import BasicBlog, BasicPosting, AdvancedBlog, AdvancedPosting, BasicCreate, AdvancedCreate

urlpatterns = [
    # url path: assignments/advanced/
    path('advanced/', AdvancedBlog, name='advanced'),

    # URL:80/assignments/advanced/숫자로 접속하면 게시글-세부페이지(posting)
    path('advanced/<int:post_id>/', AdvancedPosting, name="AdvancedPosting"),

    # AdvancedCreate 뷰에 대한 URL 패턴을 추가합니다.
    path('advanced/create/', AdvancedCreate.as_view(), name='advanced_create'),

]

 

submit.html

게시판 작성 페이지 html

  <div class="container">
    <div class="row">
      <div class="col-lg-12">
        <div class="page-content">

            {% load crispy_forms_tags %}

            <div class="game-details">
                <div class="row">
                    <div class="col-lg-12">
                        <div id="assignment-content" class="content assignment-container">
                            <div class="row">
                                <div class="container">
                                <h3 class="assignment-title">Create New Assignment</h3>
                                    <form method="post" enctype="multipart/form-data" class="assignment-form">
                                    {% csrf_token %}
                                    {{ form|crispy }}
                                    <button type="submit" class="assignment-submit-button">Submit</button>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="markdown-preview">
            {{ form.media }}
            </div>

        </div>
      </div>
    </div>
  </div>

 

포스트 작성 페이지에서 내용을 입력해보면 {{ form.media }} 에 의해 실시간을 마크다운이 적용되어 렌더링된 모습이 아래에 나타난다.

 

post.html

<div class="game-details">
            <div class="row">
              <div class="col-lg-12">
                <div id="acontent" class="content" style="border-radius: 5px">
                  <div class="row">
                          <ul>
                            <li>
                              <ul>
                                <li style=" font-size: 1.9rem">Title: {{ post.title }}</li>
                                <li style=" font-size: 1.2rem">{{ post.created_at }} by {{author}}</li>
                                <li class="view-count"><i class="bi bi-eye-fill"></i> {{ post.view_count }}</li>
                              </ul>
                            </li>
                          </ul>
                  </div>
                </div>
               </div>
            </div>
          </div>

          <img src="" alt="" style="border-radius: 16px;">

          <div class="game-details">
            <div class="row">
              <div class="col-lg-12">
                <div id="acontent" class="content" style="border-radius: 5px">
                  <div class="row">
                          <ul>
                            <li>
                              <ul>
                                <li style="font-size: 1.2rem">{{ post.get_content_markdown | safe }}
                            </li>
                          </ul>
                  </div>
                </div>
               </div>
            </div>
          </div>

 

css는 생략,,, 일단 형식만 공유하는 느낌으로 하겠다.

 


힘들어,,, ㅜㅅㅜ

728x90