[ Architecture, Technology ,Web ] SSO(Single Sign On) 그리고 SAML에 대해

이미지
이번 프로젝트 내부에서 어쩌다보니  유저 인증 관련 업무를 담당하게 되었고, 해야하는 업무는 내부에 사용했던 적이 없던  새로운 개발 플랫폼에서  SSO의 프로토콜 중  SAML을 이용해 앱의 인증을 구현해야만 했다. SSO를 생각해본적 조차 없는 상황에 이를 새로운 개발 플랫폼에 도입해야 했기 때문에 많은 시행착오를 겪었으나 구현에 성공하였으며 덕분에 SSO에 대한 전반적인 지식을 쌓을 수 있었다. 이번에는 그러한 과정에서 나온 지식들과 경험을  공유하고자 한다. SSO에 대한 정의 먼저 사전적 정의 부터 살펴보자. 다만, 기술적인 용어다보니 자주 사용하는 옥스포드 사전에 정의를 찾을 수 없기 때문에  검색으로 찾을 수 있는 정의를 몇 가지 살펴보고 교차 검증을 해보자. 첫 번째 정의를 살펴보자. Single sign-on (SSO) is an identification method that enables users to log in to multiple applications and websites with one set of credentials.  SSO는 웹사이트에서 한 번의 인증(one set of credentials)으로 복수의 어플리케이션에 로그인 할 수 있는 인증(identification) 방법(method) 이다. 두 번째는 위키피디아의 정의이다. Single sign-on (SSO) is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems. SSO는 독립적이지만 연관되어있는 몇몇 소프트웨어에 대해 하나의 ID로 로그인을 할 수 있도록 하는 인증 구조(scheme) 세부 설명에 조금 차이가 있어 보이지만 전체적인 틀은 매우 비슷해 보인다.  몇 가지 포인트가 되는 단어를 추출해 이를 연결해보자면 아래와 같은 의미를 산출 할 수 있다. 독립적이지만 연관되어 있

[ Django, Python ] mozilla 튜토리얼 예제로 살펴보는 Django 분석 ⑧ : Working with Forms (Form으로 작업하기 )

HTML Form


아래와 같은 간단한 HTML Form이 있다고 가정해보자.


위의 Form을 나타내는 코드는 아래와 같다.

<form action="/team_name_url/" method="post">
    <label for="team_name">Enter name: </label>
    <input id="team_name" type="text" name="name_field" value="Default name for team.">
    <input type="submit" value="OK">
</form>
type 속성은 어떤 종류의 위젯이 화면에 표시될지,
nameid 속성은 JavaScript/ CSS/ HTML의 필드를 식별하는 데,
value는 화면에 처음 표시될 때의 값을 정의 한다.

여기서 for 속성은 관련된 inputid 값을 가지고 있다.

submit type을 가지고 있는 input 태그는 
기본적으로 버튼으로 표시되는데,
버튼을 누름으로써 해당 Form안에 있는 데이터들이 전송 된다.

보내지는 도착지는 action, 
보내는 방법은 method에 정의되어 있다.

method에는 POST와 GET이 데이터로 들어갈 수 있는데

POST은 사이트 간 요청 위조 공격에 비교적 안전하기 때문에
서버의 DB가 변경될 수 있는 경우에 일반적으로 사용하며

GET은 탐색과 같이 서버의 DB가 변경되지 않는 경우에 사용 된다.

Django는 프레임워크 답게
HTML 작성, 데이터 유효성 검사 등의 기본적인 포맷을 제공한다.

Django 폼 처리 프로세스


Django Form 처리(handling) 프로세스는 
3가지 프로세스로 볼 수 있다.

첫째, 브라우저 또는 사용자로 부터 
처음 요청(Request)이 들어왔을 경우,

디폴트 form을 보내준다.
여기서 디폴트 form이란 웹 애플리케이션의 홈 페이지를 말한다.

예를 들어 튜토리얼의 웹 애플리케이션이라면

이 페이지에 해당된다.

이 경우 프로세스 다이어그램에 파란색 선에 해당한다.

둘째, 처음 요청(Request)은 아니지만
유효성 검사에서 통과하지 못했을 경우이다.

유효성 검사에 해당되는 것은 
form에 아이디나 패스워드를 입력했는지,
패스워드가 옳바른지 등이 포함된다.

예를 들어 튜토리얼의 웹 애플리케이션이라면


위와 같이 옳바른 패스워드가 아니기 때문에 
로그인에 실패한 경우가 해당된다.

위의 프로세스 다이어그램에 노란색 선에 해당한다.

셋째, 처음 요청(Request)도 아니며,
유효성 검사에도 통과한 경우 이다.

예를 들어 튜토리얼의 웹 애플리케이션이라면

로그인에 성공한 페이지가 된다.

위의 프로세스 다이어그램에 주황색 선에 해당한다.

책 대여 갱신 Form과 함수 기반 View


Django의 Form 처리(handling) 프로세스를 이해했다면 
다음으로 도서관 직원이 대여 기간을 갱신 할 수 있는 페이지를 추가해보자.


이번에 함수 기반의 View와 Form클래스를 이용할 것이며 
추가할 것은 아래와 같다.

1) 날짜를 입력할 수 있는 form 및 필드 생성
2) 필드의 디폴트 값은 현재 날짜로 부터 3주
3) 너무 긴 대여기간을 입력하지 않도록 유효성 체크

① Form 선언하기


Form을 선언하는 방법은 Model을 선언했을 때와 매우 유사하며,
Form 파일은 애플리케이션 디렉토리 안의
즉, 이번 예제의 경우에는 locallibrary/catalog/ 경로의 forms.py에 저장 된다.


from django import forms
    
class RenewBookForm(forms.Form):
    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
forms.py는 디폴트 파일이 아니기 때문에
파일을 생성하고 위의 사진과 같이 코드를 추가하자.

・Form 필드


Form 안에는 대여 갱신날을 입력할 renewal_date라는 필드가 있는데,
이 필드는 Renewal date: 라는 Label로 디폴트 값 없이 
빈 칸으로 HTML에 표시 된다.

또한 DateField라는 데이터형을 가지며,
Django의 input_formats의 형식을 따르기 때문에
따로 Casting 할 필요는 없다.

그리고 Django의 기본 Widget: DateInput을 사용 했다.

또한 Model의 Field들과 유사한 점이 많기 때문에 
자주 사용되는 인수도 비슷하다.

자주 사용되는 인수는 위의 사진과 같다.

・유효성 체크


Django에서 어떤 필드의 유효성을 체크하는 가장 쉬운 방법은
해당 필드의 clean_<fieldname>() 메소드를 OverRiding하는 것이다.

예를 들어
입력된 renewal_date 값이 현재로 부터 4주 이후 사이에 있는지는
clean_renewal_date()를 아래와 같이 구현하면 된다.

import datetime

from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _

class RenewBookForm(forms.Form):
    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")

    def clean_renewal_date(self):
        data = self.cleaned_data['renewal_date']
        
        # Check if a date is not in the past. 
        if data < datetime.date.today():
            raise ValidationError(_('Invalid date - renewal in past'))

        # Check if a date is in the allowed range (+4 weeks from today).
        if data > datetime.date.today() + datetime.timedelta(weeks=4):
            raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))

        # Remember to always return the cleaned data.
        return data
여기서 주목해야 할 점은
self.cleaned_data['renewal_date']를 이용하여 
데이터를 가져오고 
데이터의 수정 여부와 관계없이 
함수가 끝나면 데이터를 반환한다는 것과

입력값이 지정한 범위를 벗어날 경우
ValidationError 에러를 발생시키고,
유효하지 않은 입력값일 때 Form에 에러 페이지를 표시한다.

Form 및 필드의 유효성 검사에 관한 좀 더 자세한 내용은
아래의 공식 도큐먼트를 참고하길 바란다.

② URL Configuration 작성하기 


View를 작성했다면 
이제는 URL을 추가해보자.


urlpatterns += [   
    path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'),
]
locallibrary/catalog/ 경로의 urls.py 파일에 
위의 코드를 추가 하자.

위의 코드는 /catalog/book/<bookinstance id>/renew/ 라는 URL의 요청이 왔을 시
view.py에 있는 renew_book_librarian()라는 객체를 참조하게 된다.

③ View 작성하기


이전에 Django 폼 처리(handling) 프로세스에서 언급한 대로 
Form View첫 번째 요청(Request)될 경우 
디폴트 Form을 표시해야 한다.

책 대여 갱신 프로세스는 
서버의 데이터 값을 변경하기 때문
관례상 POST 요청(Request)방법을 사용한다.

locallibrary/catalog/ 경로의 views.py 파일에 
아래와 같이 코드를 추가하자.


import datetime

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse

from catalog.forms import RenewBookForm

def renew_book_librarian(request, pk):
    book_instance = get_object_or_404(BookInstance, pk=pk)

    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = RenewBookForm(request.POST)

        # Check if the form is valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
            book_instance.due_back = form.cleaned_data['renewal_date']
            book_instance.save()

            # redirect to a new URL:
            return HttpResponseRedirect(reverse('all-borrowed') )

    # If this is a GET (or any other method) create the default form.
    else:
        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
        form = RenewBookForm(initial={'renewal_date': proposed_renewal_date})

    context = {
        'form': form,
        'book_instance': book_instance,
    }

    return render(request, 'catalog/book_renew_librarian.html', context)
이 부분에 대해서는 조금 자세하게 살펴보고 넘어가자.

먼저 이전에 언급했었던 Form 처리(Handling) 프로세스를 
다시 한번 보고 시작해보자.



첫번째로

def renew_book_librarian(request, pk):
    book_instance = get_object_or_404(BookInstance, pk=pk)
인수는 requestpk로서 
request는 클라이언트 쪽에서 요청했을 시 받은 데이터를,
그리고 pk는 해당 책의 고유 번호일 것이다.

book_instanceget_object_or_404(BookInstance, pk=pk)를 넣음으로써
변수에 넣기 전에 get_object_or_404()라는 함수가 한번 실행된다.

BookInstance 객체 찾기에 실패하면 
404 에러를 응답(Response)로 클라이언트에 보내게 될 것이다.

BookInstance 객체가 존재한다면 
참조해 book_instance 변수에 객체로서 저장한다.

이 과정은 처리 프로세스의 노란색 선에 해당된다.

# If this is a GET (or any other method) create the default form.
    else:
        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
        form = RenewBookForm(initial={'renewal_date': proposed_renewal_date})

    context = {
        'form': form,
        'book_instance': book_instance,
    }

    return render(request, 'catalog/book_renew_librarian.html', context)
요청은 대개 POSTGET요청(Request)이 들어오게되는데,
위의 코드는 GET이거나 다른 메소드에서 참조했을 때
디폴트 form을 만들어낸다.

또한 리턴할 때 render()함수를 이용해 생성한 
디폴트 Formbook_instance 객체context 변수에 담아 반환 한다.

이 과정은 Form 처리(Handling) 프로세스에서 파란색 선에 해당된다.

book_instance = get_object_or_404(BookInstance, pk=pk)

    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = RenewBookForm(request.POST)

        # Check if the form is valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
            book_instance.due_back = form.cleaned_data['renewal_date']
            book_instance.save()

            # redirect to a new URL:
            return HttpResponseRedirect(reverse('all-borrowed') )
    
    context = {
        'form': form,
        'book_instance': book_instance,
    }

    return render(request, 'catalog/book_renew_librarian.html', context)
요청을 POST로 받았을 경우 
이전 forms.py에 추가했던 Class RenewBookForm에 
요청(Request) 때 받았던 데이터를 넣고 
객체를 생성했을 때, 2가지의 경우가 발생할 수 있다.

생성한 Form이 유효할 수도 있고,
Form이 유효하지 않을 수도 있다. 

그렇기 때문에 is_valid() 메소드를 활용해 이를 검증한다.

먼저 유효할 경우이다.

book_instance = get_object_or_404(BookInstance, pk=pk)

    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = RenewBookForm(request.POST)

        # Check if the form is valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
            book_instance.due_back = form.cleaned_data['renewal_date']
            book_instance.save()

            # redirect to a new URL:
            return HttpResponseRedirect(reverse('all-borrowed') )
    
    
유효할 경우 
renewal_date라는 변수안에 들어있는 데이터를 
저장할 데이터 형에 맞게 클렌징하고
서버 DB의 due_back(반납일)에 이를 넣고 저장해준다.

그 다음 all-borrowed라는 URL로 리다이렉트 해준다.

book_instance = get_object_or_404(BookInstance, pk=pk)

    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = RenewBookForm(request.POST)

       
    context = {
        'form': form,
        'book_instance': book_instance,
    }

    return render(request, 'catalog/book_renew_librarian.html', context)
유효하지 않을 때를 포함한 그 외의 경우에는 
Form과 수정하지 않은 book_instance 객체를 
context 변수에 담아 반환한다.

이 과정은 처리(Handling) 프로세스에서 주황색 선에 해당한다.

그 다음은 사서만이 이 작업을 할 수 있도록
권한을 부여해야 한다.

import datetime

from django.contrib.auth.decorators import permission_required
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse

from catalog.forms import RenewBookForm

@permission_required('catalog.can_mark_returned')
def renew_book_librarian(request, pk):
    """도서관 사서에 의해 특정 BookInstance를 갱신하는 뷰 함수."""
    book_instance = get_object_or_404(BookInstance, pk=pk)

    # POST 요청이면 폼 데이터를 처리한다
    if request.method == 'POST':

        # 폼 인스턴스를 생성하고 요청에 의한 데이타로 채운다 (binding):
        book_renewal_form = RenewBookForm(request.POST)

        # 폼이 유효한지 체크한다:
        if book_renewal_form.is_valid():
            # book_renewal_form.cleaned_data 데이타를 요청받은대로 처리한다(여기선 그냥 모델 due_back 필드에 써넣는다)
            book_inst.due_back = book_renewal_form.cleaned_data['renewal_date']
            book_inst.save()

            # 새로운 URL로 보낸다:
            return HttpResponseRedirect(reverse('all-borrowed') )

    # GET 요청(혹은 다른 메소드)이면 기본 폼을 생성한다.
    else:
        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
        book_renewal_form = RenewBookForm(initial={'renewal_date': proposed_renewal_date})
   
    context = {
        'form': book_renewal_form,
        'book_instance': book_instance,
    }

    return render(request, 'catalog/book_renew_librarian.html', context)
위와 같이 권한을 부여하는 코드를 추가하자.

다만,  이전에 사용되었던 권한이 부여되어 있는데
이는 작업을 간단하게 하기 위함이다.

이렇게 된다면 2개의 권한이 묶이게 되기 때문에
원래라면 따로따로 분리해주어야 하는 것이 옳다.


④ Template 작성 하기


다음은 템플렛을 작성해보자.


{% extends "base_generic.html" %}

{% block content %}
    <h1>Renew: {{ book_instance.book.title }}</h1>
    <p>Borrower: {{ book_instance.borrower }}</p>
    <p{% if book_instance.is_overdue %} class="text-danger"{% endif %}>Due date: {{book_instance.due_back}}</p>
    
    <form action="" method="post">
        {% csrf_token %}        
        {{ form.as_table }}        
        <input type="submit" value="Submit">
    </form>
{% endblock %}

catalog/templates/catalog/ 경로에 
book_renew_librarian.html 파일을 만들고 위의 코드를 추가하자.

Form 코드의 경우 일반적인 경우에는 
action을 지정해주어서 버튼을 누름과 동시에
action에 지정된 URL로 이동하면서 데이터를 전달 하지만,
비워두면 현재 페이지로 다시 이동하면서 데이터를 전달하게 된다.

여기서 {% csrf_token %}는 
사이트간 요청 위조(Cross-site request forgery)을 방지하는 코드이다.

위의 코드와 같이{{ form.as_table }}을 사용하면 
각 필드가 테이플 행으로 렌더링 되는데

이외에도 {{ form.as_ul }}을 사용하여 목록 항목(list itme)으로 렌더링 하거나
{{ form.as_p }}를 사용하여 단락(paragraph)으로 렌더링 할 수 있다.

또한 Form 속성을 인덱싱하여 각 부분을 제어할 수 있는데
예컨데, renewal_date 필드에 경우 아래와 같이 접근할 수 있다.


⑤ Template에 목록 추가


그 다음으로 지금 까지 작성했던 페이지로 
들어가기 위한 목록을 추가해보자.


  <!-- bookinstance_list_borrowed_librarian.html -->
  {% extends "base_generic.html" %}

  {% block content %}
      <h1>All Borrowed books</h1>

      {% if bookinstance_list %}
      <ul>

        {% for bookinst in bookinstance_list %}
        <li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
          <a href="{% url 'book-detail' bookinst.book.pk %}">{{bookinst.book.title}}</a> ({{ bookinst.due_back }})
          - {{ bookinst.borrower }} -
          {% if perms.catalog.can_mark_returned %}
            <!-- We can mark a BookInstance as returned. -->
            <!-- Perhaps add code to link to a "book return" view here. -->
            <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a>
          {% endif %}
        </li>

        {% endfor %}
      </ul>

      {% else %}
        <p>There are no books borrowed.</p>
      {% endif %}
  {% endblock %}
locallibrary/catalog/templates/catalog/ 경로에
bookinstance_list_borrowed_librarian.html 파일로 들어가
빨간색 박스의 코드를 추가 하자.

기능 테스트


정상적으로 작동한다면 


위의 빨간색 박스와 같이 Renew라는 하이퍼 링크가 표시된다.

이 Renew들 중에 한 가지를 선택해보면

View에서 작성한대로 Renewal date에 
현재 시간을 기준으로 3주 이후의 날짜가 
자동적으로 입력됨을 알 수 있다.

이 상태로 submit 버튼을 누르면

위와 같이 반납 날짜가 수정된 것을 확인할 수 있다.


ModelForms 만들기


Django에는 ModelForm 이라는 클래스를 지원해주는데,
이 ModelForm은 필드를 매핑할 Form이 필요할 경우 유용하게 사용될 수 있다.


from django.forms import ModelForm

from catalog.models import BookInstance

class RenewBookModelForm(ModelForm):
    def clean_due_back(self):
       data = self.cleaned_data['due_back']
       
       # Check if a date is not in the past.
       if data < datetime.date.today():
           raise ValidationError(_('Invalid date - renewal in past'))

       # Check if a date is in the allowed range (+4 weeks from today).
       if data > datetime.date.today() + datetime.timedelta(weeks=4):
           raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))

       # Remember to always return the cleaned data.
       return data

    class Meta:
        model = BookInstance
        fields = ['due_back']
        labels = {'due_back': _('Renewal date')}
        help_texts = {'due_back': _('Enter a date between now and 4 weeks (default 3).')}
위의 코드는 위에서 작성했던 
함수 기반 class RenewBookForm()와 정확히 동일한 기능을 가진다.

유효성 검사에 대해서는 
함수 기반 Forms와 비슷하게
clean_field_name()이라는 함수로 정의 가능하며,
유효하지 않은 값에 대해서는 ValidationError 예외를 발생 시킨다. 

다른 점이라고 한다면 renewal_date가 아니라 
Model에서 정의했던 필드 이름인 due_back을 사용한다는 것이다.

제너릭 편집 View(Generic Editing Views)

위의 함수 기반 Form은 매우 일반적인 패턴을 나타낸다.

Django는 생산성 향상을 위해
View 때와 마찬가지로 
Model을 기반으로 View를 생성, 편집 및 삭제하기 위한 
제너릭 편집 View를 지원 한다.

이는 View의 동작을 처리 할 뿐만 아니라
Model에서 자동으로 ModelForm을 생성 한다. 

이번에는 제너릭 편집 View를 활용해서
Author에서 레코드를 생성, 편집, 삭제 하는 기능을 추가해보자.

①  View


locallibrary/catalog/경로의 views.py에 


from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy

from catalog.models import Author

class AuthorCreate(CreateView):
    model = Author
    fields = '__all__'
    initial = {'date_of_death': '05/01/2018'}

class AuthorUpdate(UpdateView):
    model = Author
    fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death']

class AuthorDelete(DeleteView):
    model = Author
    success_url = reverse_lazy('authors')
위와 같은 코드를 추가하자.

코드에서도 알 수 있듯이 
추가, 수정, 삭제에 대해 각각
CreateViewUpdateViewDeleteView로 제너릭 편집 View를 정의했다.

Create와 Update의 경우 필드 또한 정의해주어야 하는데 
이는 field_name / value의 사전형 데이터를 사용하여
디폴트 값도 지정할 수 있다.

Delete의 경우는 필드를 정의할 필요가 없지만
그렇게 되면다면, Django에서 지정해야 할 
디폴트 값이 없기 때문에 삭제에 성공할 경우 
리다이렉트 할 URL을 정의해야 한다.

여기서는 reverse_lazy() 함수를 사용하는데 
이 함수는 reverse() 함수와 동일한 기능을 가지지만
조금 느리게 실행된다.

이는 클래스 기반 View 속성에 URL을 제공해야하기 때문이다. 

② Template


Create와 Update View는 Model의 이름을 따서 정의 된다.
기본적으로 Model_NAME 에 _form.html이 붙어야 한다.

locallibrary/catalog/templates/catalog/경로의
author_form.html 파일을 생성해


{% extends "base_generic.html" %}

{% block content %}
  <form action="" method="post">
    {% csrf_token %}
    <table>
    {{ form.as_table }}
    </table>
    <input type="submit" value="Submit">
  </form>
{% endblock %}
위와 같은 코드를 추가하자.

Delete View는 
model_name에 _confirm_delete.html으로 설정해야 한다.

위와 동일하게 
locallibrary/catalog/templates/catalog/경로에 
author_confirm_delete.html 파일을 생성하고


{% extends "base_generic.html" %}

{% block content %}

<h1>Delete Author</h1>

<p>Are you sure you want to delete the author: {{ author }}?</p>

<form action="" method="POST">
  {% csrf_token %}
  <input type="submit" value="Yes, delete.">
</form>

{% endblock %}
위와 같이 코드를 추가하자.

③ URL 구성


locallibrary/catalog/ 경로의
urls.py 파일에


from django.urls import path
from catalog import views


urlpatterns = [
    path('', views.index, name='index'),
    path('books/', views.BookListView.as_view(), name='books'),
    path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
    path('author/', views.AuthorListView.as_view(), name='authors'),
    path('author/<int:pk>', views.AuthorDetailView.as_view(), name='author-detail'),
    path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
    path('borrowed/', views.LoanedBooksByLlibrarianListView.as_view(), name='all-borrowed'),
    path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'),

    path('author/create/', views.AuthorCreate.as_view(), name='author_create'),
    path('author/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author_update'),
    path('author/<int:pk>/delete/', views.AuthorDelete.as_view(), name='author_delete'),
]
URL과 연결시키기 위해
위와 같은 코드를 추가하자.

페이지 테스트


그렇다면 이제 페이지가 
우리가 의도한대로 정상적으로 작동하는지 확인해보자.


위와 같은 화면이 나오며

위와 같이 데이터를 적당히 입력해 submit 버튼을 누르면

위와 같이 pk가 6으로 등록된 새로운 저자가 생성된다.

이 pk번호를 통해 직접 
라는 URL에 접속하면

위와 같은 페이지가 나오며,
Yes, delete 버튼을 누를 경우

해당 저자의 데이터가 삭제됨과 동시에
리다이렉트 됨을 확인할 수 있다.




이 블로그의 인기 게시물

[ Web ] 웹 애플리케이션 아키텍처 (Web Application Architecture)

[ Web ] 서버 사이드(Sever Side) ? 클라이언트 사이드(Client Side)? 1 [서론, 클라이언트 사이드(Client Side)]

[ Web ] 웹 애플리케이션? 웹 사이트?(Web Application? Web Site?)