서버로부터 데이터를 받아올 때 한번에 다 받아올 수도 있지만 데이터가 많을 경우 일부분만 받아와야 할 경우가 생긴다. 예를 들면 대부분의 홈페이지에 게시판을 보더라도 한번에 모든 글들을 리스트로 보여주지 않고 아래 숫자로 page 를 넘기는 식으로 구현되어 있는데 이럴 경우에 사용하는 것이 Pagination 이다. Django RestFramework 에서는 기본적으로 Pagination 를 제공하는데 활용한다면 특별한 구현 필요 없이 자동으로 Page 로 구성이 된 형식으로 결과를 받아볼 수 있다. 아래는 공식 guide book 에서 설명한 pagination 이다. 아래의 링크를 보고 따라해도 되고 간단하니 내가 작성한 글을 따라 해도 된다.

https://www.django-rest-framework.org/api-guide/pagination/

 

Pagination - Django REST framework

pagination.py Django provides a few classes that help you manage paginated data – that is, data that’s split across several pages, with “Previous/Next” links. — Django documentation REST framework includes support for customizable pagination styles. This a

www.django-rest-framework.org

 

1. Setting.py 설정

 아래와 같이 REST_FRAMEWORK 의 Default Pagination Class 를 정의해주면 Pagination 된 결과를 확인할 수 있다.

LimitOffsetPagination 은 Parameter 를 통해 limit값과 offset 값을 줄 수 있는데, 예를 들어 get url 마지막에 limit=40&offset=100 이라고 보내준다면 40개의 데이터를 100번째 부터 달라는 의미이다.

 

Settings.py

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 100
}

무튼 Settings.py 만 정의해주면 아래같이 pagination 된 결과를 받을 수 있다. 아래에서 count field 는 총 Data 의 갯수이고 next 와 previous 는 다음 값과 이전 값의 url 이다. 현재는 default page_size 로 100이 선언이 되었기 때문에 다음 페이지가 필요가 없으므로 next가 null 로 비어 있다. 

2. Pagination Custom

Default 값으로 선언된 Pagination 말고도 직접 나의 Viewset 이나 GenericAPIView 에 내가 정의한 Pagination 을 할당 해 줄 수 있다. 공식 가이드 처럼 PageNumberPagination 를 override 해서 사용해보자. PageNumberPagination 은 파라미터로 Page 번호만 받는 방식이다. 

http://localhost:8000/posts/?page_size=2

views.py 에 PageNumberPagination 을 import 한 후 가이드처럼 LargeResultsSetPagination 이름의 PageNumberPagination 를 override 한 class 를 선언해준다 한번에 가져올 Data 갯수 parameter 로 사용할 page_size 를 정의 해주고 page_query_param 변수에 할당 해준다. max_page_size 는 최대 요청 가능한 page size 값이다.

 그리고 아래와 같이 Vieset 의 pagination_class 에 내가 정의한 LargeResultsSetPagination 를 할당해 준다.

 이러면 끝이다. 결과를 살펴보자. 참고로 GenericAPIView 에도 똑같은 방식으로 pagination_class 가 존재하고 값을 할당해 줄 수 있다.

 

views.py

...
from rest_framework.pagination import PageNumberPagination

class LargeResultsSetPagination(PageNumberPagination):
    page_size = 3
    page_query_param = 'page_size'
    max_page_size = 100

class PostViewset(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    pagination_class = LargeResultsSetPagination

page size 가 3으로 되어있고 Data 는 총 4개이기 때문에 next 에 다음 query 가 있는 것을 볼 수 있다.

 

3. 결과 확인

 next 를 눌러보면 다음 Data set 이 보여진다. 4개이니 next는 없고 이전값인 previous query 가 있는 것을 볼 수 있다.

 

기존에는 단순 results 의 List 만을 가지고 안드로이드의 retrofit 을 사용해서 restAPI 를 호출 하였지만 pagination 을 활용 하니 json 의 구조가 바뀌었다. 안드로이드에 바뀐 구조를 받아오는 restfit 예제도 실습한뒤에 포스팅 하겠다.

내가 쓴 글이 있으면 밑에 댓글 기능을 추가 하고싶은 요구사항이 생기는 것은 당연하다. Comment 자체를 새로운 App 으로 만들어서 구현한 예제도 있는 듯 하나 Post 모델과 Comment 모델을 하나의 app 에서 정의해보는 예제를 구현해 보았다.

 

1. Comment 모델 추가

기존에 사용하던 app 의 post 모델이 선언된 models.py 파일에 아래와 같이 Comment Model을 추가해준다.

text field 하나를 가지고, post field 는 foreignkey 로 선언해준다. foreignkey 에 Post 모델을 넣어주면 comment 는 Post 에 종속성을 가진다.

 

models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    text = models.TextField()
    image = models.ImageField(upload_to="%Y/%m/%d")
    

class Comment(models.Model):
    text = models.CharField(max_length=200)
    post = models.ForeignKey('Post', related_name='comments', on_delete=models.CASCADE, blank=True)

 

2. Serializers 추가

Comment 모델에 대한 직렬화를 해주기 위하여 생성해 놓은 serializers.py 파일에 CommentSerializer 를 정의 해준다.

.model 에 Comment 를 import 시켜주는 것은 잊지말자. CommentSerializer 의 model 에 Comment 를 정의해주고 fields 에는 __all__을 넣어주면 Comment 모델에서 사용하는 모든 field 가 들어간다.

 기존에 PostSerializer 에는 comments field 를 추가해 주는데 하나의 Post에는 여러개의 comment 가 존재할 수 있으므로 many=True 라고 정의 해준다. 

 

Serializers.py

from rest_framework import  serializers
from .models import Post, Comment

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'

class PostSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(use_url=True)
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ('title', 'text', 'image', 'comments')


 

3. Viewset 추가

 View는 가장 쉬운 Viewset 을 사용하여 Comment 의 View 를 정의해보았다.

여기에서도 새로만든 Comment 모델과 CommentSerializer 를 import 하는 것을 잊지 말자.

기존에 선언해 두었던 Postviewset 과 동일하게 CommentViewset 를 정의해줫다.

 

views.py

from rest_framework import viewsets
from .serializers import PostSerializer, CommentSerializer
from .models import Post, Comment

class PostViewset(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class CommentViewset(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

 

4. URL 추가

url은 기존에 post 가 쓰고 있었던 Router에 CommentViewset 을 추가 해주기만 하면 바로 동작된다.

알고 있겟지만 viewset 을 사용하면 자동으로 CRUD 에 해당하는 URL 이 정의 된다.

아래에서 추가되는 코드는 router.register(r'comments', views.CommentViewset) 이 것 단 한줄이다.

 

urls.py

router = routers.DefaultRouter()
router.register(r'posts', views.PostViewset)
router.register(r'comments', views.CommentViewset)


urlpatterns = [
    url(r'^',include(router.urls)),
    ...
    

 

5. 실행

실행해보자 실행전에 database migration을 진행한다. 진행 후 server를 실행한다.

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver

http://localhost:8000/ 에 접속하면 기존에는 posts api 밖에 없었지만 comments 까지 추가가 되 있는 것을 볼 수 있다.

posts 를 호출해보면 Post Model에 추가적으로 정의했던 comments field 가 추가된 것을 확인 할 수 있다. 원래는 공백이지만 나는 미리 구현 해놓고 테스트를 해봐서 comment 들이 남아 있는 상황이다.

이제 http://localhost:8000/comments/ 에 들어가보자. 내가 입력했던 comment들을 볼 수 있고 아래의 입력창을 보게 되면 text 와 Post field 가 있다. 

Post field 의 경우 Foreign key 로 선언을 해놓았기 때문에 작성한 Post 들 중에서 선택할 수 있는 UI 를 만날 수 있다.

하나 선택을 해서 입력을 해보자. Post를 누르게 되면 아래와 같이 입력한 comment 가 저장이 되고 post field 에는 post의 PK 값이 들어가게 된다.

Comment 가 잘내려오는지 Post 에 가서 확인해보자. 아래와 같이 내가 선택했던 Post에 댓글이 성공적으로 추가된 것을 확인 할 수 있다.

 

Post Field 에 내가 선언해 놓은 field 들이 더 있어서 보이는 것이니 혼동하지 말자. 

 

서버시나리오에서 빠지지 않는 기능은 이미지를 업로드 하고 보는 기능이다 RestFramework 을 이용하면 ImageField 를 활용하여 쉽게 Image를 업로드 할 수 있다. 기존에 사용했던 Model에 ImageField를 추가해서 사용하는 방법으로 구현을 해보겠다.

 

1. /media 폴더 추가

프로젝트 root directory 에 media folder를 생성한다.

 

2. Setting 및 URL 추가

MEDIA_URL 은 Media 파일에 대해서 고정된 URL 값을 설정할 때 사용 한다. 

MEDIA_ROOT 는 db 에는 파일의 경로만 저장이 되기 때문에 앞으로 파일이 저장될 root 경로를 정해준다.

 

settings.py

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

 

MEDIA_URL 에 대한 요청이 왔을 때 MEDIA_ROOT 에서 일을 처리하게된다.

urls.py

...
from django.conf import settings
from django.conf.urls.static import static
...


urlpatterns = [
	...
]  + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

3. Model 에 ImageField 추가

기존에 선언해 두었던 Post Model에 ImageField 를 추가한다.

upload_to="%Y/%m/%d" 를 사용하면 /Root/년도/월/일 폴더가 생성되어 저장되고

upload_to="String" String 값을 넣어주면 특정 폴더에 저장을 해준다. Image File 이 많아지면서 한 Directory 에 사진이 많아지게되면 성능상의 이슈가 생기므로 Image Path 를 컨트럴을 잘해줄 필요가 생길 수 있다.

models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    text = models.TextField()
    image = models.ImageField(upload_to="%Y/%m/%d")
    #image = models.ImageField(upload_to="image")

 

후에 이미지 업로드를 해보면 아래와 같이 폴더가 생성되어 이미지가 저장된 것을 확인할 수 있다.

 

4. Serializers.py 에 image field를 추가

RestFramework 에서 사용하는 Serializer.py 에 Image field를 추가한다. Meta class의 fields 에 값을 추가한다.

use_url 을 True 로 선언해 주면 output으로 URL String 이 내려오고 False 이면 File Name 이 output 으로 나온다. Default 값은 UPLOADED_FILES_USE_URL 로 set 되어 있는데, 따로 설정안한다면 이 값은 True 값을 가지고 있다.

Serializers.py

from rest_framework import  serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(use_url=True)
    
    class Meta:
        model = Post
        fields = ('title', 'text', 'image')

5. DB migrations

DB가 변경되었으므로 migrate해준다.

$ python manage.py makemigrations
$ python manage.py migrate

위의 명령어를 사용해보니 Pillow 라는 앱을 사용해야 되나보다. Pillow 라는 앱이 install 이 안되어있다는 error 메세지가 뜬다. 다행히 HINT 에서 나온대로 pip install Pillow 하니 바로 해결되었다.

설치 후 다시 makemigrations 한 결과이다. 잘 추가 되었다는 메시지를 볼 수 있다.

migrate 까지 완료 시켜주고 서버를 실행해보자

 

결과 확인

Post 창에 Image Field 가 생긴 것을 확인 할 수 있다. 데이터를 입력해주고 POST 를 클릭해보도록 하자.

POST 를 누르게 되면 아래와 같이 내가 입력한 데이터가 잘 들어간 것을 보여준다. 나같은 경우는 upload_to 에 %Y/%m/%d를 넣어줫기 때문에 Image URL Path 가 아래와 같이 형성된 것을 확인할 수 있다.

URL 을 클릭해보면 내가 올린 이미지가 보이는 것을 확인할 수 있다.

 

 서버시나리오를 생각 했을 때 빠질 수 없는 것이 회원가입, 로그인, 로그아웃 시나리오 이다. 한번만 구현을 해본 경험이 있으면 앞으로 꾸준히 쓸 수 있을 것 같아서 정리를 해두려고 한다.

 

로그인을 관리하기 위해서는 Django 에서 제공하는 Django-rest-auth 라는 패키지를 이용하여 구현을 한다. 클릭 하셔서 Docs 를 보고 직접 구현을 하셔도 되고 내가 실습한 예제를 보셔도 된다.

 

1. Django-rest-auth 설치

아래의 패키지를 나의 가상환경에 설치해준다.

$ pip install django-rest-auth

 

2. Setting.py 에 rest_auth 추가

Setting.py 에 rest_auth 를 추가해준다. rest_framework.authtoken 을 같이 설치해준다. Doc에 보면 rest_auth Project 는 django-rest-framework libary 에 dependency 가 있으므로 rest_framework 을 꼭 설치 하라고 가이드 되어 있다. rest_framework 에 대한 예제는 https://freekim.tistory.com/4 에 나와 있다. 참고 하시길.

 

settings.py

INSTALLED_APPS = [
    ...,
    
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth',
    ...,
]

 참고) rest_framework 이 설치가 안되어 있는경우

pip install djangorestframework

 

3. rest_auth 의 url 을 추가

urls.py

urlpatterns = [
    ...
    url(r'^rest-auth/', include('rest_auth.urls')),
    ...
]

 

4. Database 에 migration 하기 

url 까지 추가가 끝났으면 database 에 migrate 해준다.

$ python manage.py migrate

명령어를 python 명령어를 실행하면 아래의 화면을 볼 수 있다.

5. Registration

 registration 을 해주기 위해서 setting.py 에 아래의 앱들을 추가해 준다. 이 때 social 의 Authentication 을 추가할 수 있으나 이건 나중에 해보기로 하자. 그리고 SITE_ID = 1 도 추가해준다. allauth app이 설치가 안되어 있다면 설치해주자.

pip install django-allauth

Setting.py

INSTALLED_APPS = [
    ...
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'rest_auth.registration',
]

SITE_ID = 1

 

url 도 추가해주자

urls.py

urlpatterns = [
    ...,
    url(r'^rest-auth/', include('rest_auth.urls')),
    url(r'^rest-auth/registration/', include('rest_auth.registration.urls'))
]

 

Setting 에 아래의 configuration 값을 넣어주면 jwt 를 활성화 해줄 수 있는데, 아직 JWT 를 학습하지 않아 나중에 JWT POST 를 작성하면서 더 자세한 설명을 하도록 하겠다. 

 Docs 를 읽어보면 django-rest-auth 는 Default로 Django’s Token-based authentication이지만 JWT(Json Web Token)로 사용 할 수 있게 해주는 것 같다. 나같은경우는 rest api 로 json 으로 응답을 받으니 JWT 는 다음 포스트에 공부해서 바로 사용 해보아야 겠다.

REST_USE_JWT = True

 

이제 API 의 EndPoint 를 살펴보자

<< Basic >>

  • /rest-auth/login/ (POST)

    • username
    • email
    • password

    Returns Token key

  • /rest-auth/logout/ (POST)

  • /rest-auth/password/reset/ (POST)

    • email
  • /rest-auth/password/reset/confirm/ (POST)

    • uid
    • token
    • new_password1
    • new_password2
  • /rest-auth/password/change/ (POST)

    • new_password1
    • new_password2
    • old_password
  • /rest-auth/user/ (GET, PUT, PATCH)

    • username
    • first_name
    • last_name

    Returns pk, username, email, first_name, last_name

<< Registration >>

  • /rest-auth/registration/ (POST)

    • username
    • password1
    • password2
    • email
  • /rest-auth/registration/verify-email/ (POST)

    • key

 

결과 확인

아래의 명령어를 통해 server를 실행 시킨다.

$ python manage.py runserver

1. end point 의 registration 을 먼저 접근해보자. 아래의 페이지를 볼 수 있다.

간단하게 정보를 입력하고 post 를 해보자. (e-mail 은 default로 활성화 되어 있다. 아래와 같이 설정을 바꾸면 변경 가능하다.

ACCOUNT_EMAIL_REQUIRED = False

ACCOUNT_EMAIL_VERIFICATION = 'none'

)

http://localhost:8000/rest-auth/registration/

나같은 경우에는 migrate 이 잘못됫는지 email 이 field 가 없다는 오류가 났었다. allauth 에 뭔가 연관이 있는것 같기도 하고 무튼 다시migrate 을 해주고 실행을 하니 정상 동작 했으나 password 를 너무 짧게 썻었는지 아래의 결과를 얻었다. 기본적으로 비밀번호 길이에 대한 예외처리가 들어가 있나보다. 편하다. 그리고 단순한 password 에 대해서도 예외처리가 되어 있나보다.

 

비밀 번호를 변경해 준 후 post 를 눌러보니 잘안되는 것 같았지만 등록이 되었다. 등록이 되었다면 로그인을 해보자.

localhost:8000/rest-auth/login 을 하면 아래의 화면을 볼 수 있다.

방금 전 registration 한 정보를 입력 시켜주고 POST 하게 되면

아래의 그림처럼 token값이 넘어오면서 오른쪽 상단에 볼 수 있듯이 내가 입력한 id 로 로그인이 되게 된다.

이 밖에 다른 endpoint 들도 test해보시길 바란다.

장고 server 를 테스트 하기 위해서 웹을 사용한다면 로컬에서도 충분히 확인을 할 수 있지만

IOS 나 Android 개발을 할 때 에 서버가 필요한 경우가 있다.

물론 돈을 내고 호스팅을 하거나 집에 서버 컴퓨터가 있으면 걱정이 없겠지만

단순히 테스트 용도로 사용하기에는 무리가 있다.

여러 방법을 찾던중 ngrok 을 사용하여 5분만에 내 컴퓨터를 테스트용 홈서버로 만드는 방법을 소개 하고자 한다.

 

1. ngrok download

https://ngrok.com/download 에 접속하여 ngork 을 다운로드 받는다.

 

2. django server 실행

 - 아래의 명령어로 장고 서버를 실행해 준다. 0.0.0.0:8000 의미는 현재 요청된 호스트의 IP나 이름에 상관없이 실행한다는 의미이다.

$ python manage.py runserver 0.0.0.0:8000

 - django project의 settings.py 수정

ALLOWED_HOSTS = ['*']

3. ngrok 실행

 - ngrok을 실행해보면 아래의 커맨드 화면이 보인다.

그리고 내가 연 포트번호를 넣어 아래의 명령어를 실행해준다.

>ngrok http 8000

 주소가 부여되는데 핸드폰 단말에서 해당 주소로 들어가면 내가 구성해놓은 rest api 페이지를 볼 수 있다.

 

ngrok 은 도메인을 할당하는 것 처럼 ngrok 을 통해서 고정 아이피와 나의 서버를 바인딩 해준다. 다른 서버를 들렸다 오는 것이므로 속도가 느리고 테스트용도로만 사용하자.

1. Restframework 설치

 - 터미널에 아래의 명령어를 입력시켜 준다.

pip install djangorestframework

 - settings.py 에 rest framework 추가 해준다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
]

2. 어플리케이션 생성

 - 어플리케이션을 생성한다.

$ python manage.py startapp myapp

 - setting.py 에 추가한다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'myapp'
]

3. 모델 생성

 myapp/models.py 에 간단하게 아래의 post model class 를 추가해준다.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    text = models.TextField()

아래의 명령어로 데이터 베이스에 모델을 위한 테이블을 형성한다.

$ python manage.py makemigrations myapp
$ python manage.py migrate

4. Serializers

쿼리셋, 모델 인스턴스 등의 복잡한 데이터를 json, xml 등의 데이터로 변환해준다.

-  myapp/serializers.py 생성 후 아래의 코드를 입력한다.

from rest_framework import  serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('title', 'text')

 

5. Views

 Viewset 하나에 다양한 method를 포함하고 있다.

 - myapp/views.py 수정한다.

from rest_framework import viewsets
from .serializers import PostSerializer
from .models import Post

class PostViewset(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

 

6. urls 

router 를 설정해 준다. router 는 URL을 자동으로 맵핑해준다.

 - project/urls

from rest_framework import routers
from myapp import views
from django.conf.urls import url, include

router = routers.DefaultRouter()
router.register(r'posts', views.PostViewset)


urlpatterns = [
    url(r'^',include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

 

7. 서버 실행 후 동작 확인

$ python manage.py runserver

 - http://127.0.0.1:8000/

서버를 실행 후 위 주소로 들어가면 Default router에 의해서 접근할 수 있는 URL 인 http://127.0.0.1:8000/posts가 표시된다.

http://127.0.0.1:8000/posts에 접속을 해보면 아래와 같이 post list를 볼 수 있다. 현재는 데이터를 하나도 등록을 안시켰으므로 비어 있다.

하단의 title 과 text 에 test 문구를 넣어주고 post 버튼을 눌러보자. post 를 누르면 post된 data만 보이게되고

다시 오른쪽 상단의 get버튼으로 list를 다시 받아오면 아래와 같이 내가 등록했던 data list를 볼 수 있다.

 

기본적으로 rest framework 을 구성해 보았다. 

다음 시간에는 안드로이드와 연결되서 실제로 어떻게 쓸 수 있는지 알아보고자 한다.

+ Recent posts