해리의 데브로그

Django 09 - 1:N 관계 설정 / 댓글 기능 구현

|

1:N 관계 설정 (Database relation)

앞서, 게시글을 작성하는 코드(CRUD)를 작성해보았음. 더 나아가, 각 게시글에 댓글을 쓰는 코드를 작성해 보도록 하자. 이때 한 게시글에는 여러개의 댓글이 작성 될 수 있으므로, 게시글과 댓글은 1:N 구조를 띔.

models.py 내 comment 클래스 정의

  • postmodels.ForeignKey() 객체가 저장되어 있는 변수임.하기 예시의 경우, 댓글(Comment)이 새로 생성될때, 어떠한 게시글(Post)인지를 나타내는 대한 변수 명임.
  • models.ForeignKey() 는 외래키를 설정하는 함수임.
    • 첫번째 인자: 외래키가 연결되는 테이블을 입력함. 여기서는 Post 모델 클래스를 의미함
    • 두번째 인자: ForeignKeyField가 바라보는 값이 삭제 될 때, 어떻게 처리할건지를 옵션으로 정함
      • CASCADE : 부모가 삭제 되면, 자기 자신도 삭제 예) 게시글에 대한 모델 클래스인 Post 에 따라 생성된 게시글이 삭제되면, 댓글에 대한 모델 클래스인 Comment에 따라 생성된 댓글도 삭제)
      • PROTECT : 자식이 존재하면, 부모 삭제 불가능 ( ProtectedError 발생시킴)
      • SET_NULL : 부모가 삭제되면, 자식의 부모 정보를 NULL로 변경
class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    content = models.TextField()
  • 클래스 작성 후 마이그레이션 재실행
    • python manage.py makemigrations
    • python manage.py migrate

Python Shell 을 통해 데이터 입력

  • python manage.py shell : 파이썬 쉘 실행
  • from posts.models import Post, Comment : models.py 내 Post 및 Comment 클래스 import
  • post = Post.objects.last() : 데이터 베이스에 마지막으로 저장된 데이터를 post라는 변수에 저장.
>>> post = Post.objects.last()
>>> post
<Post: 제목입니다>
  • 댓글에 대한 모델 클래스인 Comment 를 호출하여, c라는 클래스의 인스턴스를 생성한다. 이때, post에는 위에서 정의한 인스턴스post를 값으로 저장시킨다. 이를 통해, 해당 댓글이 어느 글에 대한 것인지를 알 수 있게 됨. content에는 내용을 작성
    • 동일하게 Comment 모델 클래스를 통해 DB에 저장된 모든 레코드를 불러올 수 있음.
    • 또한, c.post & c.content와 같이 Comment 클래스의 멤버변수를 호출 할 수도 있음.
>>> c = Comment(post=post, content='댓글입니다!')
>>> c.save()
>>> Comment.objects.all()
<QuerySet [<Comment: Comment object (1)>]>

>>> c.post
<Post: 제목입니다>
>>> c.content
'댓글입니다'
  • post 변수(Post 모델 클래스의 인스턴스)에 저장된 모든 댓글(관계로 연결된 Comment 클래스의 객체들)을 모두 가져올 수도 있음
  • 이때 SQL 문법에 따라 모델 클래스 Comment는 소문자로 입력해줘야함.
>>> post.comment_set.all()
<QuerySet [<Comment: Comment object (1)>, <Comment: Comment object (12)>]>
  • post.comment_set.first() 는 post 변수 내Comment 모델 클래스를 통해 DB에 저장된 첫번째 레코드를 가져온다. 이 레코드는 객체를 갖고 있으며, 객체가 들고 있는 멤버 변수 content 를 반환할 수 있음.
>>> post.comment_set.first()
<Comment: Comment object (1)>
>>> post.comment_set.first().content
'댓글입니다!'
  • c.post 를 하면, c (Comment 모델 클래스의 인스턴스)의 멤버변수인 post 에 저장된 값을 반환하는데, 그 값은 Post 모델 클래스의 객체이다. 예) Post.objects.last()
  • 따라서, post 객체가 들고있는 변수도 당연히 반환 할 수 있으므로 아래의 방법으로 접근이 가능함.
>>> c = Comment.objects.get(pk=1)
>>> c
<Comment: Comment object (1)>
>>> c.post
<Post: 제목입니다>
>>> post.title
'제목입니다'
>>> post.content
'내용입니다'
>>> c.post.title
'제목입니다'
>>> c.post.content
'내용입니다'

Django 내 댓글 생성 기능 구현

#views.py

#모델 클래스 Comment를 import해옴
from .models import Post, Comment

def comments_create(request, post_id):
    #댓글을 달 게시물에 대한 정보 가져오기
    post = Post.objects.get(pk=post_id)
    #form 태그에서 넘어온 댓글 내용 가져오기
    content = request.POST.get('content')
 
    #댓글 생성 및 저장 
    comment = Comment(post=post, comment=comment)
    comment.save()
    
    #댓글 생성후, 디테일 페이지로 redirect시킴
    return redirect('posts:detail', post.pk)


#urls.py
path('<int:post_id>/comments/create/', views.comments_create, name='comments_create'),
  • detail.html
    • 댓글 생성을 위한 추가적인 html 페이지는 생성할 필요 없으며 디테일 페이지 내에서 작성 가능
    • form & input 태그등을 사용하여, comment_create 함수를 호출하기 위한 url 주소를 지정
    • 저장된 댓글(전체 댓글) html에서 보여주기 위해, 진자 템플릿을 이용하여 반복문을 실시
    • 이때, 댓글에 대한 템플릿 변수는 추가로 설정할 필요는 없으며, post를 통해 댓글에 접근이 가능함.
      • post 와 연결된Comment 모델 클래스의 객체을 불러오는 post.comment_set.all 을 사용
      • html문서 내에서는 all() 을 쓰지 않음을 유의하자.
	<form action="{% url 'posts:comments_create' post.pk %}" method="post">
        {% csrf_token %}
        댓글 : <input type="text" name="content"/>
        	  <input type="submit" value="Submit"/>
    	</form>

    <ul>
        {% for comment in post.comment_set.all %}
        <li>  </li>
        {% endfor %}
    </ul>

Django 내 댓글 삭제 기능 구현

  • 게시글의 정보와 댓글의 정보가 둘다 필요하므로, variable routing으로 post_idcomment_id 를 둘다 넘겨 줘야함.
#views.py
def comments_delete(request, post_id, comment_id):
    comment = Comment.objects.get(pk=comment_id)
    comment.delete()
    
    return redirect('posts:detail', post_id)

#urls.py
path('<int:post_id>/comments/<int:comment_id>/delete/', views.comments_delete, name='comments_delete'),
  • form 태그와 input 태그등을 적절히 사용하여, 댓글을 삭제하는 내용이 정의된 comment_delete 함수를 호출하기 위한 url 주소를 지정한다.
<-- detail.html -->    
<ul>
  {% for comment in post.comment_set.all %}
   <li>  - 
    <a href="{% url 'posts:comments_delete' post.pk comment.pk %}">Delete</a>
    </li>
   {% endfor %}
</ul>

Django 08 - Template 에서 하드코딩된 URL 을 제거하기

|

Template 에서 하드코딩된 URL 을 제거하기

지금 까지 우리는 url.py - urlpatterns에 각 url 주소가 요청되면, 그에 따른 함수가 실행되게 설정을 하였으며, return되는 render 혹은 redirect 함수의 인자로 url 주소를 일일이 하드코딩하였음.

<li><a href="/posts//"></a></li>    

위와 같이 강력하게 결합되고 하드코딩된 접근방식의 문제점은 하드코딩을 인한 에러 발생율이 높으며(예, / 미입력), 수 많은 템플릿을 가진 프로젝트들의 URL을 바꾸는게 어렵다는 것임.

그러나 urls.py 내 path() 함수에서 인수의 이름을 정의하면 템플릿을 포함한 Django 어디에서나 명확하게 url을 참조 할 수 있음. 이 강력한 기능을 이용하여, 단 하나의 파일만 수정해도 project 내의 모든 URL 패턴을 바꿀 수 있으며, 직관적으로 코드를 읽을 수 있음.

  • urls.py
    • path 함수의 두번째 인자로 name 속성 적용.
    • name 은 특정한 URL 매핑을 위한 고유 식별 ID의 개념임. 우리는 이 이름을 매퍼에 반전시킬 수 있음.
    • 즉, 매퍼가 처리하도록 설계된 리소스를 향하는 URL을 동적으로 생성하기 위해 설정
app_name = 'posts' 

urlpatterns = [
    path('', views.index, name='list'),
    path('new/', views.new, name='new'),    
    path('create/', views.create, name='create'),
    path('<int:post_id>/', views.detail, name='detail'),
    path('<int:post_id>/delete/', views.delete, name='delete'),
    path('<int:post_id>/edit/', views.edit, name='edit'),
    path('<int:post_id>/update/', views.update, name='update'),
]

네임 스페이스 설정

  • 위의 예시에서 우리는 url.py의 주인이 posts 라는 어플리케이션 이라는 것을 설정 해줘야함.
  • url의 name 값을 사용하다 보면, 한 프로젝트 내에 많은 어플리케이션을 생성할 경우, 이름이 중복되는 문제가 발생할 수 있음. 따라서 중복을 방지 하기 위해 app_name 이라는 url 네임 스페이스를 사용 해야 함.
#네임 스페이스 예시
app_name = 'posts'

views.py 내 url 매핑 설정

  • delete 함수 - posts:list
    • posts : urls.py 에서 설정한 네임 스페이스를 의미.
    • list : urls.py에서 설정한 list 페이지로 연결하는 urlpattern에 대한 name 값. path('', views.index, name='list')
def delete(request, post_id):
    post = Post.objects.get(pk=post_id)
    post.delete()
    
	#변경 전
	return redirect('/posts/')
	#변경 후
    return redirect('posts:list')
  • create 함수
    • create 함수의 경우, 디테일 페이지로 redirect 지정해놓았으며, 디테일 페이지는 id값을 variable routing으로 설정이 되어있었음. 따라서, 이 작업을 추가로 적용해야함.
    • variable routing , 동적으로 설정되는 변수를 두번째 파라미터로 적용하면 됨.
    • 만약 변수가 여러개 일 경우, 콤마로 구분하여 그 다음 파라미터로 삽입.
def create(request):
    title = request.POST.get('title')
    content = request.POST.get('content')
    #DB Insert
    post = Post(title=title, content=content)
    post.save()
    
    #변경 전
    return redirect(f'/posts/{post.pk}')
    
    #변경 후
    return redirect('posts:detail', post.pk)

html 문서 내 url 매핑 설정

index.html 문서와 같이, a태그에 연결되는 url주소를 입력한 경우도 있음. url 매핑을 설정하는 기본적인 구조는 다음과 같음.

{% url '[app_name]:[설정된 name 값]' [동적 변수 ] %}

html문서 내에서는 동적으로 들어가는 변수에 대해서는 콤마가 아니라 띄워쓰기로 구분을 해줘야함.

  • list.html ```html <– index.html –>

Post Index

New - 새로운 글쓰기
    {% for post in posts %}
  • {% endfor %}

- 변경 전
```html
<a href="/posts/new/">New - 새로운 글쓰기</a>
<li><a href="/posts//"></a></li>    
  • {{ post.title }}
  • 
    
    
    - new.html
    
    ```html
    <form action="/posts/create/", method="post">
        {% csrf_token %}
        <input type="text" name="title"/>
        <input type="text" name="content"/>
        <input type="submit" value="Submit"/>
    </form>
    
    <form action="{% url 'posts:create' %}", method="post">
    
    • detail.html
    <h1>Post Detail</h1>
    <h2>Title : </h2>
    <p> Content : </p>
    <a href="/posts/">List</a>
    <a href="/posts//edit/">Edit</a>
    <a href="/posts//delete/">Delete</a>
    
    <a href="{% url 'posts:list' %}">List</a>
    <a href="{% url 'posts:edit' post.pk %}">Edit</a>
    <a href="{% url 'posts:delete' post.pk %}">Delete</a>
    
    • edit.html
    <form action="/posts//update/", method="post">
        {% csrf_token %}
        <input type="text" name="title" value=""/>
        <input type="text" name="content" value=""/>
        <input type="submit" value="Submit"/>
    </form>
    
    <form action="{% url 'posts:update' post.pk %}", method="post">
    

    Django 07 - CRUD 4 (Delete, Update)

    |

    Model을 활용한 CRUD 구현 (4)

    Delete (Delete 페이지 구현)

    • views.py & urls.py
      • detail 함수를 작성했던 방식과 매우 유사함.
      • urls 주소의 post_id 를 variable routing으로 사용하여, 함수 내 특정 레코드를 갖고오는 id 값으로 사용.
      • post.delete() 이용하여 레코드를 삭제 한 후, list 페이지인 ‘/posts/’ 로 redirect 시킴.
    #views.py
    def delete(request, post_id):
        post = Post.objects.get(pk=post_id)
        post.delete()
        return redirect('/posts/')
    
    #urls.py
    urlpatterns = [
        path('<int:post_id>/delete/', views.delete),
    ]
    
    • detail.html
      • 게시글을 삭제하는데는 추가적인 웹페이지를 작성할 필요가 없음.
      • 따라서, 레코드의 세부정보를 보여주는 details.html에 내용을 구현할 수 있음.
      • <a href="/posts//delete/">Delete</a>
      • id값을 불러오기 위해 템플릿 변수를 사용하여 삭제 기능을 동적으로 구현.
    <-- details. html-->
    <body>
        <h1>Post Detail</h1>
        <h2>Title : </h2>
        <p> Content : </p>
        <a href="/posts/">List</a>
        <a href="/posts//delete/">Delete</a>
    </body>    
    

    Update(Edit & Update 페이지 구현)

    게시판 글을 수정하기 위해서는 어떠한 레코드를 수정할건지에 대한 정보가 필요하므로 다시 id값을 받아야함. new & create 페이지에서 우리는 아래와 같이 페이지의 역할을 구분했었음.

    • new.html: 내용 입력 Form을 작성. 작성된 내용을 /posts/create/로 전달하는요청을 보냄
    • create 함수: /posts/create/ 로 url이 접근되면, 함수가 실행되어, 데이터베이스에 내용을 저장시킴.

    마찬가지로, 글을 수정하는 페이지도 역할을 아래와 같이 2가지로 구분하여 작성해야함.

    • edit.html : 내용을 수정하는 Form을 작성하는 페이지.수정된 내용을 posts/[id값(템플릿변수)]/update으로 전달하는 요청을 보냄.
    • update 함수 : posts/[id값(템플릿변수)]/update url을 사용자가 입력하면 update 함수가 실행됨.

    Edit 페이지 구현

    • views.py & urls.py
      • 기본주소/[id값]/edit/ url으로 서버에 요청을 보내면, urlpattern에 따라 edit 함수가 실행됨.
      • post_id 는 variable routing으로 `edit 함수의 두번째 파라미터로 받아옴.
      • 이 값을 가지고 DB의 특정 레코드에 접근.
      • 레코드 값이 저장된 post 변수를 html 파일에서 사용하기 위해 템플릿 변수로 지정.
    #views.py
    def edit(request, post_id):
        post = Post.objects.get(pk=post_id)
        return render(request, 'edit.html', {'post':post})
    
    #urls.py
    urlpatterns = [
        path('<int:post_id>/edit/', views.edit),
    ]
    
    • edit.html
      • input 태그의 내용에는 변경사항을 실어서 update 주소로 보냄.
      • 기존에 입력된 내용을 보기 위해, 템플릿 변수로 연결된 post라는 인스턴스의 멤버변수인 post.titlepost.content 를 value 속성의 값으로 저장.
      • 이를 통해 edit.html이 실행될 때, input 태그의 value에 기존의 데이터를 볼 수 있게됨.
      • 어떠한 레코드를 수정하는지에 대한 정보가 들어가야하므로, post.pk 변수는 variable routing으로 사용하여, update주소로 요청을 보냄.
      • 요청방식은 POST가 사용되었기 때문에, 추가로 csrf_token 을 입력.
    
    <body>
        <h1>Post Edit</h1>
        <form action="/posts/{{ post.pk }}/update/", method="POST">
            {% csrf_token %}
            <input type="text" name="title" value="{{ post.title }}"/>
            <input type="text" name="content" value="{{ post.content }}"/>
            <input type="submit" value="Submit"/>
        </form>
    </body>
    
    

    update 페이지 구현

    • views.py & urls.py
      • variable routingpost_id를 통해 우리는 수정하고자 하는 레코드를 반환.
      • Post 클래스의 인스턴스 객체인 post 의 멤버변수를 edit.html - form 태그에 작성된 내용으로 업데이트 시킴.
      • 이후 .save() 을 통해 DB에 내용을 저장시킴.
    #views.py
    def update(request, post_id):
        #수정하는 코드
        post = Post.objects.get(pk=post_id)
        post.title =request.POST.get('title')
        post.content =request.POST.get('content')
        post.save()
        return redirect(f'/posts/{post_id}/')
    
    #urls.py
    urlpatterns = [
        path('<int:post_id>/update/', views.update),
    ]
    

    게시글을 업데이트 하고싶을 때 마다 /posts//edit/ url주소를 직접 입력하는 방법은 매우 비효율적일 것이다. 따라서, 레코드의 세부정보를 보여주는 detail.html에 게시글을 업데이트하는 내용을 구현하도록 하자.

    • detail.html
      • delete 때와 마찬가지로, a 태그와 id값을 템플릿 변수로 적절히 활용하여 edit 함수를 실행시키는 링크를 작성.
    <body>
        <h1>Post Detail</h1>
        <h2>Title : </h2>
        <p> Content : </p>
        <a href="/posts/">List</a>
        <a href="/posts//edit/">Edit</a>
        <a href="/posts//delete/">Delete</a>
    </body>    
    

    Django 06 - CRUD 3 (Read)

    |

    Model을 활용한 CRUD 구현 (3)

    Read (3) - 디테일 & 리스트 페이지 연결하기

    #views.py
    def index(request):
        posts = Post.objects.all()
        return render(request, 'index.html', {'posts':posts})
    
    • views.py - index 함수의 코드를 살펴보면, 모델 클래스인 Post 으로 작성된 레코드 전체를 갖고 오는 Post.objects.all() 메서드를 사용하여 posts 변수에 저장한 것을 알 수 있음.
    • posts 를 파이썬 쉘에서 불러오면, 다음과 같은 예시 결과가 뜸. 여기서 우리는 posts 라는 변수에는 Post 의 인스턴스들이 리스트에 저장 되어 있다는 것을 알 수 있음. 이를 활용하여, 반복문을 통해 posts 내 각각의 인스턴스 객체를 불러오는 작업을 index.html에서 할 수 있다.
    >>> posts = Post.objects.all()
    >>> posts
    <QuerySet [<Post: 2번째로변경>, <Post: 추가번째>, <Post: 몇번째?>, <Post:  나는  수업시간만되면>, <Post: 민재야>, <Post: [개념글]>, <Post: [뻘글]>, <Post: [정보]>, <Post: [to harry]>, <Post: 66666>, <Post: [to harry]>, <Post: 1번째>, <Post: 배가왜이리>, <Post: 123123>]>
    
    <-- index.html -->
    <body>
        <a href="/posts/new/">New - 새로운 글쓰기</a>
        <ul>
        {% for post in posts %}
            <li><a href="/posts//"></a></li>    
        {% endfor %}
        </ul>
    </body>
    
    • index.html 내 진자 템플릿을 이용하여 반복문을 사용하였으며, 리스트 내 인스턴스 객체를 불러오는 변수명을 post로 정의하였음. 반복문이 돌면서 리스트 내 객체가 하나씩 호출되는 과정은 아래와 같음.

      1번째 반복: post = <Post: 2번째로변경> => 기본주소/students/1 2번째 반복: post = <Post: 추가번째> => 기본주소/student/2 3번째 반복: post = <Post: 몇번째?> => 기본주소/student/3 …

    • 각 객체는 post라는 Post 클래스의 인스턴스에 저장되어, post.pkpost.title 와 같이 클래스의 멤버변수를 호출하여 저장된 값을 불러올 수 있다.

    • 이를 활용하여, post.pk 를 a태그 내 링크 주소로 입력하면 variable routing 으로 반복문이 돌면서 각각의 인스턴스는 detail 함수(세부정보가 담긴 페이지를 호출하는 함수)를 불러오는 url patterns와 연결이 된다.

    #urls.py
    urlpatterns = [
        path('<int:post_id>/', views.detail),
    ]
    

    Read (4) - new & create 페이지 연결

    앞서 우리가 작성한 코드에서, new.html에 내용을 작성하면, create.html으로 연결되어 해당 웹페이지에 작성된 내용인 “성공적으로 Post되었습니다” 라는 화면을 사용자가 볼 수 있었음. 이부분을 수정하여, new.html에서 내용을 작성하면, 작성한 내용을 보는 페이지로 넘어가게 해보자.

    이에 앞서, 클라이언트(웹 브라우저)에서 서버를 요청(request)을 보내는 방식을 Get에서 Post로 변경을 해보자.

    • Get: 가져오는것 (html 문서를 보여달라!)
    • Post: 수행하는 것

    데이터베이스에 내용을 저장하는 행위는 단지, 데이터를 가져오는 역할을 하는 GET 방식보다는 POST 방식과 더 적합하다. 또한, POST 방식으로 데이터를 실어서 요청을 보낼 경우, 주소창에 데이터가 표시되지 않으므로 보안적인 측면에도 더 낫음.

    <--! new.html -->
    <form action="/posts/create/", method="post">
    {% csrf_token %}
    

    CSRF: 사이트 간 요청 위조(Cross-site Request Forgery)

    웹 애플리케이션 취약점 중 하나로 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 하여 특정 웹페이지를 보안에 취약하게 한다거나 수정, 삭제 등의 작업을 하게 만드는 공격 방법

    • CSRF 토큰이란 CSRF 공격에 대응을 하기 위한 방어 기법 중 하나임. 보통 CSRF공격은 특정액션시 넘어가는 파라미터를 가지고 그 행위를 특정액션 이외에 자동으로 넘어가게 하는 기법인데 이것을 넘어가는 값 중에 랜덤으로 발행되는 키값을 넘기고 받게 해서 이 값이 일치하지 않으면 그 액션을 수행하지 않는 것이다.
    • CSRF token은 보안상의 목적으로 사용. token을 넣어줘야 token과 함께 create 페이지에 요청을 보냄. 장고에서 csrf token을 가지고, 피하식별 하듯이, 우리 사이트에서 보낸 요청이라는 것을 확실히 알 수 있게 됨.
    • Post 메소드를 쓸 때는 반드시 csrf_token이 있어야함.
    from django.shortcuts import render, redirect
    
    def create(request):
        title = request.POST.get('title')
        content = request.POST.get('content')
        
        post = Post(title=title, content=content)
        post.save()
        return redirect(f'/posts/{post.pk}')
    
    • views.py에서 메소드 방식을 GET => POST로 변경.
    • POST는 html 문서를 돌려주지 않음(반환 X). 그 대신 특정 페이지로 redirect 하게 지정 할 수 있음. redirect는 외장 함수 이므로 import 해줘야함.

    최종 summary

    1) new.html에서 사용자가 내용을 입력하면, form 태그에서 정의된 actionmethod 속성에 따라, 기본주소/posts/create/ url 주소를 서버에 요청함.
    2) create 함수에서 정의된 내용에 따라, 입력된 내용이 각각 title & content 변수에 저장 3) 데이터베이스에 내용을 저장하기 위해 Model 클래스 인 Post 를 호출하여, 각각의 내용을 테이블의 구조에 맞게 저장한다 post = Post(title=title, content=content) (이때, post.save() 를 반드시 넣어야 데이터베이스에 내용이 저장됨을 잊지 말자) 4) 이후, redirect 함수에따라, /posts/{post.pk} 주소로 이동된다. (views.pydetail 함수 실행)

    Django 05 - CRUD 2 (Create & Read)

    |

    Model을 활용한 CRUD 구현 (2)

    Create (Create 페이지 구현)

    #views.py
    from django.shortcuts import render
    from .models import Post
    
    def new(request):
        return render(request, 'new.html')
    
    
    def create(request):
        title = request.GET.get('title')
        content = request.GET.get('content')
        
        post = Post(title=title, content=content)
        post.save()
        
        return render(request, 'create.html')
    
    
    #urls.py
    from posts import views
    
    urlpatterns = [
        path('create/', views.create),
        path('new/', views.new),
    ]
    
    <-- new.html -->
    <body>
        <form action="/posts/create/", method="get">
            <input type="text" name="title"/>
            <input type="text" name="content"/>
            <input type="submit" value="Submit"/>
        </form>
    
    </body>
        
    <-- create.html -->
    <body>
       <h1> 성공적으로 Post를 생성 했습니다! </h1>
    </body>
    
    • views.py 에서 render 함수로 new.html 을 호출함.
    • input 태그를 통해 들어오는 값은 총 2가지로, 각각 titlecontent라는 name의 속성으로 정의 되어 있다. 여기서 Dict의 구조를 띔. {title: 사용자 입력 값, content: 사용자 입력값}

    • views.pycreate 함수에서 Dict에서 저장된 value값을 가져와 각각 titlecontent 라는 변수에 저장.
    title = request.GET.get('title')
    content = request.GET.get('content')
    
    • 데이터베이스에 내용을 저장하는 코드를 작성함. 아래의 경우, Post 클래스를 사용하기 위해 models.py에 작성된 Post 클래스를 import를 해와야함.
    from .models import Post
    
    post = Post(title=title, content=content)
    post.save()
    
    • 최종적으로, new.html 에서 사용자가 내용을 입력하면, form 태그 action 속성으로 연결된 create.html 으로 이동되어, views.py 에 정의된 create 함수가 실행된다.
    • 해당 함수에 정의된 변수명에 따라, 데이터베이스에 내용이 저장되며, 사용자는 create.html 에 따라, “성공적으로 Post되었습니다” 라는 결과를 화면을 보게 된다.

    URL 구조 변경

    지금까지 우리는 프로젝트 폴더 내에 존재하는 urls.py (어플리케이션 폴더의 상위디렉토리)에 모든 url을 저장해왔다. 그런데 만약, 한 프로젝트 내에 무수히 많은 어플리케이션을 생성하고, 관련 url을 urls.py에 작성한다면, traceability 적인 측면 뿐만 아니라 직관적으로 각 어플리케이션에 해당하는 url을 구분하기 힘들 것이다.

    따라서, urls.py를 각 어플리케이션 내에 새로 생성하여, 손쉽게 urls.py를 관리 할 수 있도록 해보자.

    • 어플리케이션 내에 urls.py 작성
    from django.urls import path
    from . import views
    #현재 디렉토리 내에 views.py를 불러오므로 from 에는 현재 디렉토리를 의미하는 . 을 써줌.
    
    urlpatterns = [
        path('create/', views.create),
        path('new/', views.new),
    ]
    #일반적인 작성방법대로, urlpatterns을 작성한다. 
    
    • 상위 디렉토리(프로젝트 폴더) 내 urls.py 수정
    from django.urls import path, include
    
    urlpatterns = [
        path('posts/', include('posts.urls')),
        path('admin/', admin.site.urls),
    ]
    

    path의 첫 번째 인수로, 'posts/' 값을 줌에 따라, posts(어플리케이션)에 적용되는 모든 url을 직관적으로 이해할 수있음. 또한 두번째 인수로, include('posts.urls') 을 주어, posts(어플리케이션) 내 urls.py와 연결을 시켰음. 이 때, 외장 함수인 include를 import 하였음.

    Read (1) - 리스트 페이지 구현

    • views.py
      • 모델 Post 내 DB에 저장된 모든 값을 불러오는 objects.all() 을 입력한 후, posts 변수에 저장
      • 해당 변수를 템플릿 변수로 index.html으로 함께 넘김
    #views.py
    def index(request):
        #데이터 베이스에 저장된 모든 값을 불러옴.
        posts = Post.objects.all()
        return render(request, 'index.html', {'posts':posts})
    
    #urls.py
    urlpatterns = [
        path('', views.index),
    ]
    
    • index.html
      • 템플릿 변수로 넘어간 posts 를 진자템플릿을 사옹하여 반복문을 돌림
      • 반복문 안에서, 객체의 멤버변수인 .title.content 를 진자템플릿을 불러옴.
    <body>
        <h1>Post Index</h1>
        <ul>
        <-- 반복문을 돌리기 위해 jinja 템플릿 사용 -->
        
        </ul>
    </body>
    

    Read (2) - 디테일 페이지 구현

    우리가 작성한 index.html 은 데이터베이스에 입력한 레코드 전체에 대한 정보를 보여줬음. 이제, 게시판 안의 글의 내용을 보는 것과 같이, 클릭하여 들어가서 세부 내용을 보여주는 페이지를 생성해보자.

    레코드에 대한 세부 정보를 보여주기 위해서는, 각 레코드가 갖고 있는 고유값(id; pk)로 접근을 해야 함. 이를 위해 id값을 파라미터로 갖고오는 Variable routing을 설정 해야 함.

    예) 기본주소/posts/1

    • views.py & urls.py
      • detail 함수의 두번째 파라미터인 post_id 는 레코드의 고유키를 의미하는 variable routing임. 이는 urls.pypath 함수의 첫번째 인자와 반드시 일치 시켜줘야함.
      • post = Post.objects.get(pk=post_id) : Post라는 모델 클래스를 통해 저장된 1번(pk=1) 레코드를 post 라는 변수로 저장함. 이때 postPost 클래스의 인스턴스라고 볼 수 있음.
      • ‘post’ 를 템플릿 변수로 지정하였으며, 사용자의 편의상, Dict의 key값와 value값을 post로 일치시켰음. 따라서, detail.html 에서 템플릿 변수 post 를 입력하면, Post의 인스턴스이자, 템플릿 변수의 key값인 post가 호출 됨.
    #views.py
    def detail(request, post_id):
        post = Post.objects.get(pk=post_id)
        return render(request, 'detail.html', {'post':post})
    
    #urls.py
    urlpatterns = [
        path('<int:post_id>/', views.detail),
    ]
    
    • detail.html
      • 위의 설명에 이어, Post의 인스턴스인 post 는 클래스에서 정의된 멤버 변수(예, post.title / post.content )를 불러올 수 있음.
      • 추가로, 레코드 전체에 대한 정보가 담겨 있는 기본주소/posts/ 또는 (index.html) 으로 돌아가기 버튼을 만들기 위해, a 태그를 사용 하였음.
    <body>
        <h1>Post Detail</h1>
        <h2>Title : </h2>
        <p> Content : </p>
        <a href="/posts/">List</a>
     
    </body>