반응형
  • 권한 문제 해결하기
    • 다른 사람으로 로그인 시, 삭제 및 수정 하지 못하게 함 
    • html기준
    • views 조정으로 해결하기
  • 댓글 기능 구현하기
    • disqus를 활용한 댓글 구현

 

 <권한문제 해결하기>

  • 분기를 통해 간단하게 화면에서 나오지 않도록 만들기
  • 단, 새로운 유저 생성 이후 /delete/2 이런식으로 주소접근 해보면 새로운 유저가 기존 유저의 사진을 수정, 삭제가 가능 
      -> views.py로 해결 가능
  • user와 작성자가 같으면 수정하기와 삭제하기를 보여주도록 설정
  • 새로운 유저 생성 이후에 다시 로그인을 해보면,  다른 유저가 올린 사진에 수정하기와 삭제하기가 사라짐

- photo/templates/photo/photo_list.html

                <div class="card-body">
                    <a href="{% url 'photo:detail' object.id %}" class="card-link">댓글달기</a>
                    {% if user == object.author %}
                    <a href="{% url 'photo:update' object.id %}" class="card-link">수정하기</a>
                    <a href="{% url 'photo:delete' object.id %}" class="card-link">삭제하기</a>
                    {% endif%}
                </div>

 

 

views로 해결 ( /photo/views.py)

- def dispatch를 활용하여 get형태 및 post형태 2가지 모두 커버하기

 

1. PhotoUpdate class 수정 

  • 사용자 접속 시,  get이냐, post이냐를 결정하고 분기를 자동으로 해줌
  • 만약에 작성자와 요청자가 다르면 수정할 권한이 없다는 메시지를 주고 메인페이지로 이동
  • 그렇지 않으면 super을 써줘서 원래 Updateview가 실행되도록 해주며 super을 쓰게 되면 실행시 absolute_url로 자동적으로 이동한다. (단 success url이 설정되어 있으면 우선시 된다.)
from django.http import HttpResponseRedirect
from django.contrib import messages 

class PhotoUpdate(UpdateView):
    model = Photo
    fields = ['text', 'image']
    template_name_suffix = '_update'
    success_url = '/'

    def dispatch(self, request, *args, **kwargs):
        object = self.get_object()

        if object.author != request.user:
            messages.warning(request, '수정할 권한이 없습니다.')

            return HttpResponseRedirect('/')
            #삭제페이지에 권한이 없다고 띄우거나
            #detail 페이지 들어가서 삭제에 실패했다고 띄우거나

        else:
            return super(PhotoUpdate, self).dispatch(request, *args, **kwargs)

 

 

2. PhotoDelete class 수정

class PhotoDelete(DeleteView):
    model = Photo
    template_name_suffix = '_delete'
    success_url = '/'

    def dispatch(self, request, *args, **kwargs):
        object = self.get_object()

        if object.author != request.user:
            messages.warning(request, '삭제할 권한이 없습니다.')
            return HttpResponseRedirect('/')

        return super(PhotoDelete, self).dispatch(request, *args, **kwargs)

 

 

3. layout/base.html

  • {if message}를 통해 만약에 message가 있으면 message를 띄우도록 구현
  • 위치 : {block content}위에 위치하여 화면의 가장 위에 구현
  <div class ="container">
    {% if messages %}
    <div class = "row">
      <div class = "col"></div>
      <div class = "col-6">
        {% for message in messages%}
          <div class = "alert alert-{{message.tag}}">{{message}}</div>
        {% endfor %}
      </div>
      <div class = "col"></div>
    </div>
  </div>
    {% endif %}

  {% block content %}
  {% endblock %}

 

<확인하기>

  • 새로운 유저 생성 이후 다시 delete/2로 접속
  • 메인페이지에서 삭제할 권한이 없습니다 .확인

 

 

 <댓글 구현하기 - 소셜로그인>

- disqus 가입 후 -> create a new site -> Basic -> universal code -> configure -> complete setup

 

- 프로젝트로 돌아와 터미널에 

$pip install django.disqus #물론, 가상환경이 켜져 있는 상태에서 진행
$pip freeze > requirements.txt #환경업그레이드 되었으니, 다시한번 요구 사항에 적어줌 

 

- config/settings.py -> installed app 에 django.contrib.sites . disqus 추가

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'photo.apps.PhotoConfig',
    'accounts.apps.AccountsConfig',
    'django.contrib.sites',
    'disqus',
]


#맨 밑에 추가
DISQUS_WEBSITE_SHORTNAME = 'expinstagram'
SITE_ID = 1

 

$python manage.py migrate 는 필수!

 


잠깐, 여기서 

from six.moves.urllib_parse import urlencode ModuleNotFoundError: No module named 'six'

에러가 발생했다. 확인해보니 내가 낮은 버전의 장고와 pip를 사용하고 있었기 때문에 에러가 난 것 같은데, 

 

터미널에 

$pip install six

를 입력해주면 해결 가능 하다..

 

그리고 오류가 난 저부분 경로를 타고 들어가서 disqus/__init__.py를 살펴보고

urlopen과 urlencode 부분을 저렇게 바꿔주면 오류가 발생하지 않는다.

from six.moves.urllib_parse import urlencode
from six.moves.urllib_request import urlopen

<화면에 보이는 위치 설정해주기>

- photo/templates/photo/photo_detail.html

  1. 수정일 =>  { load disqus_tags }
  2. 보일 위치에 { disqus_show_comments } 
        <div class="card" >
            <img src=" {{ object.image.url }}" class="card-img-top" alt="...">
            <div class="card-body">
                <h5 class="card-title">{{ object.author }}</h5>
                <p class="card-text"> {{ object.text }} </p>
            </div>
            <ul class = "list-group list-group-flush">
                <li class = "list-group-item">생성일 : {{ object.created }}</li>
                <li class = "list-group-item">
                    {% load disqus_tags %}
                    <div class = "card-body">
                        {% disqus_show_comments %}
                    </div>

                </li>
            </ul>

 

 

<확인해보기>

- python manage.py runserver 후 

- 게시글 detail 에 들어가서 댓글달기를 아래와 같이 성공하면 완성!

 

 


기다리시는 분들이 있으시네요 ㅜㅜ 하지만... 회사 업무에 치여.. 장고관련 프로젝트는 중단된 상태입니다 ㅜㅜ 

반응형

'Web > Django' 카테고리의 다른 글

[Django] Instagram 클론 코딩(4)  (0) 2020.03.25
[Django] Instagram 클론 코딩(3)  (0) 2020.03.20
[Django] Instagram 클론 코딩(2)  (0) 2020.03.19
[Django_심화]4 - (1)pagination  (0) 2020.03.18
[Django] Instagram 클론 코딩(1)  (0) 2020.03.17
반응형
  • success url을 get_absolute_url 로 연동시켜보기
  • account 앱 만들기
    • 로그인/ 로그아웃 기능 구현하기
    • 템플릿에 로그인/ 로그아웃 보이도록 하기
    • 로그아웃 되었을 때는 create 및 로그아웃이 안 보이도록 구현하기 (분기)

 

<success url을 get_absolute_url로 연동시켜보기>

  1. models.py에 absolute url 입력해주기
  2. 상세페이지로 이동하도록 absolute_url 설정 (나중에 사용)
  3. 이후에 views에서 return super가 나오게 되면 자동적으로 absolute_url 이 실행

/photo/models.py 

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

# Create your models here.

class Photo(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user')
    #장고에서 구현하는 user 불러와 photo를 fk로 연결
    text = models.TextField(blank=True)
    image = models.ImageField(upload_to = 'timeline_photo/%Y/%m/%d')
    #timeline_photo 폴더에 연, 월, 일 만들어 사진 저장
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return "text : " + self.text
        #admin 사이트 화면 표시 구현 

    class Meta:
        ordering = ['-created']
        #ordering 정렬

    def get_absolute_url(self):
        return reverse('photo:detail', args = [self.id])
        #상세페이지로 이동하도록 absolute_url 설정, views 에서 return super가 나오게되면 absolute가 실행

 

 

<account 앱 만들기>

  1. python manage.py startapp accounts
  2. setting.py에 install_app에 accounts 추가
    1. 추가해주지 않으면 템플릿 및 models을 찾을 수가 없다.
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'photo.apps.PhotoConfig',
    'accounts.apps.AccountsConfig'
]

 

- 로그인, 로그아웃 기능 만들기

  1. 장고에는 login view 와 logoutview를 기본적으로 제공
    • 기본적으로 loginview나 logoutview는 registration/login.html, logout.html을 자동 렌더링 하게 되어 있다
  2. 고로, urls.py 에서 login.html과 logout.html를 구현해 바로 전달해줄 계획
  3. 로그인 성공했을시 접속할 링크 구현

/account/urls.py

from django.urls import path
from django.contrib.auth.views import LoginView, LogoutView

app_name = "accounts"

urlpatterns = [
    path('login/', LoginView.as_view(template_name = 'accounts/login.html'), name = 'login'),
    path('logout/', LogoutView.as_view(template_name = 'accounts/logout.html'), name = 'logout'),
]

 

 

  4. main url (config /urls.py) 에 accounts url 연결

    - /config/urls.py 

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('photo.urls')),
    path('accounts/', include('accounts.urls'))
]

 

 

  5. templates 구현하기 

     - login.html (accounts/templates/accounts/login.html)

{% extends 'base.html' %}

{% block title %}
{% endblock %}

{% block content %}
<div class="row">
    <div class="col"></div>
    <div class="'col-6">
        <div class="alert alert-primary" role="alert">
            Please enter your login information. </div>
        <form action="" method="post">
            {% csrf_token %}
            {{form.as_p}}
            <input type="submit" value="login" class = 'btn btn-outline-primary'>
        </form>
    </div>
    <div class="col"></div>

</div>
{% endblock %}

 

 

     - logout.html (accounts/templates/accounts/logout.html)

{% extends 'base.html' %}

{% block title %}
{% endblock %}

{% block content %}
<div class="row">
    <div class="col"></div>
    <div class="'col-6">
        <div class="alert alert-primary" role="alert">
            로그아웃 되었습니다. </div>
        <a href="{% url 'accounts:login'%}" class="btn btn-outline-success">login 하기</a>
    </div>
    <div class="col"></div>

</div>
{% endblock %}

 

6. 로그인 성공 시, 접속할 링크 구현해주기 

- config/settings.py 에 리다이렉트 url 추가

LOGIN_REDIRECT_URL ='/' 

 

여기까지 해서 python manage.py runserver 해보고 accounts/login , accounts/logout 들어가보고 확인해보기!

 

 

<템플릿에 로그인 / 로그아웃 보이도록 하기>

  1. base.html의 navbar를 추가하여 로그인 / 로그아웃 구현
  2. url 링크 연동 
  3. 로그인 했을 때만, 사진 업로드 및 logout 보이게 하기(분기 사용)

-layout/base.html

    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav mr-auto">
        <li class="nav-item active">
          <a class="nav-link" href="{% url 'photo:index' %}">Home<span class="sr-only">(current)</span></a>
        </li>
        {% if user.is_authenticated %}
        <li class="nav-item">
          <a class="nav-link" href="{% url 'photo:create'%}">사진 올리기</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="{% url 'accounts:logout'%}">Logout</a>
        </li>
        {% else %}
        <li class="nav-item">
          <a class="nav-link" href="{% url 'accounts:login'%}">Login</a>
        </li>
        {% endif %}

 

 

runserver 해보면 유저가 인증받은 상태일 때만 사진올리기와 Logout이 가능하며, 

유저가 인증받지 않았다면 navbar 부분에 Login만 보이게 해 놓았다.

 

이런식으로 나오면 login / logout 기능 구현 완료 

 

반응형

'Web > Django' 카테고리의 다른 글

[Django] Instagram 클론 코딩(5)  (0) 2020.03.26
[Django] Instagram 클론 코딩(3)  (0) 2020.03.20
[Django] Instagram 클론 코딩(2)  (0) 2020.03.19
[Django_심화]4 - (1)pagination  (0) 2020.03.18
[Django] Instagram 클론 코딩(1)  (0) 2020.03.17
반응형
  • Template 만들기
  • 사진이 화면에 업로드 할 수 있도록 만들기

<Template 만들기>

1. 템플릿 만들기

  • photo/templates/photo 폴더 생성

    • 그 아래에 html 파일들 생성해주기

    • 많은 앱들 생성 시, 겹치는 문제 해결 위해 photo 폴더를 다시 생성해주고 html 파일 생성

  • photo/layout 폴더 생성

    • 그 아래에 layout.html 파일 생성

    • 모든 html 파일 안에 들어갈 base가 될 layout.html 생성

    •  setting에 templates 경로에 layout 경로 찾을 수 있도록 지정 

layout/base.html 경로

/instagram/config/settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'layout')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

 

 

<Bootstrap 사용>

  1. Bootstrap · The most popular HTML, CSS, and JS library in the world. 에 접속합니다.

  2. base.html 파일에 부트스트랩을 사용하기 위해 필요한 css 및 javascript 링크를 넣습니다.

    1. css 파일은 head의 마지막에 위치

    2. javascript 파일은 body의 마지막에 위치

#css 
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> 

#javascript

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

  3. 마음에 드는 포맷을 찾으면 해당 포맷을 적용할 html에 넣어줍니다.

  • 지금은 base.html에 넣어줍니다.(body tag내 위치)

  • 파일들을 적절하게 수정하고 클릭시 연결할 링크를 연결해줍니다.

    1. <a href = " " > 내 위치

    2. { url 'photo:index' } 등과 같이 연결

  • block content를 만들어서 다른 템플릿에서 추가할 수 있도록 구성합니다.

  • Home / 사진 올리기

  • 아래쪽에 { block content } { endblock } 을 구성

  • Dstagram 제목 설정 및 Home, 사진 올리기 url 연결하기

/photo/layout/base.html

<!DOCTYPE html>
<html lang = "en">


<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %}</title>

    #css 
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> 

</head>

<body>

#navbar
<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="#">Dstagram</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
  
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav mr-auto">
        <li class="nav-item active">
          <a class="nav-link" href="{% url 'photo:index' %}">Home<span class="sr-only">(current)</span></a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="{% url 'photo:create'%}">사진 올리기</a>
        </li>
      </ul>
      <form class="form-inline my-2 my-lg-0">
        <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
        <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
      </form>
    </div>
  </nav>

  {%block content%}

  {% endblock %}

  #javascript bootstrap
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

</body>



</html>

 

<Photo 템플릿 수정>

/photo/templates/photo/photo_list.html

  • for문으로 models를 통해 생성된 object의 list를 하나씩 

  • img의 경로는 image.url 을 통해 연결

{% extends 'base.html' %}
{% block title %}
{% endblock %}

{% block content %}

<div class='container'>
    <div class="row">
        <div class="col"></div>
        <div class="col-5">
            {% for object in object_list %}

            <div class="card">
                <div class="card-header">
                    {{ object.author }}
                </div>
                <img src=" {{ object.image.url }}" class="card-img-top" alt="...">
                <div class="card-body">
                    <p class="card-text"> {{ object.text }} </p>
                </div>
                <ul class="list-group list-group-flush">
                    <li class="list-group-item">생성일 : {{ object.created }}</li>
                    <li class="list-group-item">수정일 : {{ object.updated }}</li>
                </ul>
                <div class="card-body">
                    <a href="{% url 'photo:detail' object.id %}" class="card-link">댓글달기</a>
                    <a href="{% url 'photo:update' object.id %}" class="card-link">수정하기</a>
                    <a href="{% url 'photo:delete' object.id %}" class="card-link">삭제하기</a>

                </div>

            </div>
            {% endfor %}
        </div>

        <div class = "col"></div>
    </div>
</div>
{% endblock %}

 

/photo/templates/photo/photo_create.html

  • form을 활용하여 입력창 생성

  • post형식으로 전송

  • { csrf_token }을 작성하여 보안문제 해결

  • {{ form.as_p }} 를 적어줌으로서 한줄씩 띄우면서 create의 field에 등록된 것을 가지고 옴

- html의 폼형태에서 입력할 때는 모두 text 파일로 인식 ->  따라서 form에 image 파일이라는 것을 인식시켜줌!

 

{% extends 'base.html' %}
{% block content %}
<div class="row">
    <div class="col"></div>
    <div class="col-12 col-xl-8 col-md-8">
        <div class="alert alert-primary" role="alert">
            모든 부분을 다 채워넣어주세요!!
        </div>
        <form action="" method="post" enctype='multipart/form-data'>
            {% csrf_token %}

            {{form.as_p}}
            <input type="submit" value="Upload" class="btn btn-primary">
        </form>
    </div>
    <div class="col"></div>
</div>
{% endblock %}

 

 

/photo/templates/photo/photo_detail.html

  • 해당 컨텐츠의 작성자 / text를 보여준다.

  • 수정과 삭제를 할 수 있도록 링크 연결

{% extends 'base.html' %}
{% block title %}
{% endblock %}

{% block content %}
<div class="row">
    <div class="col"></div>
    <div class="col-12 col-xl-4 col-md-4">
        <div class="card" style="">
            <img src=" {{ object.image.url }}" class="card-img-top" alt="...">
            <div class="card-body">

                <h5 class="card-title">{{ object.author }}</h5>
                <p class="card-text"> {{ object.text }} </p>
            </div>
            <div class="card-body">
                <a href="{% url 'photo:update' object.id %}" class="card-link">수정하기</a>
                <a href="{% url 'photo:delete' object.id %}" class="card-link">삭제하기</a>
            </div>
        </div>
    </div>

    <div class="col"></div>
</div>
{% endblock %}

 

 

/photo/templates/photo/photo_delete.html

  • form 형식으로 구현

  • 한번 더 실제로 지울지를 확인하는 페이지로 구성

  • form 을 다시 보여줄 필요는 없다

{% extends 'base.html' %}
{% block content %}

<div class="alert alert-primary" role="alert">
  진짜로 {{object}}를 지우실 건가요?
</div>

<form action="" method ="post">
    {% csrf_token%}
    <input type="submit" value="Delete OK">
</form>
{% endblock %}

 

 

/photo/templates/photo/photo_update.html

  • create와 비슷하게 구현

  • 폼 형태로 입력할 수 있도록 구현

{% extends 'base.html' %}
{% block content %}
<div class="row">
    <div class="col"></div>
    <div class="col-12 col-xl-8 col-md-8">
        <form action="" method="post">
            {% csrf_token %}
            {{form.as_p}}
            <input type="submit" value="Create">
        </form>
    </div>
    <div class="col"></div>
</div>
{% endblock %}

 

 

 

<화면에 사진 업로드 할 수 있도록 만들기>

- photo을 생성하는데 user_id가 확인되어야 하므로 views.py 수정

/photo/views.py

class PhotoCreate(CreateView):
    model = Photo
    fields = ['text', 'image']
    template_name_suffix = '_create'
    success_url = '/'


    def form_valid(self, form):
        form.instance.author_id = self.request.user.id
        if form.is_valid():
            form.instance.save()
            return redirect('/')
        else:
            # 올바르지 않다면
            return self.render_to_response({'form': form})

 

- 메인 화면에 사진이 오류가 뜸

  1. static으로 url 지정
  2. photo/urls.py 수정
urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)

 

 

아래와 같이 나온다면 성공!

 

반응형

'Web > Django' 카테고리의 다른 글

[Django] Instagram 클론 코딩(5)  (0) 2020.03.26
[Django] Instagram 클론 코딩(4)  (0) 2020.03.25
[Django] Instagram 클론 코딩(2)  (0) 2020.03.19
[Django_심화]4 - (1)pagination  (0) 2020.03.18
[Django] Instagram 클론 코딩(1)  (0) 2020.03.17
반응형
  • views 설계하기
  • URL 연결하기

<Views 설계>

  1. class형 뷰의 generic view를 이용하여 구현

  2. ListView/CreateView/UpdateView/DeleteView/DetailView 구현

(instagram/photo/views.py)

from django.shortcuts import render
from django.views.generic.list import ListView
from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.views.generic.detail import DetailView
from .models import Photo

# class형 뷰의 generic view를 이용하여 구현
# ListView/CreateView/UpdateView/DeleteView/DetailView 구현

class PhotoList(ListView):
    model = Photo
    template_name_suffix = '_list'
#PhotoList:가장 메인에서 보여줄 로직 / 모델을 불러와서 데이터를 활용할 것이라고 기제
class PhotoCreate(CreateView):
    model = Photo
    fields = ['text', 'image']
    template_name_suffix = '_create'
    success_url = '/'

# PhotoCreate:
# 모델을 활용하는데생성할 때 채워야 할 필드 확인이후 연결될 템플릿 이름은 Photo_create 일 것이다.
#성공하면 메인 페이지로 돌아가도록 연결(이후 url로 연결)

class PhotoUpdate(UpdateView):
    model = Photo
    fields = ['text', 'image']
    template_name_suffix = '_update'
    success_url = '/'

#photoUpdate : Create와 동일

class PhotoDelete(DeleteView):
    model = Photo
    template_name_suffix = '_delete'
    success_url = '/'

class PhotoDetail(DetailView):
    model = Photo
    template_name_suffix = '_detail'

#PhotoDelete와 PhotoDetail
#삭제와 상세페이지는 특별한 로직이 필요하지 않음
#템플릿과 연결을 잘 시킬 수 있도록

 

 

<URL 연결하기>

 

- Photo URL 연결하기

  1. photo의 urls.py 생성 ( instagram/photo/urls.py)

  2. URL 작성하기

from django.urls import path
from .views import PhotoList, PhotoDelete, PhotoDetail, PhotoUpdate, PhotoCreate

app_name = "photo"
#app_name 설정을 통해 namespace(이름공간)확보
#다른 앱들과 url pattern 이름이 겹치는 것을 방지하기 위해 사용한다.

urlpatterns = [
    path("create/", PhotoCreate.as_view(), name = 'create'),
    path("delete/<int:pk>/", PhotoDelete.as_view(), name = 'delete'),
    path("update/<int:pk>/", PhotoUpdate.as_view(), name = 'update'),
    path("detail/<int:pk>/", PhotoDetail.as_view(), name = 'detail'),
    path("", PhotoList.as_view(), name = 'index'),
]

 

 

- Config URL 연결하기

  1. config의 url.py에 작성(instagram/config/urls.py)

  2. 2차 URL을 설정한 이후에 가장 기본이 되는 URL과 연결시켜준다.

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('photo.urls')),
]

include를 통해서 해당 주소로 연결시켜준다.

  1. 가장 기본 주소가 들어오면 photo의 url로 연결

반응형

'Web > Django' 카테고리의 다른 글

[Django] Instagram 클론 코딩(4)  (0) 2020.03.25
[Django] Instagram 클론 코딩(3)  (0) 2020.03.20
[Django_심화]4 - (1)pagination  (0) 2020.03.18
[Django] Instagram 클론 코딩(1)  (0) 2020.03.17
[Django_심화]3 - viewset & router  (2) 2019.11.03
반응형

API 서버에서 Pagination 

-> 사용자가 게시판의 전체 글 목록 요청 시 /
    수 많은 데이터들을 한번에 전달 한다면 데이터를 받는 사용자는 많은 데이터를 감당할 수 없을 것 

 

API 서버에서 Pagination을 사용하는 이유

- > 하나의 Request 만으로 처리하기 어려운 레코드들을 여러 Request로 나누어 전송하기 위해 사용

 

DRF에서의 Pagination

  1. PageNumberPagination 
  2. LimitOffsetPagination 
  3. CursorPagination
  4. CustomizedPagination

1, 2, 3 = Django restFramework 에서 제공하는 Pagination class

4 = 직접 Pagination class를 직접 커스터마이징 해서 사용하는 방법 

 

흔히, PageNumberPagination 을 Default로 설정하고

추가적인 페이지 설정 부분만 CustomizedPagination 을 사용

 

 

프로젝트 기본 세팅

1. Django Project 생성

2. 경로 이동 및 앱 생성

3. settings.py에 앱 등록

4. models.py 작성 및 admin.py 연결

5. makemigrations 및 migrate 진행

6. serializer.py 생성 및 작성

from .models import Post
from rest_framework import serializers


class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"

 

7. views.py 작성

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


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

 

 

8. urls.py 작성

- 앱 내부의 urls.py

from rest_framework.routers import DefaultRouter
from django.urls import include, path
from .views import PostViewSet

router = DefaultRouter()
router.register('post', PostViewSet)

urlpatterns = [
    path('', include(router.urls))
]

 

- 프로젝트 폴더의 urls.py 

from django.contrib import admin
from django.urls import path, include
import post.urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(post.urls)),
]

 

DRF로 Pagination 구현하기

DEFAULT_PAGINATION_CLASS는 None이며 PAGE_SIZE 또한 NONE이다.

 

settings.py에 Pagination관련 설정하기

- DEFAULT_PAGINATION_CLASS에 Pagination 클래스를 설정하고, PAGE_SIZE에 한 번에 가져올 레코드의 수 설정

 

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':
        'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
}

 

 

Pagination 적용 후 테스트

아래와 같이 한 번에 10개씩의 레코드만 가져오는 것을 확인할 수 있다.
총 데이터의 개수인 count와 이전 10개의 레코드 주소인 previous
다음 10개의 레코드를 위한 next또한 RESPONSE에 생겼다.

 

 

 

유의 사항

Pagination을 사용할 때는 반드시 레코드를 정렬해 사용해야 한다.
=> views.py를 아래와 같이 id로 정렬하도록 수정해준다.

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


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all().order_by('id')
    serializer_class = PostSerializer

 

CustomizedPagination 클래스 작성하기

PageNumberPagination을 상속받는 클래스를 선언하고
클래스 내부에 page_size변수에 원하는 레코드의 수를 지정한 후
적용할 모델의 ViewSet의 pagination_class에 클래스를 지정하면 된다.

 

...
from rest_framework.pagination import PageNumberPagination

class MyPagination(PageNumberPagination):
    page_size = 6

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all().order_by('id')
    serializer_class = PostSerializer
    pagination_class = MyPagination

지정해준 page 수 대로 잘 정리되는 것을 볼 수 있다

 

코드 클린

1. pagination.py 생성 및 클래스 옮기기

from rest_framework.pagination import PageNumberPagination


class MyPagination(PageNumberPagination):
    page_size = 6

 

2. views.py 에 추가, 사용

from .models import Post
from .serializer import PostSerializer
from .pagination import MyPagination
from rest_framework import viewsets


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all().order_by('id')
    serializer_class = PostSerializer
    pagination_class = MyPagination
반응형

'Web > Django' 카테고리의 다른 글

[Django] Instagram 클론 코딩(3)  (0) 2020.03.20
[Django] Instagram 클론 코딩(2)  (0) 2020.03.19
[Django] Instagram 클론 코딩(1)  (0) 2020.03.17
[Django_심화]3 - viewset & router  (2) 2019.11.03
[Django_심화]2 - Rest, Serializer  (0) 2019.11.03
반응형
https://github.com/syujisu/Django_instagram

 

초기 설정

photo app 생성

모델 설계

<초기 설정>

- 가상환경 생성
$ python -m venv myvenv

- 가상환경 실행
$ source myvenv/Scripts/activate
(myvenv) 

 

-장고 설치 
$ pip install django 

 

- 프로젝트 시작
$ django-admin startproject config
(myvenv) 


- DB 초기화 (project 상단 이름을 바꿔줍니다 -> instagram)

$ cd instagram

$ python manage.py migrate

 

 

 

<photo app>

- app 생성(/instagram)

$ python manage.py startapp photo

 

- settings.py에 installed app에 photo 추가해주기

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'photo.apps.PhotoConfig'
]

 

- photo model 생성(/instagram/photo/models.py)

class Photo(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user')
    #장고에서 구현하는 user 불러와 photo를 fk로 연결
    text = models.TextField(blank=True)
    image = models.ImageField(upload_to = 'timeline_photo/%Y/%m/%d')
    #timeline_photo 폴더에 연, 월, 일 만들어 사진 저장
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return "text : " + self.text
        #admin 사이트 화면 표시 구현 

    class Meta:
        ordering = ['-created']
        #ordering 정렬

 

-settings.py 에서 media 루트 설정 (/instagram/config/settings.py)

STATIC_URL = '/static/'



MEDIA_URL = '/res/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
#MEDIA_URL 을 설정하고 ROOT를 잡아주어서 저기로 사진이 저장되도록 한다.

 

-모델 설계 이후 migration

  1. Image파일을 업로드 하기 위해 pip install Pillow를 해줘야 한다.
    1. pip install Pillow
  2. python manage.py makemigrations photo
  3. python manage.py migrate

-admin site 등록(/instagram/photo/admin.py)

from django.contrib import admin
from .models import Photo
# Register your models here.

admin.site.register(Photo)

 

- 확인해보기

$ python manage.py createsuperuser
Username (leave blank to use 'user'): admin
Email address: 282532@naver.com
Password: 
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
  1. python manage.py runserver (기본값은 8000 포트 입니다)
  2. 해당 링크의 /admin으로 이동하여 글 등록해보기

admin 사이트
사진 올려서 이렇게 나오면 완성 :)

반응형

'Web > Django' 카테고리의 다른 글

[Django] Instagram 클론 코딩(2)  (0) 2020.03.19
[Django_심화]4 - (1)pagination  (0) 2020.03.18
[Django_심화]3 - viewset & router  (2) 2019.11.03
[Django_심화]2 - Rest, Serializer  (0) 2019.11.03
[Django_심화]1 - 준비운동  (0) 2019.11.02
반응형

1. Views of DRF

모델 기반의 viewset을 작성했었다.

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

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

ViewSet

-> View(CRUD)를 설계하는 가장 간단한 방법

 

 

2. APIView

         -> 장점 : 자신의 마음대로 코드를 작성할 수 있다.

         -> 단점 : CBV의 장점인 중복되는 코드 제거를 하지 못한다.

         

         -> Response : 클라이언트가 요청한 형태로 콘텐츠를 렌더링 해준다.

         -> Status : Http 에서 제공하는 상태 코드는 숫자이지만, REST 프레임워크에서는 
              문자 형태의 식별자를 제공하여 상태를 좀더 잘 구분할 수 있도록 한다.

 

         -> APIView를 상속해서 view를 설계할 때는 
             status 와 Response를 import 하여 Response과정을 작성

         -> APIView를 상속해서 만든 CBV의 내부 함수들은 필요로 하는  HTTP Method 이름으로 갖는다.

         -> 어떠한 status를 받고 response를 전달할지 직접 정하는 것이 apiview를 상속받은 cbv

class SnippetDetail(APIView):
    def get(...):
        ...

    def put(...):
        ...

    def delete(...):
        ...
        
#SnippetDetail 클래스에 필요한 method는 GET, PUT, DELETE

##실습해보기

  • django-admin startproject <project-name>
  • cd <project-name>
  • python manage.py startapp <app-name>
  • settings.py에 App, rest_framework추가
  • models.py에 모델 작성 및 migrate
class Post(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
  • serializer.py 생성 및 작성
from .models import Post
from rest_framework import serializers


class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"
  • views.py 작성
    1) 필요한 모듈 추가(apiview를 상속받은 cbv를 작성하기 위해 아래 모듈 추가)
from CBV.models import Post
from CBV.serializer import PostSerializer
from django.http import Http404
from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView

           2)객체들 목록 가져오는 PostList 작성

             - post 메서드는 다수의 post 객체를 반환하는 메서드 

             - 다수의 객체를 직렬화 하기위해서는 PostSerializer 에 many = True를 넘겨야 한다

            - post 메서드는 새로운 객세 생성하는 함수

           - 저장 성공시 상태코드 201(created) / 반환 실패시 400(bad request)반환  

class PostList(APIView):
    def get(self, request):
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)

        return Response(serializer.data)

    def post(self, request):
        serializer = PostSerializer(data=request.data)

        if serializer.is_valid():
            serializer.save()

            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

          3) 객체의 상세 정보를 받는 PostDetail 작성

              - 각각의 함수에 pk 값을 인자로 받는다

              - get_object 함수 : get_object_or_404 함수와 동일한 기능을 한다

              - PostList 에서 작성한 함수들과 비슷한 방식으로 작동

class PostDetail(APIView):
    def get_object(self, pk):
        try:
            return Post.objects.get(pk=pk)
        except Post.DoesNotExist:
            return Http404

    def get(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(post)

        return Response(serializer.data)

    def put(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(post, data=request.data)

        if serializer.is_valid():
            serializer.save()

            return Response(serializer.data)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        post = self.get_object(pk)
        post.delete()

        return Response(status=status.HTTP_204_NO_CONTENT)
  • urls.py 작성하기
    1) App 내부의 urls.py 
        - rest_framework.urlpatterns 의 format_suffix_patterns 모듈 추가 
        - views.py 가져와 <ClassName>.as_view() 형식으로 작성
from django.urls import path, include
from rest_framework.urlpatterns import format_suffix_patterns
from . import views

urlpatterns = [
    path("post", views.PostList.as_view()),
    path("post/<int:pk>", views.PostDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

       2) 프로젝트 폴더의 urls.py 
           - include 를 사용해 앱의 urls.py 를 가져와 PATH 설정

from django.contrib import admin
from django.urls import path, include
import CBV.urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(CBV.urls)),
]

실행하면 -> http://127.0.0.1:8000/post 로 젒고하면 결과가 잘 보인다.

 


3. Mixins

  • APIView를 상속해 CBV 구현시 문제점
    • 여러가지 모델 사용 시 비슷한 논리 코드 반복
      -> 불필요한 반복을 제거하기 위해 mixins 사용 
      -> 상속을 통해 불필요한 반복을 제거 

##실습해보기

1. views.py 작성하기 전

  • django-admin startproject <project-name>
  • cd <project-name>
  • python manage.py startapp <app-name>
  • settings.py에 App, rest_framework추가
  • models.py에 모델 작성 및 migrate

2. views.py 작성

   1) 필요한 모듈 추가하기 

from .models import Post
from serializer import PostSerializer
from rest_framework import generics
from rest_framework import mixins

  2) PostList 클래스 작성

  • apiview를 상속해 작성한 PostList 클래스와 동일한 기능
  • mixins 의 ListModelMixin 과 CreateModelMixin , generics의 GenericAPIView 상속
  • 사용할 모델의 쿼리셋과 Serializer 클래스 등록
  • 객체 목록을 가져오는 GET 메서드는 ListModelMixin 에 구현된 list 함수 사용
  • 객체 생성하는 POST 메서드는 CreateModelMixin 에 구현된 create 함수 사용
class PostList(mixins.ListModelMixin,
               mixins.CreateModelMixin,
               generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

   3) PostDetail 클래스 작성하기  

  • PostList를 작성한 것과 동일하게 작성
  • 각 메서드에 필요한 mixins 모듈 상속하고 , serializer, 쿼리셋 등록 후 각 HTTP Method 함수 작성
class PostDetail(mixins.RetrieveModelMixin,
                 mixins.UpdateModelMixin,
                 mixins.DestroyModelMixin,
                 generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

3. urls.py 작성

- 앱 내부에 urls.py 생성 및 작성

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from . import views

urlpatterns = [
    path("post", views.PostList.as_view()),
    path("post/<int:pk>", views.PostDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

- 프로젝트 폴더 내부의 urls.py 에 include 

from django.contrib import admin
from django.urls import path, include
import post.urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(post.urls)),
]

실행하고 http://127.0.0.1:8000/post 에 접속하면 정상 작동한다

 

4. Generic CBV

<분류>

  • 기반 뷰(Base View): 뷰 클래스를 생성하고 다른, 제너릭 뷰의 부모 클래스가 되는 기본 제너릭 뷰
    • view : 최상위 부모 제너릭 뷰 클래스
    • TemplateView : 주어진 템플릿으로 렌더링
    • RedirectView : 주어진 URL로 리다이렉트
  • 제너릭 보기 뷰(Generic Display View): 객체의 목록 또는 하나의 객체 상세 정보를 보여주는 뷰
    • DetailView : 조건에 맞는 하나의 객체 출력
    • ListView : 조건에 맞는 객체 목록 출력
  • 제너릭 수정 뷰(Generic Edit View): 폼을 통해 객체를 생성, 수정, 삭제하는 기능을 제공하는 뷰
    • FormView : 폼이 주어지면 해당 폼 출력
    • CreateView : 객체 생성하는 폼 출력
    • UpdateView : 기존 객체 수정하는 폼 출력
    • DeleteView : 기존 객체 삭제하는 폼 출력
  • 제너릭 날짜 뷰(Generic Date View): 날짜 기반 객체의 연/월/일 페이지로 구분해 보여주는 뷰
    • YearArchiveView : 주어진 연도에 해당하는 객체 출력
    • MonthArchiveView : 주어진 월에 해당하는 객체 출력
    • DayArchiveView : 주어진 날짜에 해당하는 객체 출력
    • TodayArchiveVIew : 오늘 날짜에 해당하는 객체 출력
    • DateDetailView : 주어진 연, 월, 일 PK(OR 슬러그)에 해당하는 객체 출력

<제너릭 뷰 오버라이딩>

  • 속성 변수 오버라이딩
    • model : 기본 뷰(View, Template, RedirectView) 3개를 제외하고 모든 제너릭 뷰에서 사용한다.
    • queryset : 기본 뷰(View, Template, RedirectView) 3개를 제외하고 모든 제너릭 뷰에서 사용한다. queryset을 사용하면 model 속성은 무시된다.
    • template_name : TemplateView를 포함한 모든 제너릭 뷰에서 사용한다. 템플릿 파일명을 문자열로 지정한다.
    • context_object_name : 뷰에서 템플릿 파일에 전달하는 컨텍스트 변수명을 지정한다.
    • paginate_by  : ListView와 날짜 기반 뷰(예, YearArchiveView)에서 사용한다. 페이징 기능이 활성화 된 경우 페이지당 출력 항목 수를 정수로 지정한다.
    • date_field : 날짜 기반 뷰(예, YearArchiveView)에서 사용한다. 이 필드의 타입은 DateField 또는 DateTimeField이다.
    • form_class : FormView, CreateView, UpdateView에서 폼을 만드는데 사용할 클래스를 지정한다.
    • success_url : FormView, CreateView, UpdateView, DeleteView에서 폼에 대한 처리가 성공한 후 리디이렉트할 URL 주소이다.
  • 메소드 오버라이딩
    • def get_queryset() : 기본 뷰(View, Template, RedirectView) 3개를 제외하고 모든 제너릭 뷰에서 사용한다. 디폴트는 queryset 속성을 반환한다. queryset 속성이 지정되지 않은 경우 모델 매니저 클래스의 all() 메소드를 호출해 QuerySet 객체를 생성해 반환한다.
    • def get_context_data(**kwargs) : 뷰에서 템플릿 파일에 넘겨주는 컨텍스트 데이터를 추가하거나 변경하는 목적으로 오버라이딩한다.
    • def form_valid(form)

<모델을 지정하는 방법 3가지>

  1. model 속성 변수 지정
  2. queryset 속성 변수 지정
  3. def get_queryset() 메소드 오버라이딩

##실습

1. views.py 작성하기 전

  1. django-admin startproject <project-name>
  2. cd <project-name>
  3. python manage.py startapp <app-name>
  4. settings.py에 App, rest_framework추가
  5. models.py에 모델 작성 및 migrate
  6. serializer.py생성 및 작성

2. views.py 작성하기

   1) 필요한 모듈 추가하기

       - 사용할 모델, serializer , rest_framework 의 generics 추가

from .models import Post
from .serializer import PostSerializer
from rest_framework import generics

    2) PostList 클래스 작성하기

     -  generics의 ListCreateAPIView 는 mixins에서 사용한 list 함수와 create 함수를 가지고 있는 class 이다.

class PostList(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    3) PostDetail 클래스 작성하기

     - genercis의 RetrieveUpdateDestoryAPIView 또한 mixins에서 사용한 함수들과 동일한 기능

class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

3. urls.py 작성하기

- 앱 내부에 urls.py 생성 및 작성

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from . import views

urlpatterns = [
    path("post", views.PostList.as_view()),
    path("post/<int:pk>", views.PostDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

- 프로젝트 폴더 내부의 urls.py 에 include 

from django.contrib import admin
from django.urls import path, include
import post.urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(post.urls)),
]

-> 실행 결과 : http://127.0.0.1:8000/post로 접속하면 잘 작동하는 것을 확인할 수 있다.

 

5. ViewSet

rest_framework의 viewsets.py내부에는 4개의 클래스가 존재한다.

  • ViewSet
  • GenericViewSet
  • ReadOnlyModelViewSet
  • ModelViewSet

모두 간단하게 구성되어 mixins와 GenricViewSet, APIView등을 묶는 역할을 한다.

 

ReadOnlyModelViewSet

- 상속 받은 클래스

  • mixins.RetrieveModelMixin
  • mixins.ListModelMixin
  • GenericViewSet

상속받은 클래스로 보아 해당 viewset 역할은 retrieve 함수와 list 함수 기능을 가지고 있다

- 기능 : 특정 객체 가져오는 역할(retrieve)  / 객체 목록을 가져오는 역할(list)

 

ReadOnly를 수행하는 viewSet 이다

 

아래와 같이 작성하면

기존 postlist, postDetail 클래스의 기능을 따로 작성하지 않아도 ReadOnlyModelViewSet을 상속받아 사용할 수 있다.

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

class PostViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Post.objects.all()
    serializer_class = 

ModelViewSet

모델과 Serializer 을 사용할 건지 등록한다

permission_classes는 action을 수행할 수 있는 권한을 설정하는 변수

  • IsAuthenticatedOrReadOnly : 권한이 없을 경우 ReadOnly
  • IsOwnerOrReadOnly : 소유자가 아닐 경우 ReadOnly
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

class SnippetViewSet(viewsets.ModelViewSet):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [
        permission.IsAuthenticatedOrReadOnly,
        IsOwnerOrReadOnly,
    ]

    @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

-> customAPI 함수는 default로 GET 메서드로 처리된다. / 다른 메서드로 처리하고 싶다면 action의 format 인자로 지정 가능

 

%action decorator

@는 decorator장식자라고 부른다.
ViewSet을 이용하면 CRUD는 간단하게 구현되나 다른 논리들을 구현할 때
@action를 사용한다. 개발자가 임의로 View를 설계할 수 있도록 해준다.

 

%renderer_classes

Response를 어떤 형식으로 Rendering시킬 것인가를 정의

  • renderer.JSONRenderer
  • renderer.BrowsableAPIRenderer
  • renderer.StaticHTMLRenderer
  • TemplateHTMLRenderer

등 여러가지가 있다. 기본 설정은 JSONRenderer와 BrowsableAPIRenderer다.

 

##실습해보기

viewSet 적용하기

1. views.py 작성하기 전

  • django-admin startproject <project-name>
  • cd <project-name>
  • python manage.py startapp <app-name>
  • settings.py에 App, rest_framework추가
  • models.py에 모델 작성 및 migrate
  • serializer.py생성 및 작성

2. views.py 작성하기

    1) 필요한 모듈 추가

from .models import Post
from .serializer import PostSerializer
from rest_framework import viewsets
from rest_framework import renderers
from rest_framework.decorators import action
from django.http import HttpResponse

    2) ReadOnlyModelViewSet을 상속해 클래스 작성하기

      - viewsets의 ReadOnlyModelViewSet을 상속받아 작성한다. ReadOnlyModelViewSet을 상속받았으므로 읽기만 가능하다.

class PostViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

3. urls.py 작성하기

- post 앱 내부의 urls.py

from rest_framework.routers import DefaultRouter
from django.urls import include, path
from .views import PostViewSet

router = DefaultRouter()
router.register('', PostViewSet)

urlpatterns = [
    path('post', include(router.urls))
]

- 프로젝트 폴더의 urls.py 

from django.contrib import admin
from django.urls import path, include
import post.urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(post.urls)),
]

>>실행결과 

-http://127.0.0.1:8000/로 접속하면 아래와 같은 결과를 확인할 수 있다.
DefaultRouter를 사용했기 때문에 PATH가 ""일때도 화면이 나온다.

 

-http://127.0.0.1:8000/post로 접속하면 사용가능한 HTTP Method가
ReadOnlyModelViewSet을 상속받았으므로 GET밖에 없다.

 

4. ModelViewSet 상속해 클래스 작성하기

viewsets의 ModelViewset 상속받은 PostViewSet 클래스 작성

사용할 모델과 serializer을 등록하는 것은 기존과 동일

추가적으로 @action 데코레이터를 이용해 custom api 작성

get 방식으로 @action Custom API Test를 띄우는 highlight 함수 작성

 

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

    @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        return HttpResponse("@action Custom API Test")

post로 처리하고 싶다면 @action(method=['post']) 와 같이 작성

 

>>실행결과

http://127.0.0.1:8000/post로 접속하면 사용가능한 HTTP Method가 GET외에도 POST가 추가된 것을 확인

 

Custom API 테스트

>http://127.0.0.1:8000/post/2/와 같이 Detail페이지로 접속하면 Extra Actions 스크롤 메뉴가 생긴 것을 확인할 수 있다.

스크롤 메뉴에서 highlight 버튼 클릭 시 아래와 같은 결과가 생성된다

6. Router

viewSet 복습

- ReadOnlyModelViewSet

  • retrieve()와 list()의 기능

- ModelViewSet

  • list(), create(), retrieve(), update()
    partial_update(), destroy()의 기능

path 

viewSet을 하나의 path 함수로 처리할 수 있을까?

하나의 path로 ListView, DetailView 의 CRUD가 모두 처리 가능할까??

 

===> 불가능하다 / PATH를 하나로 묶어줄 방법이 필요하다

 

as_view

as_view({'http_method' : '처리할 함수'})

예시

path = PostViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})

urlpatterns = [
    path('', path)
]

router : 관례적인 url 맵핑 단계를 자동으로 구현해주는 방식

 

<사용 방법>

1. rest_framework의 router에서 DefaultRouter 추가

from rest_framework.router import DefaultRouter

2. DefaultRouter 객체 생성 및 등록

-> URL Prefix : 127.0.0.1:8000/post와 같은 URL에서 post부분

router = DefaultRouter()
router.register(<URL Prefix>, <View Logic>)

3.  urlpatterns에 router의 urls 추가

urlpatterns = [
    path('', include(router.urls)),
]
반응형

'Web > Django' 카테고리의 다른 글

[Django_심화]4 - (1)pagination  (0) 2020.03.18
[Django] Instagram 클론 코딩(1)  (0) 2020.03.17
[Django_심화]2 - Rest, Serializer  (0) 2019.11.03
[Django_심화]1 - 준비운동  (0) 2019.11.02
[6주차]Form  (0) 2019.03.06
반응형

1.Rest Architecture 

-REST란? : Representational State Transfer  / “자원을 이름으로 구분하여 상태를 전송하는 방법”

 

-Rest 설계 필요 충분 조건

   1. SERVER – CLIENT 

   2. STATELESS #클라이언트의 이전 상태를 기록하지 않는 연결방식을 취함 

   3. CACHE #캐쉬 처리기능 

   4. UNIFORM INTERFACE #일관된 인터페이스 

  • Resource가 URI로 식별이 되어야한다.
  • CRUD를 HTTP메세지로 수행 가능해야한다.
  • 메세지는 자체로 모든 것이 설명 가능해야한다.
  • 어플리케이션의 상태는 하이퍼링크를 이용해 전이되야한다.(HATEOAS)

   5. LAYERED SYSTEM #다칭적인 구성

   6. CODE-ON-DEMAND #JS처럼 서버와 같이 원격지에서 보낸 코드로 메소드를 실행시킬 수 있는 것

 

-API란? Application Program Interface / Request, Response로 오가는 구조화된 데이터

-Rest API  : REST의 설계방식을 잘 따르는 API / 대부분의 API는 rest를 잘 따르지 않는다 / Json은 모든 태그가 만들어져 있지않고, 만든 이가 정의하기 나름이기 때문에

 

    -> 필요한 이유?

         : 백엔드, 프론트엔드 분리 / 독립적인 네트워크 향상 가능 / 클라이언트 플랫폼 다양화 지원 가능

 

Django VS Django Rest Framework

  Django Djnago Rest Framework
만들어지는 것 웹 어플리케이션 RESTful API
전달하는 데이터 HTML Form(HTML, CSS, JS) JSON 데이터
데이터를 담는 곳  Form / ModelForm Serializer/ ModelSerialier
model로 부터 Field 읽어온다
유효성 검사

 

Form VS Serializer

 

 

Serializer vs ModelSerialier

Form과 ModelForm이 존재하듯이 Model만 작성할 줄 알면
더 간편하게 Serializer를 사용할 수 있게하는 ModelSerialier에 대해서 공부한다.
ModelSerialier는 쿼리셋과 모델 직렬화를 알아서 해준다.

 

2.Serializer 실습 

가상환경 생성

$python -m venv myvenv

 

필요 패키지 설치

$pip install djangorestframework

$pip install django

 

django 프로젝트 생성

$django-admin startproject rest

-> 생성 후 cd rest 로 폴더 이동

 

django app 생성

$ python manage.py startapp post

 

settings.py에 앱 추가(장고 프레임워크도 등록)

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

url 관리

   -> 앱 폴더 내부에 urls.py 생성(계층적 url 관리 위해 분할) ##아니 나는 urls.py를 완성하지 못하면 마이그레이트가 안된다.. 이거먼저!

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter() #ROUTER 객체 생성
router.register('post', views.PostViewSet) #ROUTER 객체의 REGISTER 함수(첫번째 인자 = URL, 함수)

urlpatterns = [
    path('', include(router.urls))
]

#rest_framework는 ROUTER을 사용해 URL을 결정한다
#CRUD 기반으로 URL을 설정한다

  

    -> 프로젝트 폴더 urls.py 에 모듈 추가

from django.contrib import admin
from django.urls import path, include
import post.urls


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(post.urls)),
]

앱 폴더의 models.py 에 모델 클래스 정의 (post/models.py)

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
# Create your models here.

앱 폴더 내부에 serializer.py 생성 및 작성 (어떤 클래스의 어떤 필드를 사용할 것인지)

from .models import Post
from rest_framework import serializers

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['title', 'body'] 
        # 모든 필드를 사용할 경우 '__all__' 사용 가능

views.py 작성하기

-> rest_framework 의 viewsets 모듈과 작성한 모델serializer 추가

 

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

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
Django Rest Framework는 Class base View 이기에 
view로서 class를 작성한다
viewsets의 ModelViewSet을 상속받는다.

 

##ROUTER

▪ ViewSet에는 여러가지 메소드(함수)들이 있다 

      ex) retrieve() , destroy(), …

▪ 이 모든 것을 하나의 path()로 처리해 주는 것은 불가능하다.

▪ Path함수를 묶어준다 == path함수의 두번째 인자로 오는 함수를 묶는다 

     -> Path(요청을 처리할 url, 요청을 인자로 받아 처리할 함수, namespace)

 

#실행 결과(api 서버 보기)

1. api root : urls.py 에 작성된 DefaultRouter 에 의해 만들어진 기본적인 환경 / 처음 보여지는 화면은 GET 요청을 할 수 있는 URL이 보여진다.

 

2.POST API

-request : 127.0.0.1/8000/post

 

-response

 

post 후 post/1로 들어가면 allow 부분에 put이 들어간 것을 볼 수 있음

각 객체에 pk 값 부여하기 

-> serializer.py 파일의 PostSerializer 을 수정한다

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'body'] 

새로 고침 후 보면 id 값을 직접 확인할 수 있는 것을 볼 수 있다. 

id 값 : 각 인스턴스에 붙는 고유 번호

Read Only 설정하기

 -> serializer.py 파일의 PostSerializer을 수정한다

 -> read_only_fields 에 read_only 속성을 적용할 필드를 tuple에 추가한다

 -> 수정 시 read_only 부분이 true로 바뀌는 것을 볼 수 있다.

 -> write_only부분도 같은 방법으로 수정하면된다!

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"
        read_only_fields = ("title",)

반응형

'Web > Django' 카테고리의 다른 글

[Django] Instagram 클론 코딩(1)  (0) 2020.03.17
[Django_심화]3 - viewset & router  (2) 2019.11.03
[Django_심화]1 - 준비운동  (0) 2019.11.02
[6주차]Form  (0) 2019.03.06
[5.5주차]Faker  (0) 2019.03.05

+ Recent posts