본문 바로가기
DEV/Web 개발

Web 개발 :: Django 머신러닝 프로젝트 Code Review _ User_TIL#40

by 올커 2022. 10. 29.

■ JITHub 개발일지 40일차

□  TIL(Today I Learned) ::

Django 머신러닝 프로젝트 Code Review

   1) 게시글(post) 업데이트/삭제 기능 구현 

 - 게시글 업데이트나 삭제는 게시글 상세페이지와 동일하게 특정 게시글이 존재한다. 이 때문에 url에도 <int:post_id>를 넣어주어 특정 포스트의 id를 활용하도록 한다.

# post/urls.py

urlpatterns = [
    path('', views.index, name='index'),
    path('post/create/', views.post_create, name='post-create'),
    path('post/detail/<int:post_id>/', views.post_detail, name='post-detail'),
    path('post/update/<int:post_id>/', views.post_update, name='post-update'),
    path('post/delete/<int:post_id>/', views.post_delete, name='post-delete'),
	...
]

 - login_required는 로그인이 필요할 때 사용하도록 한다. 그런데 로그인이 안되어있을 경우 특정 페이지로 보내주어야 할 때 아래와 같이 데코레이터를 사용하면 굉장히 편리하다. (대부분 로그인 상태가 필요해서 넣다보니 무분별하게 쓰고있는 건가?싶은 생각이 들 때도 있다. 코딩을 할 때 한땀 한땀 생각하면서 짜는 습관이 필요할 듯 하다.)

@login_required(login_url='user:signin')

 - 게시글 수정은 아래와 같이 짜주었다. 기존의 게시글을 가져와야 하니 GET, 다 작성하고 나면 POST. 간단하다.

   GET은 해당되는(id=post_id)포스트의 글 내용을 가져온다. (Post.objects.get(id=post_id))

   그런데, 수정은 로그인상태와 별개로 해당 게시자와 유저가 동일해야 하니 if문으로 한번 더 체크해주었다.

   글을 보내줄 때에는 render해주는데, context라는 이름으로 딕셔너리를 만들어 key, value를 보내주는 형식을 많이 봐서 그런지, 생각대로 코딩할 때에는 {'post':post}로 작성하니 뭔가 어색했지만 잘 동작했다. 익숙한 것에 대한 경계...

   POST도 마찬가지로 유저가 작성하고 있는 POST정보를 가져오고, 해당되는 포스트의 필드를 각각 불러와서(post.title 등) 현재 작성된 내용(request.POST.get('title') 등)을 넣어준다. 마지막에 꼭 save해주고 redirect해준다.

# post/views.py

@login_required(login_url='user:signin')
def post_update(request, post_id):
    if request.method == 'GET':
        post = Post.objects.get(id=post_id)
        if request.user == post.author:
            return render(request, 'post/post/post_update.html', {'post':post})
        return redirect('/')

    elif request.method == 'POST':
        post = Post.objects.get(id=post_id)
        post.title = request.POST.get('title')
        post.content = request.POST.get('content')
        post.save()
        return redirect('/')

 - 포스트 삭제는 아래처럼 작성하였다.

# post/views.py

@login_required(login_url='user:signin')
def post_delete(request, post_id):
    post = Post.objects.get(id=post_id)
    post.delete()
    return redirect('/')

 

 

   2) 댓글(comment) 생성/업데이트/삭제 기능 구현 

 - 코멘트도 마찬가지로 view가 작동할 때 필요한 해당되는 comment_id를 url에 각각 넣어주어 사용한다. 그런데! url에 포스트 id는 안적어주어도 크게 문제는 없었다.

# post/urls.py

urlpatterns = [
    ...
    path('comment/create/<int:comment_id>/', views.comment_create, name='comment-create'),
    path('comment/update/<int:comment_id>/', views.comment_update, name='comment-update'),
    path('comment/delete/<int:comment_id>/', views.comment_delete, name='comment-delete'),
]

 - 코멘트 생성/수정/삭제도 게시글과 크게 다르지 않았다. 코멘트를 만들 때에는 save가 아닌 create를 사용해보았다.

 - 코멘트 업데이트에는 해당 유저에게만 삭제 버튼을 띄우기 때문에 코드를 간단하게만 작성했다.

# post/views.py

@login_required(login_url='user:siginin')
def comment_create(request, comment_id):
    if request.method == "POST":
        author = request.user
        post = Post.objects.get(id=comment_id)
        content = request.POST.get('content')
        Comment.objects.create(author = author, post = post, content = content)
        return redirect('post:post-detail', post.id)


@login_required(login_url='user:siginin')
def comment_update(request, comment_id):
    if request.method == 'GET':
        comment = Comment.objects.get(id=comment_id)
        return render(request, 'post/post/comment_update_form.html', {'comment':comment})

    elif request.method == 'POST':
        update_comment = Comment.objects.get(id=comment_id)
        post_id = update_comment.post_id
        update_comment.content = request.POST.get('content')
        update_comment.save()
        return redirect('post:post-detail', post_id)

 - 코멘트 삭제시에는 유의할 점이 있다. 해당 코멘트는 특정 포스트의 id정보를 갖고 있다. 아래 뷰를 통해 코멘트를 삭제할 경우 redirect할 때 코멘트 정보 내에 있는 포스트 id정보 또한 삭제되기 때문에 임시로 current_post라는 곳에 post_id를 저장해두고, comment를 삭제한 후 current_post를 통해 redirect를 해줘야 한다.

# post/views.py

@login_required(login_url='user:siginin')
def comment_delete(request, comment_id):
    comment = Comment.objects.get(id=comment_id)
    current_post = comment.post.id              # 코멘트 삭제 후 기존 코멘트가 해당되는 포스트로 되돌아가기 위해 포스트 id 저장
    comment.delete()
    return redirect('post:post-detail', current_post)

 

   3) 포스트 좋아요, 사용자 follow하기

 - 특정 게시글에 좋아요하는 것이기 때문에 post_id를 url에 넣어주었다.

# post/urls.py

urlpatterns = [
    ...
    path('post/likes/<int:post_id>/', views.post_likes, name='post-likes'),
	...
]

 - 특정 포스트에 좋아요하기 때문에 post_id는 꼭 넣어줘야 한다. 좋아요는 시행할 때마다 해당 포스트에 좋아요를 했는지? 안했는지? 확인하고 수행되어야 한다. 포스트 모델에는 아래와 같이 ManyToManyField로 like_posts라는 이름으로 필드를 정의하였다.

# post/models.py

class Post(models.Model):
    ...
    like_authors = models.ManyToManyField(User, related_name='like_posts')

 - 뷰에서도 이 필드 전체(.all())를 불러와서 그 중에 유저가 있는지 확인하고, 없으면 넣어주고(add(유저이름)), 있으면 제거(remove(유저이름))하는 방식으로 하고, 마지막으로 시행이 끝나면 다시 해당 포스트로 보내주기 위해 post_id를 넘겨준다.

# post/views.py

def post_likes(request, post_id):
    post = Post.objects.get(id=post_id)
    user = request.user
    if user in post.like_authors.all():
        post.like_authors.remove(user)
    else:
        post.like_authors.add(user)
    return redirect('post:post-detail', post_id)

 

   4) 게시글에 이미지 업로드할 때 유의할 점

 - 게시글에 이미지를 업로드할 때 업로드가 계속 안되는 상황이 발생하였다. 예전에 다 구성했던 것들인데 잊었던 것이 화근이다. 아래처럼 다시 체크해보았다.

 - 템플릿의 form 태그에 enctype을 수정해주었는지 확인한다.

<form ... enctype="multipart/form-data">

 

 - setting.py에 MEDIA_ROOT를 넣어주어야 한다. media라는 폴더로 들어가게 정의해준다.

# setting.py

import os

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

 - post/urls.py에도 맨 아랫줄처럼 추가 urlpatterns를 정의해준다.

# post/urls.py

from django.conf.urls.static import static

...
app_name = 'post'

urlpatterns = [
    path('', views.index, name='index'),
    ...
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

 - models.py에는 ImageField, 그리고 디렉터리 경로를 설정한다.

# post/models.py

class Post(models.Model):
    title = models.CharField(max_length=30, blank=True)
    image = models.ImageField(upload_to="post_pics", blank=True)
    ...

 - views.py에서는 request.FILES.get으로 이미지를 받아오도록 한다.

# post/views.py

def post_create(request):
    ...
    
    elif request.method =='POST':
        post = Post()
        post.title = request.POST.get('title')
        post.content = request.POST.get('content')
        post.image = request.FILES.get('before_image')
        ...

 

   5) 나중에 추가적으로 해보아야 할 것 

 - useradmin을 사용하여 user model을 만들어볼 것

 - Q를 사용한 검색기능 구현해볼 것

 - notification 기능 구현해볼 것

 

 

   6) timeattack : drf

 

# settings.py

INSTALLED_APPS = [
    ...
    'rest_framework',
    'article',
]


REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication'
    ]
}

 

# urls.py

from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('api/article/', include('article.urls')),
]

 

# article/models.py

from django.db import models
# from django.contrib.auth.models import User       # 1
from django.contrib.auth import get_user_model      # 2. 현재 장고프로젝트에 지정된 모델을 가져온다.(AUTH_USER_MODEL이 지정되었을 경우 해당 모델을 가져온다.)

# Create your models here.
class Article(models.Model):
    author = models.ForeignKey(get_user_model(), verbose_name="작성자", on_delete=models.CASCADE)
    title = models.CharField(max_length=50)
    content = models.TextField()
# article/serializers.py

from rest_framework import serializers

from article.models import Article

# # 방법 1
# class ArticleSerializer(serializers.ModelSerializer):
#     class Meta:
#         model = Article
#         fields = ['id', 'title', 'content']


# 방법 2
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'

        extra_kwargs = {
            'author': {'read_only' : True}          # write할 때에는 author를 보지 않고, read할 때에만 차조하겠다.
        }

# # 방법 3
# class ArticleSerializer(serializers.ModelSerializer):
#     author = serializers.SerializerMethodField()

#     def get_user(self, obj):
#         return obj.author.username

#     class Meta:
#         model = Article
#         fields = '__all__'

#         extra_kwargs = {
#             'author': {'read_only' : True}          # write할 때에는 author를 보지 않고, read할 때에만 차조하겠다.
#         }

#     def create(self, validated_data):
#         author = self.context["request"].user
#         ...
# article/urls.py

from django.urls import path
from article import views

urlpatterns = [
    path('', views.ArticleView.as_view(), name="article_view"),
]
# article/views.py

from django.shortcuts import render

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status

from article.serializers import ArticleSerializer
from .models import Article

# Create your views here.
class ArticleView(APIView):
    def post(self, request):

        # print(request.user)
        # serializer = ArticleSerializer(data=request.data)
        # if serializer.is_valid(user=request.user):
        #     serializer.save()
        #     return Response(serializer.data, status.HTTP_200_OK)
        # else:
        #     return Response(serializer.errors, stutus=status.HTTP_400_BAD_REQUEST)

        article = ArticleSerializer(data=request.data)

        # 방법1
        article.is_valid(raise_exception=True)

        # 방법2
        if not article.is_valid():
            return Response(article.errors)

        # 방법 -1
        # article.save(author=request.user)       # 그냥 실행시 오류 발생. fields에서 ['id', 'title', 'content']만 넣어주어야 한다.

        return Response(article.data)



    def get(self, request):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

 

반응형

댓글