Python/Django

Django 댓글기능 추가

happyso 2020. 8. 7. 16:09

- blog/urls.py

from django.urls import path
from . import views


urlpatterns = [
    # loacalhost:8080/
    path('', views.post_list, name='post_list'),
    # localhost:8080/post/5
    path('post/<int:pk>/', views.post_detail, name='post_detail_test'),
    # localhost:8080/post/new
    path('post/new/', views.post_new, name='post_new'),
    # localhost:8080/post/5/edit
    path('post/<int:pk>/edit', views.post_edit, name='post_edit'),
    # localhost:8080/post/5/delete
    path('post/<int:pk>/delete/', views.post_delete, name='post_delete'),
    # localhost:8080/post/5/comment
    path('post/<int:pk>/comment/', views.add_comment_to_post, name='add_comment_to_post'),
    # localhost:8080/comment/5/approve
    path('comment/<int:pk>/approve', views.comment_approve, name='comment_approve'),
    # localhost:8080/comment/5/remove
    path('comment/<int:pk>/remove', views.comment_remove, name='comment_remove'),
]

 

- blog/views.py

from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse
from django.utils import timezone
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required

from .models import Post, Comment
from .forms import PostForm
from .forms import PostModelForm, CommentForm


# Post 목록
def post_list(request):
    #name = 'Django'
    # return HttpResponse('''
    #     <h2>Post List</h>
    #     <p>웰컴 {name}!!!</p>
    #     <p>{content}<p/>'''.format(name=name, content=request.content_type))
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'posts': posts})

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})

@login_required
def post_new(request):
    if request.method == "POST":
        # Form 데이터를 입력하고 등록요청 시 보여주는 부분
        form = PostForm(request.POST)
        # Form 데이터가 clean 한 상태
        if form.is_valid():
            # PostForm 으로 저장하는 법
            print(form.cleaned_data)
            post = Post.objects.create(author=User.objects.get(username=request.user),
                                       published_date=timezone.now(),
                                       title=form.cleaned_data['title'],
                                       text=form.cleaned_data['text'])
            # Post ModelForm 으로 저장하는 법
            # title, text 필드의 값이 저장된다.
            # post = form.save(commit=False)
            # post.author = User.objects.get(username=request.user)
            # post.published_date = timezone.now()
            # DB에 등록됨
            # post.save()
            return redirect('post_detail_test', pk=post.pk)
    else:
        # 등록 Form 보여주는 부분
        form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})

@login_required
def post_edit(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostModelForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = User.objects.get(username=request.user)
            post.published_date = timezone.now()
            post.save()
            return redirect('post_detail_test', pk=post.pk)
    else:
        form = PostModelForm(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form})

@login_required
def post_delete(request, pk):
    post = get_object_or_404(Post, pk=pk)
    post.delete()
    return redirect('post_list')

def add_comment_to_post(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.post = post
            comment.save()
            return redirect('post_detail_test', pk=post.pk)
    else:
        form = CommentForm()
    return render(request, 'blog/add_comment_to_post.html', {'form': form})

@login_required
def comment_approve(request, pk):
    comment = get_object_or_404(Comment, pk=pk)
    comment.approve()
    return redirect('post_detail_test', pk=comment.post.pk)

@login_required
def comment_remove(request, pk):
    comment = get_object_or_404(Comment, pk=pk)
    post_pk = comment.post.pk
    comment.delete()
    return redirect('post_detail_test', pk=post_pk)

 

- blog/templates/blog/base.html

<!DOCTYPE html>
{% load static %}
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Blog List</title>
    {% load static %}
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <!-- Optional theme -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <!-- google lobster font-->
    <link rel="stylesheet"
    href="https://fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext"
    type="text/css">
    <!-- google Chilanka fonf-->
    <link rel="stylesheet"
    href="https://fonts.googleapis.com/css?family=Chilanka&subset=latin,latin-ext"
    type="text/css">
    <!-- 개발자 정의 CSS-->
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    <!-- favicon 적용하기 -->
    <link rel="shortcut icon" href="{%static 'favicon.ico'%}">
</head>
<body>
    <div class="page-header">
        {% if user.is_authenticated %}
            <a href="{% url 'post_new' %}" class="top-menu">
                <span class="glyphicon glyphicon-plus"></span>
            </a>
            <p class="top-menu">Hello {{user.username}}
            <small>(<a href="{% url 'logout' %}?next={{request.path}}">Log out</a> )</small></p>
        {% else %}
            <a href="{% url 'login' %}" class="top-menu">
                <span class="glyphicon glyphicon-lock"></span>
            </a>
        {% endif %}
        <h1><a href="/">Django Blog</a></h1>
    </div>
    <div class="content container">
        <div class="row">
            <div class="col-md-8">
            {% block content %}
            {% endblock %}
            </div>
        </div>
    </div>


    {% comment "Optional note" %}
        Server comment
    {% endcomment %}
    <!-- html Client Comment -->
</body>
</html>

 

- blog/templates/blog/add_comment_to_post.html

{% extends 'blog/base.html' %}

{% block content %}
    <table>
        <h1>New Comment</h1>
        <form method="POST" class="post-form">
            {% csrf_token %}
            <table class="table table-bordered table-hover">
                {{form.as_table}}
            </table>
            <button type="submit" class="save btn btn-default">Send</button>
        </form>
    </table>
{% endblock%}

 

- blog/templates/blog/post_list.html

{% extends 'blog/base.html' %}

{% block content%}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                <p>published : {{post.published_date}}</p>
            </div>
                <h1><a href="{% url 'post_detail_test' pk=post.pk %}">{{post.title}}</a></h1>
                <a href="{% url 'post_detail_test' pk=post.pk %}">Comments: {{post.approved_comments.count}}</a>
        </div>
        {% empty %}
            <div>요청하신 Post가 존재하지 않습니다. </div>
    {% endfor %}

{% endblock %}

 

- blog/templates/blog/post_detail.html

{% extends 'blog/base.html' %}

{% block content%}
<script>
    function post_remove(){
        //{% url 'post_delete' pk=post.pk %}
        var result = confirm('삭제하시겠습니까?')
        if(result){
            var remove_url = "{% url 'post_delete' pk=post.pk %}"
            document.location.href = remove_url
        }
    }
</script>
    <div class="post">
        {% if post.published_date%}
        <div class="date">
            {{post.published_date}}
        </div>
        {% endif %}
        {% if user.is_authenticated %}
            <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}">
                <span class="glyphicon glyphicon-pencil"></span>
            </a>
            <a class="btn btn-default" href="#" onclick="post_remove()">
                <span class="glyphicon glyphicon-remove"></span>
            </a>
        {% endif %}
        <h1>{{post.title}}</h1>
        <p>{{post.text|linebreaksbr}}</p>
    </div>
<hr>
<a class="btn btn-default" href="{% url 'add_comment_to_post' pk=post.pk %}">Add comment</a>

{% for comment in post.comments.all %}
    {% if user.is_authenticated or comment.approved_comment %}
        <div class="comment">
            <div class="date">
                {{comment.created_date}}
                {% if not comment.approved_comment %}
                    <a class="btn btn-default" href="{% url 'comment_remove' pk=comment.pk %}">
                        <span class="glyphicon glyphicon-remove"></span>
                    </a>
                    <a class="btn btn-default" href="{% url 'comment_approve' pk=comment.pk %}">
                        <span class="glyphicon glyphicon-ok"></span>
                    </a>
                {% endif %}
            </div>
            <strong>{{comment.author}}</strong>
            <p>{{comment.text|linebreaks}}</p>
        </div>
    {% endif %}
{% empty %}
    <p>No comment here yet :(</p>
{% endfor %}
{% endblock %}

 

- blog/templates/registration/login.html

{% extends "blog/base.html" %}

{% block content %}
    {% if form.errors %}
        <p>이름과 비밀번호가 일치하지 않습니다. 다시 시도해주세요.</p>
    {% endif %}

    <form method="post" action="{% url 'login' %}">
        {% csrf_token %}
        <table class="table table-bordered table-hover">
            <tr>
                <td>{{form.username.label_tag}}</td><td>{{form.username}}</td>
                <td>{{form.password.label_tag}}</td><td>{{form.password}}</td>
            </tr>
        </table>

        <input type="submit" value="login" class="btn btn-primary btn-lg"/>
        <input type="hidden" name="next" value="{{next}}"/>
    </form>
{% endblock %}

 

- mydjango/settings.py

"""
Django settings for mydjango project.

Generated by 'django-admin startproject' using Django 3.0.9.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""

import os
import pymysql
pymysql.version_info = (1, 3, 13, "final", 0)
pymysql.install_as_MySQLdb()

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'i2t1nrwlwsdu&gl1-z-^nlc75%rp(gndy9-etx=&wsg%wb6^&5'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    'Board',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mydjango.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'mydjango.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases

DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    # }
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_db', # DB명
        'USER': 'python', # 데이터베이스 계정
        'PASSWORD': 'python', # 계정 비밀번호
        'HOST': 'localhost', # 데이테베이스 IP
        'PORT': '3306', # 데이터베이스 port
    }
}


# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/

LANGUAGE_CODE = 'ko'

TIME_ZONE = 'Asia/Seoul'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, STATIC_URL)

LOGIN_REDIRECT_URL = '/'

 

- blog/models.py

from django.db import models
from django.utils import timezone
from django import forms

def min_length_3_validator(value):
    if len(value) < 3:
        raise forms.ValidationError('제목은 3글자 이상 입력해주세요')


# Create your models here.
class Post(models.Model):
    # 작성자
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    # 제목
    title = models.CharField(max_length=200, validators=[min_length_3_validator])
    # 내용
    text = models.TextField()
    # 작성일자
    created_date = models.DateTimeField(default=timezone.now)
    # 게시일자
    published_date = models.DateTimeField(blank=True, null=True)
    # 필드 추가 - 삭제할 예정
    #test = models.TextField()

    # 게시 일자에 현재 날짜를 대입해주는 함수
    def publish(self):
        self.published_date = timezone.now()
        self.save()

    # 객체 주소 대신 글제목을 반한해주는 toString()함수
    def __str__(self):
        return self.title

    def approved_comments(self):
        return self.comments.filter(approved_comment=True)

class Comment(models.Model):
    post = models.ForeignKey('blog.Post', on_delete=models.CASCADE, related_name='comments')
    author = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(default=timezone.now)
    approved_comment = models.BooleanField(default=False)

    def approve(self):
        self.approved_comment = True
        self.save()

    def __str__(self):
        return self.text

 

- blog/forms.py

from django import forms
from .models import Post, Comment



# validator 함수 정의
# title 입력필드의 길이 체크 < 3
def min_length_3_validator(value):
    if len(value) < 3:
        raise forms.ValidationError('title은 3글자 이상 입력해주세요')


# PostFrom 클래스 선언
class PostForm(forms.Form):
    # title = forms.CharField()
    title = forms.CharField(validators=[min_length_3_validator])
    text = forms.CharField(widget=forms.Textarea)


# PostModelFrom 클래스 선언
class PostModelForm(forms.ModelForm):
    # title = forms.CharField()
    # title = forms.CharField(validators=[min_length_3_validator])
    # text = forms.CharField(widget=forms.Textarea)
    class Meta:
        model = Post
        fields = ('title', 'text',)

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ('author', 'text')

 

- blog/admin.py

from django.contrib import admin
from .models import Post, Comment

class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'title', 'count_text']
    list_display_links = ['title']

    def count_text(self, obj):
        return '{}글자'.format(len(obj.text))
    count_text.short_description = 'text 글자수'

# Register your models here.
admin.site.register(Post, PostAdmin)
admin.site.register(Comment)

 

- blog/static/css/blog.css

.page-header{
    background-color: #ed7777;
    margin-top: 0;
    padding: 20px 20px 20px 40px;
}

.page-header h1, .page-header h1 a, .page-header h1 a:visited, .page-header h1 a:active{
    color: #53228f;
    font-size: 36pt;
    text-decoration: none;
}
h1, h2, h3, h4, .top-menu{
    font-family:'Chilanka';
    font-size: 16pt;
}
.content{
    margin-left:40px;
}
.date{color: #828282;}
.save{float:right;}
.post-from textarea, .post-form input{width:100%;}
.top-menu, .top-menu:hover, .top-menu:visited{
    color: #ffffff;
    float:right;
    font-size:26pt;
    margin-right:20px;
}
.post{margin-bottom:70px;}
.post h1 a, .post h1 a:visited{color:#000000;}

.comment{
    margin: 20px 0px 20px 20px;
}

 

- 결과화면

1) 댓글이 등록되어있지 않으면 No comment here yet :( 라고 출력됨

 

2) 댓글 입력

 

3) 입력 후 저장 누른 화면

 

4) 저장을 했어도 approve버튼을 클릭하지 않으면 Comment갯수가 0개라고 나옴

 

5) 다시 돌아가서 approve 버튼 클릭

 

6) 다음과 같이 삭제 및 approve버튼 사라짐

 

7) 이제는 Comments갯수가 1개로 출력