본문 바로가기

PYTHON-BACK

#파이썬 31일차_게시판 만들기3

728x90

rest

모델 - 시리얼라이져 만듬

 

장고 기본모델 - 템플러? 만듬?

 

모델과 시리얼라이저, 그리고 뷰와 템플릿을 어떻게 연결하는지 설명해 드리겠습니다.

1. 모델 생성

먼저, Django의 기본 모델을 상속받아 새로운 모델을 생성합니다.

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    author = models.ForeignKey('auth.User', related_name='articles', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

2. 시리얼라이저 생성

이제 위에서 만든 Article 모델을 위한 시리얼라이저를 만듭니다.

from rest_framework import serializers
from .models import Article

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'
 

3. 뷰 생성

REST API로 모델을 다룰 수 있는 뷰를 생성합니다.

from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer
from .permissions import CustomReadOnly  # 이전에 만든 CustomReadOnly 사용

class ArticleListCreateView(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [CustomReadOnly]

class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [CustomReadOnly]

4. URL 라우팅

생성한 뷰를 URL에 연결합니다.

from django.urls import path
from .views import ArticleListCreateView, ArticleDetailView

urlpatterns = [
    path('articles/', ArticleListCreateView.as_view(), name='article-list'),
    path('articles/<int:pk>/', ArticleDetailView.as_view(), name='article-detail'),
]

5. 템플릿 생성

장고 템플릿을 사용해 데이터를 렌더링할 수 있습니다. 예를 들어, articles.html이라는 템플릿을 만들어서 모든 기사를 리스트로 보여줄 수 있습니다.

<!-- templates/articles.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Articles</title>
</head>
<body>
    <h1>Articles</h1>
    <ul>
        {% for article in articles %}
            <li>{{ article.title }} by {{ article.author }}</li>
        {% endfor %}
    </ul>
</body>
</html>

6. 뷰와 템플릿 연결

템플릿을 렌더링하기 위한 Django 뷰를 작성합니다.

from django.shortcuts import render
from .models import Article

def article_list(request):
    articles = Article.objects.all()
    return render(request, 'articles.html', {'articles': articles})

7. URL에 템플릿 뷰 연결

 
from django.urls import path
from .views import article_list

urlpatterns = [
    path('articles/', article_list, name='article-list-html'),
]

정리

이제 Article 모델을 생성하고, 시리얼라이저를 통해 API로 접근할 수 있으며, Django 템플릿을 통해 HTML로 데이터를 렌더링할 수 있습니다. API와 템플릿을 적절히 활용해 프로젝트의 요구사항에 맞는 웹 애플리케이션을 구축할 수 있습니다.


저번시간 이어서 작성

 

4. 필터링 기능

  • 필터링 기능: 게시글 전체를 가져올 때 조건을 걸어 가져오도록 하는 기능
  • Django에서 이미 지원하는 기능임
  • View 등의 코드에서 따로 호출하지 않아도 프로젝트 전역에 적용됨
pip install django-filter

4.1 settings.py 설정

# board/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'users',
    'corsheaders',
    'posts',
    'django_filters',
]

...

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
    ],
}

 

4.2 Views (뷰는 시리얼라이저하고 템플릿을 연결해주는 역할을함, 기존 시리얼라이저들을 참고로 쓰게됨)

# posts/views.py
from django_filters.rest_framework import DjangoFilterBackend

from rest_framework import viewsets

from users.models import Profile
from .models import Post
from .permissions import CustomReadOnly
from .serializers import PostSerializer, PostCreateSerializer


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    permission_classes = [CustomReadOnly]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['author', 'likes']

    def get_serializer_class(self):
        if self.action == 'list' or 'retrieve':
            return PostSerializer
        return PostCreateSerializer

    def perform_create(self, serializer):
        profile = Profile.objects.get(user=self.request.user)
        serializer.save(author=self.request.user, profile=profile)

 

5. 페이징 기능

5.1 Pagination

  • 게시글 전체 조회 페이지를 여러 페이지로 나누는 기능
  • 한 번에 모든 글을 가져오기 부담스러울 경우 한 번의 API 요청으로 가져울 수 있는 데이터의 수를 제한하는 기능
  • 별다른 작업은 필요없음 → settings.py의 REST_FRAMEWORK에 관련 기능을 추가하기만 하면 됨
  • 단, 결과 데이터는 results에 들어가서 프론트엔드에 전달되므로 프론트엔드에서는 데이터를 꺼내가는 과정이 추가로 요구됨
# board/settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
    ],
    'DEFAULT_PAGINATION_CLASS':
    'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE':
    3,
}

6. 좋아요 기능

 

6.1 Views

  • 좋아요 기능은 오직 likes 필드에만 영향을 주므로 간단한 GET 요청 하나로 처리 가능
  • 요구되는 설정
    • 데코레이터로 GET 요청을 받는 함수형 뷰라는 설정
    • 권한이 필요하다는 설정
      • 좋아요를 누르는 권한은 회원가입을 한 유저라면 모두 가능하므로 IsAuthenticated로 설정
  • 처리 내용
    • post.likes.all() 내에 request.user가 있으면 request.user 삭제
    • post.likes.all() 내에 request.user가 없으면 request.user 추가
# posts/views.py
from django_filters.rest_framework import DjangoFilterBackend

from rest_framework import viewsets

from rest_framework.decorators import api_view, permission_classes
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from users.models import Profile
from .models import Post
from .permissions import CustomReadOnly
from .serializers import PostSerializer, PostCreateSerializer


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    permission_classes = [CustomReadOnly]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['author', 'likes']

    def get_serializer_class(self):
        if self.action == 'list' or 'retrieve':
            return PostSerializer
        return PostCreateSerializer

    def perform_create(self, serializer):
        profile = Profile.objects.get(user=self.request.user)
        serializer.save(author=self.request.user, profile=profile)


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def like_post(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.user in post.likes.all():
        post.likes.remove(request.user)
    else:
        post.likes.add(request.user)

    return Response({'status': 'ok'})

 

6.2 URL

# posts/urls.py
from django.urls import path
from rest_framework import routers
from .views import PostViewSet, like_post

router = routers.SimpleRouter()
router.register('posts', PostViewSet)

urlpatterns = router.urls + [
    path('like/<int:pk>/', like_post, name='like_post')
]

 

7. 댓글 기능

7.1 댓글 기능 정리
 
  • 댓글 생성
  • 댓글 1개 가져오기
  • 댓글 목록 가져오기
  • 댓글 수정하기
  • 댓글 삭제하기
  • 게시글을 가져올 때 댓글도 가져오게 만들기

7.2 댓글 모델 & 마이그레이션

  • 댓글 모델에 필요한 필드들
    • 작성자, 작성자 프로필, 게시글, 내용
  • 댓글의 경우 게시글과 밀접한 연관이 있음 → 따로 모델을 만들 필요는 없음
  • Foreign Key로 유저, 프로필, 포스트와 연결됨 + 댓글 내용 텍스트만 추가
# posts/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from users.models import Profile


class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author')
    profile = models.ForeignKey(Profile, on_delete=models.CASCADE, blank=True)
    title = models.CharField(max_length=128)
    category = models.CharField(max_length=128)
    body = models.TextField()
    image = models.ImageField(upload_to='post/', default='default.png')
    likes = models.ManyToManyField(User, related_name='like_posts', blank=True)
    published_date = models.DateTimeField(default=timezone.now)


class Comment(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
    text = models.TextField()

 

  • 마이그레이션
# Migration
python manage.py makemigrations
python manage.py migrate

 

7.3 Serializers

  • 댓글을 작성할 때, 가져올 때 각각 다른 시리얼라이저가 필요함 → 게시글 시리얼라이저와 비슷
  • 게시글에서도 댓글을 불러올 수 있어야 함 → Nested Serializer 개념 활용(작성해 놓은 댓글 시리얼라이저를 게시글 시리얼라이저에 넣어주기)
  • 게시글 시리얼라이저에 댓글 시리얼라이저가 포함됨 → 댓글 시리얼라이저가 더 위에 선언되어야 함
from rest_framework import serializers

from users.serializers import ProfileSerializer
from .models import Post, Comment


class CommentSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer(read_only=True)

    class Meta:
        model = Comment
        fields = ("pk", "profile", "post", "text")


class CommentCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ("post", "text")


class PostSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer(read_only=True)
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ("pk", "profile", "title", "body", "image", "published_date", "likes", "comments")


class PostCreateSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(use_url=True, required=False)

    class Meta:
        model = Post
        fields = ("title", "category", "body", "image")

 

7.4 Views

  • ViewSet 사용
  • 댓글에 필요한 권한은 게시글과 동일
    • 댓글 보기: 모두
    • 댓글 작성: 유저만
    • 댓글 수정/삭제: 해당 댓글 작성자만 → CustomReadOnly 활용
from django_filters.rest_framework import DjangoFilterBackend

from rest_framework import viewsets
from rest_framework.decorators import api_view, permission_classes
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import generics, status

from users.models import Profile
from .models import Post, Comment
from .permissions import CustomReadOnly
from .serializers import PostSerializer, PostCreateSerializer, CommentSerializer, CommentCreateSerializer


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    permission_classes = [CustomReadOnly]
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['author', 'likes']

    def get_serializer_class(self):
        if self.action == 'list' or 'retrieve':
            return PostSerializer
        return PostCreateSerializer

    def perform_create(self, serializer):
        profile = Profile.objects.get(user=self.request.user)
        serializer.save(author=self.request.user, profile=profile)


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def like_post(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.user in post.likes.all():
        post.likes.remove(request.user)
    else:
        post.likes.add(request.user)

    return Response({'status': 'ok'})


class CommentViewSet(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    permission_classes = [CustomReadOnly]

    def get_serializer_class(self):
        if self.action == 'list' or 'retrieve':
            return CommentSerializer
        return CommentCreateSerializer

    def perform_create(self, serializer):
        profile = Profile.objects.get(user=self.request.user)
        serializer.save(author=self.request.user, profile=profile)

 

7.5 URL

from django.urls import path
from rest_framework import routers

from .views import PostViewSet, like_post, CommentViewSet

router = routers.SimpleRouter()
router.register('posts', PostViewSet)
router.register('comments', CommentViewSet)

urlpatterns = router.urls + [
    path('like/<int:pk>/', like_post, name='like_post')
]

 

  • 실행
python manage.py runserver

 

728x90