SSAFY/Django

[DJANGO] 댓글 만들기

황성안 2021. 3. 25. 14:05
728x90

 

DATABAS

  • 여러 사람이 공유하여 사용할 목적으로 체계화하여 관리하는 데이터의 집합이다.

 

RDB(관계형 데이터베이스)

  • 관계형 모델을 기반으로 하는 전통적인 데이터베이스

RDBMS

  • 관계형 데이터베이스를 관리하는 시스템
    • SQLite
    • ORACLE
    • SQLServer
    • MySQL
    • 등...
  • 여러 사람이 동시에 수정할 수 있는 거대한 스프레드시트의 집합(시험)
  • 게시글(Article)
    • 스키마를 기반으로 만들어지는 실제 데이터가 ROW

 

관계형 데이터베이스?

데이터베이스에는 많은 테이블이 존재할수 있다.

그리고 그 테이블 사이에는 관계가 지어질수있다.

이유는? 현실 세계를 표현하기위해!!!

 

게시글(댓글)

image-20210324123911590image-20210324123924790

 

 
약속 1.

하나의 필드에는 하나의 값만 저장할 것.

 

image-20210324124202301

 

필드가 너무 늘어난다, 매번 스키마가 변해야한다.

약속 2.

컬럼 이름은 고유하게 하나만 만들 것

image-20210324124255947

약속 3.

RDB에서 가능하면 레코드 값의 중복은 피할 것

 

 

 

1:N 관계

하나의 게시글은 여러개의 댓글을 가질 수 있다.

image-20210324124730269

 

foreignKey

image-20210324124821835

models.ForeignKey(필수1, 필수2)

필수1 : 참조하려는 모델 객체(class이름)

필수2: "on_delete="가 필요합니다.

( CASCADE, PROTECT ) 기입 가능

SET_NULL (NULL=TRUE)

SET_DEFAULT(디폴트 필요)

우리는 CASCADE를 주로사용하게 된다.

 

 

Code

빈공간에서 vs code를 열어줍니다.

django-admin startproject config .

python manage.py startapp articles

그리고 settings.py의 installed_apps 에 articles 를 추가해줍니다.

다음 models.py 로 이동 class만들어주기

추가적으로 게시글이 지워지면 댓글도 같이지워져야겠습니다.

만약에 부모 테이블이 지워졌을때 댓글만 남게 되면 어떻게되나?

이를 orphan 이라한다..(고아)

이런일이 없도록 CASCADE라는 것을 추가시켜줍니다.

(만약 그대로 지킬려면 protect를 사용해줍니다.)

(https://docs.djangoproject.com/en/3.1/ref/models/fields/#arguments)

from django.db import models

# Create your models here.
class Article(models.Model):
    # 1 : Parent table
    title = models.CharField(max_length=100)
    content = models.TextField()

class Comment(models.Model):
    # N : Child Table
    content = models.TextField()
    #    article = models.Foreignkey('Article', on_delete=models.CASCADE)
    article = models.ForeignKey(Article, on_delete=models.CASCADE) # article의 pk 저장

다음 settings.py 에서 shell_plus 를 사용할수있도록 셋팅

INSTALLED_APPS = [
    'articles',

    'django_extensions',
    
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

다음 쉘 플러스를 켜줍니다.

python manage.py shell_plus
comment = Comment()

comment.context='댓글1'

article = Article.objects.create(title='1번게시글', content='1번 내용')

comment.article = article

comment.save()

 

comment.article

comment.article.title

 

2번만들기

comment2 = Comment()

comment2.context='댓글2'

article = Article.objects.create(title='2번게시글', content='2번 내용')

comment2.article = article

comment2.save()

 

위에는 N: 1을 표시하여 확인한것이고

아래는 1:N 을 소환하는것

article.comment_set.all()

쿼리값으로 나올 것입니다.

 

Article.comment_set.all()

저 녀석은 되지않는다

인스턴스 == 실제 게시글 data이다.

Class == 스키마 == 독단적인

 

ex) 1번 게시글의 2번째 댓글의 내용은?

article.comment_set.all()[1].content





article.comment_set.get(pk=2).content

 

3번째 댓글

comment3 = Comment()
comment3.content = '3'
comment3.article = article
comment3.save()

article.comment_set.all()

 

이시점부터는 https://docs.djangoproject.com/en/3.1/topics/db/queries/를 이용해야한다.

 

 

 

댓글달기 coding

articles

forms.py

from django import forms
from .models import Article, Comment


class ArticleForm(forms.ModelForm):

    class Meta:
        model = Article
        fields = '__all__'
        # exclude = ('title',)
        

class CommentForm(forms.ModelForm):

    class Meta:
        model = Comment
        fields = ('content',) # comma please...!




models.py

from django.db import models

# Create your models here.
class Article(models.Model):
    title = models.CharField(max_length=10)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def get_absolute_url(self):
        return f'/articles/{self.pk}/'

class Comment(models.Model):
    content = models.TextField()    #NOT NULL
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
    )

 

urls.py

path('<int:article_pk>/comments/', views.create_comment, name='create_comment'),

views.py

def detail(request, pk):
    article = get_object_or_404(Article, pk=pk)
    form = CommentForm()    # Comment 폼 생성

    comments = article.comment_set.all()

    context = {
        'article': article,
        'form':form,
    }
    return render(request, 'articles/detail.html', context)


def create_comment(request, article_pk):
    #1. 댓글이 가리킬 게시글을 가져옵니다.
    article = Article.objects.get(pk=article_pk)
    #2. 사용자 입력값 (댓글)을 꺼내서
    form = CommentForm(request.POST)
        #3. 유효성 검사를 하고
    if form.is_valid():
        #4. 저장합니다.
        #commit=False 설정시 시점에 DB 저장 x
        # pk 값이 없는 comment를 반환
        comment = form.save(commit=False) # 이 시점에 DB 반영
        comment.article = article
        comment.save()
        #redirect('articles:detal', article.pk)
        #article 사용시 models에서 get absolute_url 사용해야합니다.
    return redirect(article)




 

detail.html

{% extends 'base.html' %}

{% block content %}
  <h2>DETAIL</h2>
  <h3>{{ article.pk }} 번째 글</h3>
  <hr>
  <p>제목 : {{ article.title }}</p>
  <p>내용 : {{ article.content }}</p>
  <p>작성시각 : {{ article.created_at }}</p>
  <p>수정시각 : {{ article.updated_at }}</p>
  <hr>
  <a href="{% url 'articles:update' article.pk %}" class="btn btn-primary">[UPDATE]</a>
  <form action="{% url 'articles:delete' article.pk %}" method="POST">
    {% csrf_token %}
    <button class="btn btn-danger">DELETE</button>
  </form>
  <a href="{% url 'articles:index' %}">[back]</a>

  <hr>
  <form action="{% url 'articles:create_comment' article.pk %}" method="POST">
    {% csrf_token %}
    {{ form }}
    <button>댓글 작성</button>
  </form>
  <hr>
  {% for comment in comments %}
    <div>{{ comment.content }}</div>
  {% endfor %}
{% endblock %}

for 문 comments를 article.comment_set.all 로해도됨

 

오후 이어서(로그인후 댓글)

@require_POST를 사용하게된다면

유효성 검사가 통과하지 못하는 에러를 폼에 담아

보여주기 위한 것이라 적었지만 현재 로그인시에만 댓글적을수있도록

@require_POST
def create_comment(request, article_pk):
    if not request.user.is_authenticated:
        return redirect('accounts:login')
    #1. 댓글이 가리킬 게시글을 가져옵니다.
    article = Article.objects.get(pk=article_pk)
    #2. 사용자 입력값 (댓글)을 꺼내서
    form = CommentForm(request.POST)
        #3. 유효성 검사를 하고
    if form.is_valid():
        #4. 저장합니다.
        #commit=False 설정시 시점에 DB 저장 x
        # pk 값이 없는 comment를 반환
        comment = form.save(commit=False) # 이 시점에 DB 반영
        comment.article = article
        comment.save()
        #redirect('articles:detail', article.pk)
        #article 사용시 models에서 get absolute_url 사용해야합니다.
    return redirect(article)

 

 

 

 

Custom User

  1. 프로젝트 시작 전에 accounts 앱을 만든다.
  2. 커스텀 User 모델을 생성한다.
    1.  
  3. settings.py에 AUTH_USER_MODEL을 변경한다.
  4. 프로젝트 시작.

 

 

accounts models.py

from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.
class User(AbstractUser):
    pass

crud settings.py

맨 아래 추가해주기

AUTH_USER_MODEL = 'accounts.User'

makemigrations

migrate

 

게시글 작성자 넣기

 

게시글 유저

1 N > 좋아요,

N 1 > 댓글

 

 

articles models.py

 

from django.db import models
from django.conf import settings

# User 모델을 가져오는 방법
# 1. get_user_model() => models.py 제외
# 2. settings.AUTH_USER_MODEL => models.py에서만 사용


# Create your models here.
class Article(models.Model):
    title = models.CharField(max_length=10)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

    def get_absolute_url(self):
        return f'/articles/{self.pk}/'

class Comment(models.Model):
    content = models.TextField()    #NOT NULL
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
    )

python manage.py makemigrations
python manage.py migrate

 

홈페이지를 켜본다. (회원가입시 오류걸림 ㅋㅋ)

accounts forms.py

from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.contrib.auth import get_user_model

User = get_user_model() # 활성화 유저 모델

class CustomUserCreationForm(UserCreationForm):
    '''
    UserCreationForm을 커스텀마이징 하는 이유는
    UserCreationForm이 장고 내장 유저모델을 가르키기 떄문입니다
    '''
    class Meta(UserCreationForm.Meta):
        model = User
        fields= UserCreationForm.Meta.fields + ('email',)

class CustomUserChangeForm(UserChangeForm):
    password = None

    class Meta:
        model = User
        fields = ('email','first_name','last_name')

account views.py

signup 함수를 바꿔준다 커스텀유저크리에시션으로

CustomUserCreationForm 이친구 꼭 임포트 추가시켜주세용

def signup(request):
    if request.method == 'POST':
        form = CustomUserCreationForm(request.POST)
        if form.is_valid():
           user = form.save()
           auth_login(request, user) # 로그인 == 세션 생성
           # 회원가입 축하 메세지를 
           #request에 담아서 같이 보냅니다.
           messages.add_message(
               request, messages.SUCCESS, '회원가입을 축하합니다!'
               )
           return redirect('articles:index')
    else:
        form = CustomUserCreationForm()
    context = {
        'form':form,
    }
    return render(request, 'accounts/signup.html', context)

 

다음 이상이있으면

articles의 views.py 이상있는지 체크, forms.py 이상있는지체크

 

제출에 이상이있으면

url > views.py >

 

account views.py 바뀐것.

def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            # commit=False
            # 실제 DB에 저장은 하지 않지만
            # form에 담긴 인스턴스를 반환합니다.
            article = form.save(commit=False) #<<<
            article.author = request.user#<<<
            article.save() # DB 저장	#<<<< 진짜 저장
            return redirect(article) #<<< ㅎㅇ
    else:
        form = ArticleForm()
    context = {
        'form': form,
    }
    return render(request, 'articles/create.html', context)

articles index.html (글쓴이 추가)

{% extends 'base.html' %}

{% block content %}
  {% if messages %}
    {% for message in messages %}
      <div class="alert alert-{{ message.tags }}" role="alert">
        {{ message }}
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
      </div>
    {% endfor %}
  {% endif %}

  
  <h1>Articles</h1>
  <a href="{% url 'articles:create' %}">[CREATE]</a>
  <hr>
  {% for article in articles %}
    <p>글쓴이 : {{ article.author.username }}</p>
    <p>글 번호 : {{ article.pk }}</p>
    <p>글 제목 : {{ article.title }}</p>
    <p>글 내용 : {{ article.content }}</p>
    <a href="{% url 'articles:detail' article.pk %}">[DETAIL]</a>
    <hr>
  {% endfor %}
{% endblock %}

 

쓴사람만 수정 및 삭제가 가능하도록 하기

detail.html

{% extends 'base.html' %}

{% block content %}
  <h2>DETAIL</h2>
  <h3>{{ article.pk }} 번째 글</h3>
  <hr>
  <p>제목 : {{ article.title }}</p>
  <p>내용 : {{ article.content }}</p>
  <p>작성시각 : {{ article.created_at }}</p>
  <p>수정시각 : {{ article.updated_at }}</p>
  <hr>
  {% if article.author == request.user %}
  <a
   href="{% url 'articles:update' article.pk %}" 
   class="btn btn-primary"
  >
  [UPDATE]
  </a>
  <form action="{% url 'articles:delete' article.pk %}" method="POST">
    {% csrf_token %}
    <button class="btn btn-danger">DELETE</button>
  </form>
  <a href="{% url 'articles:index' %}">[back]</a>
  {% endif %}

  <hr>
  <form action="{% url 'articles:create_comment' article.pk %}" method="POST">
    {% csrf_token %}
    {{ form }}
    <button>댓글 작성</button>
  </form>
  <hr>
  {% for comment in comments %}
    <div>{{ comment.content }}</div>
  {% endfor %}
{% endblock %}

views.py

(삭제 방어)

@require_POST
def delete(request, pk):
    article = get_object_or_404(Article, pk=pk)
    if article.author == request.user:<<<< 이친구
        article.delete()
    return redirect('articles:index')

(업데이트 방어)

@require_http_methods(['GET','POST'])
def update(request, pk):
    article = get_object_or_404(Article, pk=pk)

    if article.author != request.user:
        return redirect('articles:index')
        
    if request.method == 'POST':
        form = ArticleForm(request.POST, instance=article)
        if form.is_valid():
            form.save()
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleForm(instance=article)
    context = {
        'form': form,
        'article': article,
    }
    return render(request, 'articles/update.html', context)
        

 

 

오류부분

image-20210324192539423

해결 방법 - views.py 에서 댓글을 저장을해줘야한다

def comment_create(request, pk):
    #댓글 저장
    article = get_object_or_404(Article, pk=pk)
    form = CommentForm(request.POST)
    if form.is_valid():
        comment = form.save(commit=False)#<<<<<<<<<<<<<<<<
        comment.article = article#<<<<<<<<<<<<<<<<
        #comment.article_id = pk#<<<<<<<<<<<<<<<<
        comment.save()
        return redirect('articles:detail', pk)
    
    context = {
        'form', form,
    }
    return render(request, 'articles/detail.html', context)

모든 댓글을 가져오면안된다. ( views) 댓글을 보여주게하기위한것

def detail(request, pk):
    article = get_object_or_404(Article, pk=pk)
    form = CommentForm()    # Comment 폼 생성

    comments = article.comment_set.all()#"article." 을 꼭 붙여줘라

    context = {
        'article': article,
        'form':form,
    }
    return render(request, 'articles/detail.html', context)

삭제하기

detail.html

 

{% extends 'base.html' %}

{% block content %}
  <h2>DETAIL</h2>
  <h3>{{ article.pk }} 번째 글</h3>
  <hr>
  <p>제목 : {{ article.title }}</p>
  <p>내용 : {{ article.content }}</p>
  <p>작성시각 : {{ article.created_at }}</p>
  <p>수정시각 : {{ article.updated_at }}</p>
  <hr>
  <a href="{% url 'articles:update' article.pk %}" class="btn btn-primary">[UPDATE]</a>
  <form action="{% url 'articles:delete' article.pk %}" method="POST">
    {% csrf_token %}
    <button class="btn btn-danger">DELETE</button>
  </form>
  <a href="{% url 'articles:index' %}">[back]</a>
 
  <hr>
  <ul>
    {% for comment in comments %}
      <li>{{ comment.content }}</li>
      <form action="#" method="POST">
        {% csrf_token %}
        <button>X</button>
      </form>
    {% endfor %}
  </ul>
  <form action="{% url 'articles:comment_create' article.pk %}" method="POST">
   {% csrf_token %}
    {{ comment_form.as_p }}
  <button>댓글작성</button>
  </form>
{% endblock %}

urls.py(restful 한 표현 방법)

from django.urls import path
from . import views


app_name = 'articles'
urlpatterns = [
    path('', views.index, name='index'),
    path('create/', views.create, name='create'),
    path('<int:pk>/', views.detail, name='detail'),
    path('<int:pk>/delete/', views.delete, name='delete'),
    path('<int:pk>/update/', views.update, name='update'),
    path('<int:pk>/comment/',views.comment_create, name='comment_create'),
    path('<int:pk>/articles/<int:comment_pk>/comment/delete/', views.comment_delete, name='comment_delete'),
]

views.py ( comment, delete 넣기)

from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_safe, require_http_methods, require_POST
from .models import Article, Comment #<<<<<<<<<<
from .forms import ArticleForm,CommentForm

# Create your views here.
@require_safe
def index(request):
    articles = Article.objects.order_by('-pk')
    context = {
        'articles': articles,
    }
    return render(request, 'articles/index.html', context)


@require_http_methods(['GET', 'POST'])
def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save()
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleForm()
    context = {
        'form': form,
    }
    return render(request, 'articles/create.html', context)


@require_safe
def detail(request, pk):
    article = get_object_or_404(Article, pk=pk)

    comment_form = CommentForm()
    context = {
        'article': article,
        'comment_form':comment_form,
        'comments':comments,
    }
    return render(request, 'articles/detail.html', context)


@require_POST
def delete(request, pk):
    article = get_object_or_404(Article, pk=pk)
    article.delete()
    return redirect('articles:index')


@require_http_methods(['GET', 'POST'])
def update(request, pk):
    article = get_object_or_404(Article, pk=pk)
    if request.method == 'POST':
        form = ArticleForm(request.POST, instance=article)
        if form.is_valid():
            form.save()
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleForm(instance=article)
    context = {
        'form': form,
        'article': article,
    }
    return render(request, 'articles/update.html', context)
        

def comment_create(request, pk):
    #댓글 저장
    article = get_object_or_404(Article, pk=pk)
    form = CommentForm(request.POST)
    if form.is_valid():
        comment = form.save(commit=False)
        comment.article = article
        #comment.article_id = pk
        comment.save()
        return redirect('articles:detail', pk)
    
    context = {
        'form', form,
    }
    return render(request, 'articles/detail.html', context)


@require_POST #<<<<<<<<<<<<<<<<<<<<<<<<
def comment_delete(request, pk, comment_pk):

    comment = get_object_or_404(Comment, pk=comment_pk)
    comment.delete()

    return redirect('articles:detail', pk)

 

 

유저관련

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.

class User(AbstractUser):
    pass

image-20210324195400610

이 에러가 나타나는 의미는 기존 user가 있는데 왜 또 user를 만드냐?

그럼 뒤도 안돌아보고 settings.py 에서 맨밑에

AUTH_USER_MODEL = 'accounts.User'

추가시켜준다. 그리고 마이그레이션 오류만 뜰것이다.

makemigrtions ㄱ

그 후 프로젝트 중간이니까 데이터베이스를 삭제해줘야한다.(서버꺼놓구해..)

migrate ㄱ

그럼 유저 커스텀 끘

 

 

누가적은지 확인하기위해서 아티크 모델에서

+from django.conf import settings

 

유저객체 = settings.AUTH_USER_MODEL

user = models.ForeignKey( 유저객체, on_delete = models.CASCADE)

위에는 models.py 에서만!!!!!

 

 

사용하고 그 외에는get_user_model() 을 사용한다

728x90

'SSAFY > Django' 카테고리의 다른 글

[Django] 댓글 삭제  (0) 2021.03.26
[Django] SQL 과 장고 ORM 사용 방법  (0) 2021.03.26
[Django] 회원가입 만들기  (0) 2021.03.22
[Django]Form  (0) 2021.03.21
[DJANGO] STATIC FILES  (0) 2021.03.19