본문 바로가기

PYTHON-BACK

#파이썬 34일차_이커머스 클론코딩4_(1)

728x90
python manage.py shell

from posts.forms import CommentForm

data = {"content": "SampleContent"}
form = CommentForm(data=data)
form.is_valid()
form.errors

from users.models import User
from posts.models import Post

user = User.objects.all()[0]
post = Post.objects.all()[0]
data = {"content": "SampleContent", "user": user, "post": post}
form = CommentForm(data=data)
form.is_valid()

comment = form.save()
comment.id
  • Comment를 생성하기 위해 필요한 데이터
    • 어떤 글(Post)의 댓글인지
    • 어떤 사용자(User)의 댓글인지

어떤 내용(Comment)을 가지고 있는지


2.1.2 View에서 Template으로 Form 전달

  • posts/views.py
from posts.forms import CommentForm

def feeds(request):
    ...

    posts = Post.objects.all()
    comment_form = CommentForm()
    context = {
        "posts": posts,
        "comment_form": comment_form,
    }
    return render(request, "posts/feeds.html", context)
  • templates/posts/feeds.html
    • 직접 작성했던 input 요소를 삭제하고 comment_form.as_p 변수 사용
<div class="post-comments-create">
    <form method="POST">
        {% csrf_token %}
        {{ comment_form.as_p }}
        <button type="submit">게시</button>
    </form>
</div>
  • templates/posts/feeds.html
    • CommentForm에는 post, content 필드가 있고 이 둘을 as_p로 렌더링한 경우
      • 포스트의 드롭다운 요소를 클릭하면 Post 객체를 선택할 수 있음. 사용자가 어떤 글에 댓글을 다는지는 직접 입력할 필요 없이 템플릿에서 알아서 처리해 주어야 함
      • 자동으로 < label>요소와 < input>요소가 만들어짐. 여기서는 "내용:"으로 나타나는 < label>요소가 필요하지 않음. content 값을 입력받을 < input> 요소만 있으면 됨
<div class="post-comments-create">
    <form method="POST">
        {% csrf_token %}
        {{ comment_form.content }}
        <button type="submit">게시</button>
    </form>
</div>
  • posts/forms.py
class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = [ ... ]
        widgets = {
            "content": forms.Textarea(
                attrs={
                    "placeholder": "댓글 달기...",
                }
            )
        }

2.1.3 댓글 작성 처리를 위한 View 구현

  • posts/views.py
from django.views.decorators.http import require_POST

def feeds(request):
    ...

@require_POST
def comment_add(request):
    print(request.POST)
  • posts/urls.py
from django.urls import path
from posts.views import feeds, comment_add
...

urlpatterns = [
    path("feeds/", feeds),
    path("comment_add/", comment_add),
]

2.1.4 form에서 comment_add View로 데이터 전달 및 처리

 
  • templates/posts/feeds.html
    • form의 action 속성
      • method: GET과 POST 중 어떤 방식으로 데이터를 전달할지
      • enctype: 기본값(application/x-www-form-urlencoded)과 파일 전송을 위한 값(multipart/form-data) 중 선택
<div class="post-comments-create">
    <form method="POST" action="/posts/comment_add/">
        {% csrf_token %}
        {{ comment_form.content }}
        <button type="submit">게시</button>
    </form>
</div>
 
 
  • templates/posts/feeds.html
    • 사용자가 직접 입력하지 않는 고정된 데이터를 form 내부에 위치
<div class="post-comments-create">
    <form method="POST" action="/posts/comment_add/">
        {% csrf_token %}
        <input type="hidden" name="post" value="{{ post.id }}">
        {{ comment_form.content }}
        <button type="submit">게시</button>
    </form>
</div>
  • posts/views.py
    • 사용자 정보를 View에서 직접 할당
@require_POST
def comment_add(request):
    form = CommentForm(data=request.POST)
    if form.is_valid():
        comment = form.save(commit=False)
        comment.user = request.user
        comment.save()

        print(comment.id)
        print(comment.content)
        print(comment.user)

        return redirect{"/posts/feeds/"}
  • templates/posts/feeds.html
    • 작성 완료 후 원하는 Post 위치로 이동
<div id="feeds" class="post-container">
    {% for post in posts %}
        <article id="post-{{ post.id }}" class="post">
  • posts/views.py
from django.http import HttpResponseRedirect

@require_POST
def comment_add(request):
    form = CommentForm(data=request.POST)
    if form.is_valid():
        comment = form.save(commit=False)
        ...

        return HttpResponseRedirect{f"/posts/feeds/#post-{comment.post.id}"}

2.1.5 글의 댓글 수 표시

  • Terminal
python manage.py shell

from posts.models import Post

for post in Post.objects.all():
    print(f"id: {post.id}, comment_count: {post.comment_set.count()}")
  • templates/posts/feeds.html
<div class="post-buttons">
    <button type="submit">Likes(0)</button>
    <span>Comments({{ post.comment_set.count }})</span>
</div>

2.1.6 댓글 삭제

  • posts/views.py
from posts.models import Post, Comment

@require_POST
def comment_add(request):
    ...

@require_POST
def comment_delete(request, comment_id):
    if request.method == "POST":
        comment = Comment.objects.get(id=comment_id)
        comment.delete()
        return HttpResponseRedirect(f"/posts/feeds/#post-{comment.post.id}")
  • posts/urls.py
from django.urls import path
from posts.views import feeds, comment_add, comment_delete
...

urlpatterns = [
    path("feeds/", feeds),
    path("comment_add/", comment_add),
    path("comment_delete/<int:comment_id>/", comment_delete)
]
  • posts/views.py
    • 삭제할 Comment가 요청한 사용자가 작성한 것인지 확인
from django.http import HttpResponseRedirect, HttpResponseForbidden

@require_POST
def comment_delete(request, comment_id):
    comment = Comment.objects.get(id=comment_id)
    if comment.user == request.user:
        comment.delete()
        return HttpResponseRedirect(f"/posts/feeds/#post-{comment.post.id}")
    else:
        return HttpResponseForbidden("이 댓글을 삭제할 권한이 없습니다.")
  • templates/posts/feeds.html
    • 템플릿에 삭제 버튼 추가
<div class="post-comments">
    <ul>
        {% for comment in post.comment_set.all %}
            <li>
                <span>{{ comment.user.username }}</span>
                <span>{{ comment.content }}</span>

                <!-- 댓글 삭제 form 추가 -->
                {% if user == comment.user %}
                    <form method="POST" action="/posts/comment_delete/{{ comment.id }}/">
                        {% csrf_token %}
                        <button type="submit">삭제</button>
                    </form>
                {% endif %}
            </li>
        {% endfor %}
    </ul>
</div>

2.2 글 작성하기

 

2.2.1 글 작성 기본구조 구현

 
  • View: /posts/post_add/
  • URL: post_add
  • Template: templates/posts/post_add.html
  • posts/views.py
...
def post_add(request):
    return render(request, "posts/post_add.html")
  • posts/urls.py
...
from posts.views import feeds, comment_add, comment_delete, post_add
...

urlpatterns = [
    ...
    path("post_add/", post_add),
]
  • templates/posts/post_add.html
{% externds 'base.html' %}

{% block content %}
    <div id="post-add">
        <h1>Post Add</h1>
    </div>
{% endblock %}

2.2.2 PostForm 클래스 구현

  • posts/forms.py
from posts.models import Comment, Post

...

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = [
            "content",
        ]

2.2.3 View 로직, Template 구현

  • posts/views.py
from posts.forms import CommentForm, PostForm
...

def post_add(request):
    form = PostForm()
    context = {"form": form}
    return render(request, "posts/post_add.html", context)
  • templates/posts/post_add.html
{% externds 'base.html' %}

{% block content %}
    <nav>
        <h1>Pystagram</h1>
    </nav>
    <div id="post-add">
        <h1>Post Add</h1>
        <form method="POST">
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit">게시</button>
        </form>
    </div>
{% endblock %}

2.2.4 여러 장의 이미지 업로드

  • templates/posts/post_add.html
    • Template에 직접 < input type="file"> 구성
...
<form method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    <div>
        <labl for="id_images">이미지</label>
        <input id="id_images" name="images" type="file" multiple>
    </div>
    {{ form.as_p }}
    <button type="submit">게시</button>
</form>
...
  • posts/views.py
    • View에서 multiple 속성을 가진 file input의 데이터 받기
from posts.models import Post, Comment, PostImage

...

def post_add(request):
    if request.method == "POST":
        # request.POST로 온 데이터 ("content")는 PostForm으로 처리
        form = PostForm(request.POST)

        if form.is_valid():
            # Post의 "user"값은 request에서 가져와 자동할당한다
            post = form.save(commit=False)
            post.user = request.user
            post.save()

            # Post를 생성 한 후
            # request.FILES.getlist("images")로 전송된 이미지들을 순회하며 PostImage객체를 생성한다
            for image_file in request.FILES.getlist("images"):
                # request.FILES또는 request.FILES.getlist()로 가져온 파일은
                # Model의 ImageField부분에 곧바로 할당한다
                PostImage.objects.create(
                    post=post,
                    photo=image_file,
                )

            # 모든 PostImage와 Post의 생성이 완료되면
            # 피드페이지로 이동하여 생성된 Post의 위치로 스크롤되도록 한다
            url = reverse("posts:feeds") + f"#post-{post.id}"
            return HttpResponseRedirect(url)

    # GET요청일 때는 빈 form을 보여주도록한다
    else:
        form = PostForm()

    context = {"form": form}
    return render(request, "posts/post_add.html", context)

2.2.5 내비게이션 바에 링크 추가

  • templates/posts/feeds.html
    • 피드 페이지에서 글 작성 페이지로의 작성 추가
<nav>
    <h1>
        <a href="/posts/feeds/">Pystagram</a>
    </h1>
    <a href="/posts/post_add/">Add post</a>
    <a href="/users/logout/">Logout</a>
</nav>
  • templates/posts/post_add.html
    • 글 작성 페이지에서 피드 페이지로 돌아오는 링크 추가
<nav>
    <h1>
        <a href="/posts/feeds/">Pystagram</a>
    </h1>
    <a href="/users/logout/">Logout</a>
</nav>
728x90