Django blog_pj(comment)

Haks.·2025년 1월 24일
0

How to use

목록 보기
15/32

Django

📌 Django Static File 사용하기

  1. 최상위 폴더 static 폴더 생성
  2. config/settings.py STATIC_URL아래쪽에, STATIC_DIR, STATIC_ROOT 작성
STATIC_URL = 'static/'
STATIC_DIR = BASE_DIR / 'static' # 명시적으로 정의한 거임, 
# 안쓰고 STATICFILES_DIR 에 한번에 적어도 됨 
# STATIC_JS_DIR = BASE_DIR /'js' 이런식으로도 있을 수도 있음 
# 개발환경에 따라 여러가지 경로들을 같이 넣을 수 있게 만들어져 잇음
# 결국 나눠져있는 static들을 한곳에 넣어서 배포하기 위해 files에 넣고 root로 보내는 거임 

STATICFILES_DIRS =[ # 장고 개발시 스태틱 파일들을 여러곳으로 모아 놓을수 있다.
    STATIC_DIR,
]
# 배포 환경에서 여러가지 경로들을 한곳으로 모아서 관리할 수 있게 만들어진 경로
STATIC_ROOT = BASE_DIR / '.static_root' # 나눠져있는 STATIC DIR 들 배포하시 한곳에 모아서 사용하기에 나눠져 있는거임
# 장고는 static 을 만들고 settings에 기본적으로 static을 등록해야함 
  1. bootstarp -> -> Read installation docs 접속 -> Compiled CSS and JS 다운
  2. static 폴더로 이동 css,js 폴더
  3. 사용하려는 html 파일 제일위에 {% load static %} 작성, temlpate에서의 import
  4. base.html 작성 예시
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>블로그 프로젝트</title
    <!-- settings에 staitc 경로를 지정해 주었기에 바로 그다음 경로부터 입력해 주었다. -->
    <link href="{% static 'css/bootstrap.css' %}" rel = stylesheet>
<!-- 젤밑에 -->
    {% block content %}{% endblock %} <!-- 컨텐트라는 구멍을 뚫어놓음 -->
    <footer>
        
    </footer>
    <script src = "{% static 'js/bootstrap.bundle.js' %}">
        
    </script>
    {% block js %}{% endblock %}
    
  1. 개발자 도구를 사용하여 element 주소 들어가 작동 확인

📝 댓글 기능 만들기 admin

따로 앱을 만들어 제작해도 되지만 댓글은 blog 기능 안에 만드는것이 합리적이라 안에 작성

  1. blog/models.py Comment 작성
  2. 요구되는 필드 생각, 블르고, 작성자, 내용, 작성일자, 수정일자
  3. blog의 기능과 공통되는 작성일자 수정일자의 반복을 줄이기 위해
  4. utils/models.py 생성 TimestampModel 모델 코딩
# utils/models.py
from django.db import models

class TimestampModel(models.Model): # 이 안에 테이블로 만드는것 해줫기에 상속시키면 또 상속 안시켜도 됨
    created_at = models.DateTimeField('작성일자', auto_now_add=True)
    updated_at = models.DateTimeField('수정일자', auto_now=True)
    
    class Meta:
        abstract = True # 상속을 할거고 실제로 db에서 관리할 것이 아니기에 이것은 absract 하다고 넣어준 것임
  1. TimestampModel에 모델을 상속시켜 주어 있기에 Commnet나 Blog 모델에 이 모델만 상속시켜 주면된다.
```python
from utils.models import TimestampModel
class Comment(TimestampModel):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE) # 블로그가없으면 댓글도 필요없으니 CASCADE
    content = models.CharField('본문', max_length=255)
    author = models.ForeignKey(User, on_delete=models.CASCADE) # author 니까 user 연결
    # Admin 페이지에서 객체목록을 볼떄 사용
    # 디버깅시 print(comment) 또는 str(comment)를 호출하면 나온다.
    def __str__(self):
        return f'{self.blog.title} 댓글'

    class Meta:
        verbose_name='댓글'
        verbose_name_plural = '댓글 목록'
  1. migrate 후 db browser로 잘들어갔나 확인
  2. Commnet를 admin 페이지에 등록시켜주기 위해 admin.py 수정
admin.site.register(Comment)

     # 블로그 안에서 이것을 수정할거기야 Inline
class CommentInline(admin.TabularInline): # 표로 만든 형태
    model = Comment # 모델을 불러오고
    fields = ['content', 'author'] # 수정할 필드


@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
    inlines = [ # 블로그를 수정시 블로그안에서 보고 수정하는것이 좀더 보기 좋아서 inline을 써서 하였다. 
        CommentInline
    ]


8. 댓글 입력 후 db에 들어가나 확인

📝 댓글 form 생성

  1. blog 페이지의 아래에 댓글이 존재하게 설정
  2. comment form을 생성해 DetailView 에서 렌더링
  1. froms.as_p 사용을 위해 CommentForm 제작, blog/forms.py
from django import forms
from blog.models import Blog, Comment

class CommentForm(forms.ModelForm):
    class Meta:
        model =Comment
        fields = ('content',)
  1. DetailView에서 CommentForm을 사용하기 위해, get_context_data 함수 사용
  2. 이 함수를 통해 html 에서 context['comment_form']을 사용할수 있도록 등록
class BlogDetailView(DetailView):
	model = Blog
    template_name = 'blog_detail.html'
    
    # context 가 딕셔너리 형태로 저장되어 있기에 **kwargs 
    def get_context_data(self, **kwargs): # DetailView 내장함수
    	context = super().get_context_data(**kwargs) # 기존의 데이터 가져오기
        context['comment_form'] = CommentForm() # 컨텍스트에 키 벨류 추가
        return context # 저장
  1. template_name 으로 등록되어 있는 detail.html 에서 comment_form 사용 가능.
    <div style="text-align: right">
        {{ blog.author.username }}
    </div>
    <p>{{ blog.content }}</p>
    <hr>
    <form method="post">
        {{ comment_form.as_p }}
    </form>
    <hr>
    <a class="btn btn-sm btn-info" href ="{% url 'blog:list' %}">목록</a>
{% endblock %}
  1. 나오는 이름을 verbose_name 은 모델 정의할떄 정의해 놓은 것이다. 그것을 원하는 이름으로 보이게 하고 싶으면, forms.py 에서 해당 모델의 widgets={} 를 수정한다. label 은 모델에서 지정한 이름을 다르게 설정가능, content 가 나오는 모양도 지정해놓고 보여줄 수 있다.
blog/forms.py
class CommentForm(forms.ModelForm):
    class Meta:
        model =Comment
        fields = ('content',)
        widgets ={ # 안넣으면 기본값 실제 화면 그려줄대 어떻게 나타낼지 
            'content' : forms.TextInput(attrs={'class': 'form-control'}) # 컨텐트를 수정할꺼, html 에서 타입은 text 인 인풋창이 생기게 할거야
            # attrs attribute 는 form-control 이야
        }
        labels = { # 원래는 모델에서 정해준 본문 이라 나오지만 이 내용을 폼에서 보여줄때 댓글, 이런식으로 보여줘 한것
            'content' : '댓글'
        }
  1. 로그인이 되어있는지 확인하는 코드 추가, 댓글은 로그인이 되어있어야 하니까
    {% if request.user.is_authenticated %}
        <form method="post">
            {% csrf_token %}
            {{ comment_form.as_p }}
            <div class="text-end">
                <button class="btn btn-primary">작성</button>
            </div>
        </form>
        <hr>
        <a class="btn btn-sm btn-info" href ="{% url 'blog:list' %}">목록</a>
    {% endif %}
  1. 페이지에서 작성을 해보면 현재 받아지지 않는다. DetailView에서 POST 동작 설정을 해놓지 않았기에 받아지지 않는것이다. post 함수 추가
def post(self, *args, **kwargs):
	# 사용자가 보내온 post요청이 commentForm 에 맞춰 comment_form에 등록
	comment_form = CommentForm(self.request.POST) 
	
    if not comment_form.is_valid():
    	self.object = self.get_object() # 해당 객체 받아오고 그대로 유지 폼이 타당하지 않기에
        context = self.get_context_data(object = self.object)
        context['comment_form'] = commment_form
        # 유효하지 않는 form을 담는 이유는 이형태를 다시 그상태 그대로 클라이언트에게 보내기 위해서, 어떤 것을 잘못보낸지 가르쳐주기 위해서
        return self.render_to_response(context) 
        # 유효하지 않은 데이터를 개발자가 아닌 응답페이지로 보내는 것임.
    
   if not self.request.user.is_authenticated: # 혹시 잘못된 요청이 들어올떄 서버에서 한번더 먹는거
        raise Http404
            # 유효한 상태 저장
        comment = comment_form.save(commit=False)
        # comment.blog = self.get_object() 이렇게 해도되고 아래로 해도되고
        comment.blog_id = self.kwargs['pk'] # url 에서 가져오는 것
        comment.author = self.request.user
        comment.save()
        
        # 클래스에선 HttpResponseReriect 써야함
        # 왜이렇게 쓰냐 현재 urls에 <int:pk> 라고 써놨음 그러면  blog:detail의 경로에서 저 값을 얻고 그것을 'blog:detail': pk값 이렇게 저장한다. 딕셔너리 형태로 받아와져서 kwargs='pk':self.kwargs['pk']})) 가 되는것이다.
        # path 경로가 <int:blog_pk>로 저장되어 있으면 kwargs={'blog_pk':self.kwargs['blog_pk']} 가 맞다.
        return HttpResponseRedirect(reverse_lazy('blog:detail', kwargs={'pk':self.kwargs['pk']}))
  1. detail.html 수정, blog, comment 사용 가능
  2. 댓글을 최신순으로 쓰고싶으면 comment 모델 Meta에 ordering =[-created_at] 추가, migrate
  3. sql의 join과 비슷한 prefetch_related 의 2가지 사용 방법
  • quertset 을 통해 가져올때 같이 가져오기
    class BlogDetailView(DetailView):
      model = Blog
      queryset = Blog.objects.all().prefetch_related('comment_set','comment_set__author')
  • 모델에 등록 해놓기
    • todo 객체에서 comment를 불러 올 수 있음, todo.comments.all()
    • context에 넣어서 사용 가능
    class TodoDetailView(DetailView):
    model = Todo
    template_name = 'todo_detail.html'

    def get_context_data(self, **kwargs):
        # 기존의 context 가져오기
        context = super().get_context_data(**kwargs)
        # 현재의 Todo 객체
        todo = self.get_object()
        # comments를 context에 추가
        context['comments'] = todo.comments.all()  # related_name='comments' 덕분에 접근 가능
        return context
queryset을 설정하면 기본적으로 다음과 같은 데이터가 컨텍스트에 포함됩니다:
	1.	todo (또는 object): 해당 Todo 객체.
	  •	self.object로 접근 가능.
	2.	todo.comment_set.all:
	  •	Todo 객체와 연결된 모든 댓글을 미리 가져와 사용 가능.
	3.	comment.author:
	  •	각 댓글의 작성자 정보도 미리 가져와 사용 가능.

📝 CreatView를 만들어 comment 생성

lass CommentCreateView(LoginRequiredMixin, CreateView):
    model = Comment
    form_class = CommentForm

    def get(self, *args, **kwargs):
        raise Http404
    
    def form_valid(self, form):
        blog = self.get_blog()
        self.object = form.save(comm it=False)
        self.object.author = self.request.user
        self.object.blog = self.get_blog()
        self.object.save()
        
        return HttpResponseRedirect(reverse('blog:detail', kwargs={'pk':blog.pk}))
        
    def get_blog(self):
        pk = self.kwargs['blog_pk'] # comment pk 랑 헷갈릴수 잇기에
        blog = get_object_or_404(Blog, pk=pk)
        return blog

# /comment/create/<int:blog_pk>/

#blog/urls.py

    path('comment/create/<int:blog_pk>', cb_views.CommentCreateView.as_view(), name='comment_create'),
]
  1. create view 는 기본적으로 get요청이 있기에 막아주기위해 404 를 만듬
    def get(self, *args, **kwargs):
        raise Http404
  1. detail.html 페이지 수정
        <form method="post" action="{% url 'blog:comment_create' blog.pk %}">
            {% csrf_token %}
            {{ comment_form.as_p }}
            <div class="text-end">
                <button class="btn btn-primary">작성</button>
            </div>
        </form>
        <hr>
  1. url 을 만들어 줘야하고 , get 바꿔줘야댐, detail.html 에서 action 넣어줘야함 편한거 사용

📝 DetailView -> ListView

class BlogDetail(ListView):
    model = Comment
    template_name = 'blog_detail.html'
    # listview이기에 paginate 사용가능
    paginate_by = 10

    def get(self, request, *args, **kwargs):
        # url에 전달된 blog_pk를 받아 Blog에서 모델에서 해당블로그 조회 그 객체를 self.object 에 저장
        self.object = get_object_or_404(Blog, pk=kwargs.get('blog_pk')) 
        # 부모 클래스인 ListView의 get 메서드를 호출하여, 기본 동작(HTML 렌더링 및 get_queryset 호출)을 수행합니다.
        return super().get(request,*args,**kwargs)

	
    # prefetch_realted 를 사용하여 DB에서 쿼리를 한번만 일으킨다.
    # html에서 사용해서 commmet.all() 을 사용하면 DB에 쿼리를 한번더 주는거라 부하가 심해진다
    # 그래서 한번에 가져올떄 prefetch.related 를 사용해서 가져와서 db에 부하를 덜준다.
    def get_queryset(self):
        return self.model.objects.filter(blog=self.object).prefetch_related('author')

	# 사용할 context 추가.
    def get_context_data(self, **kwargs):
        context= super().get_context_data(**kwargs)
        context['commnet_form'] = CommentForm()
        context['blog'] = self.object
        return context

📌 Tip

  • html 똑같은 부분이 있으면 html 파일 하나 만들어서 넣어놓고 그 파일안의 내용을 쓰려면

  • {% include '해당파일.html' %} 이렇게 사용 가능

  • shell_plus 를 통한 댓글 갯수 늘리기

In [1]: todo_list = Todo.objects.all()

In [2]: todo_list
Out[2]: <QuerySet [<Todo: 생성>, <Todo: cbv 새러운생성>, <Todo: cbvv생성>, <Todo: 생성2>]>

In [3]: new_list = []

In [4]: for i in range(40):
   ...:     for todo in todo_list:
   ...:         todo.id = None
   ...:         new_list.append(todo)
   ...:

In [5]: Todo.objects.bulk_create(new_list)
    

1개의 댓글

comment-user-thumbnail
2025년 4월 8일

This Django blog project walkthrough by Haks is a fantastic Sprunki Pyramixed resource for anyone looking to dive into building a comment system with Django!

답글 달기

관련 채용 정보