해리의 데브로그

Django 14 - CRUD 구현 및 Django에 Bootstrap 입히기

|

1. 기본 설정

가상환경 및 기본설정

  • pyenv virtualenv 3.6.7 06_venv
  • pyenv local 06_venv
  • pip install django==2.1.8 : django 버전 설정
  • django-admin startproject insta .
  • python manage.py startapp posts
  • settings.pyINSTALLED_APPS & ALLOWED_HOSTS 설정
INSTALLED_APPS = [
    'posts',
    #'posts.apps.PostsConfig',]
ALLOWED_HOSTS = [] #기본값 입력

Model 설정

class Post(models.Model):
    content = models.TextField()
  • python manage.py makemigrations : 마이그레이션 초안 생성
  • python manage.py migrate : 설정된 스키마를 DB에 실제 적용

URL 경로 설정

  • 프로젝트 내 urls.py 설정
    • 어플리케이션 urls.py와 연결시키기 위해 include import 해옴
from django.urls import path, include

urlpatterns = [
    path('posts/', include('posts.urls')),]
  • 어플리케이션 내 urls.py 설정
    • views.py 내 함수 호출을 위해 import
    • 네임스페이스 posts 설정
from . import views

app_name = 'posts'

urlpatterns = [
    path('create/', views.create, name='create'),
]

기본 템플릿 설정

  • 프로젝트 폴더 / templates 폴더 / base.html
  • CDN 설정하기 (Bootstrap - CSS & JSS, fontawesome)
<body>
    {% block container %}
    {% endblock %}
</body>
</html>
  • Settings.py 내 기본 템플릿 경로 설정
'DIRS': [os.path.join(BASE_DIR, 'insta', 'templates')],

모델 폼 작성

  • 어플리케이션 폴더 내 forms.py 파일 생성
  • 장고 내 forms 및 모델 클래스 Post를 import 해옴
  • model에는 연동하고자 하는 모델 클래스를 저장
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['content']

MTV에 따라 Create 기능 구현

  • urls.py 내 url 경로 설정
  • views. py 내 가장 기본적인 템플릿으로 create 함수 생성
  • 앱 폴더 내 templates 폴더 생성 후, create.html 생성.
    • 기본적인 form 태그 양식에 따라 코드 작성


2. Django에 Bootstrap 입히기

django-bootstrap4 설치

  • pip install django-bootstrap4 : 외부 라이브러리 설치
  • settings.py 내 INSTALLED_APPS 내에 앱 추가
INSTALLED_APPS = [
    'bootstrap4',
]

base.html 수정

  • bootstrap 관련 CDN 코드 삽입
<head>
    <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>
    <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>

create.html 수정

  • {% load bootstrap4 %} 을 템플릿 상속을 위한 코드 사이에 넣어줘야 함.
  • {% bootstrap_form 템플릿 변수 %} : 부트스트랩을 적용한 템플릿 변수 양식
    • cf) {{ 템플릿 변수 }} : 기본 방식
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block container %}
<h1>New Post</h1>

<form method="post">
    {% csrf_token %}
     <!-- 기본방식 {{ post_form }}-->
     <!-- 부트스트랩 클래스 이용 -->
    {% bootstrap_form post_form %}
    <input type="submit" value="Submit"/>
</form>
{% endblock %}
  • 버튼 모양 수정
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
{% endbuttons %}

3. CRUD & Model Form 적용

1) Create 페이지 수정

  • views.py
from django.shortcuts import render, redirect
from .forms import PostForm
from .models import Post

def create(request):
    #POST 방식일 경우
    if request.method == 'POST':
        post_form = PostForm(request.POST)
        
        #모델 폼이 유효 할 경우
        if post_form.is_valid():
            post_form.save()
            return redirect('posts:list')
    
    #Get 방식일 경우
    else:
        post_form = PostForm()
    return render(request, 'posts/create.html', {'post_form': post_form})

2) list페이지 작성

  • views.py
from .models import Post

def list(request):
    posts = Post.objects.all()
    return render(request, 'posts/list.html'. {'posts':posts})
  • list.html
    • 세부 내용은 _post.html에 작성
    • {% include 'posts/_post.html' %}
{% extends 'base.html' %}
{% block container %}

<h1> Post List </h1>

<!-- 진자 템플릿 사용 -->
{% for post in posts %}
<!-- 세부 내용은 _post.html에 작성 -->
{% include 'posts/_post.html' %}

{% endfor %}
{% endblock %}
  • _post.html
    • 재사용성을 위해 파일을 분리시킴
    • 파일의 제목은 반드시 _ 로 시작할 필요는 없으나, _를 사용하는 것이 통상적인 룰.
    • 어플리케이션 폴더 - 템플릿 폴더 내 _post.html 파일 생성 후 코드 작성
    • bootstrap - card를 사용
<div class="card" style="width: 18rem;">
  <img src="..." class="card-img-top" alt="...">
  <div class="card-body">
    <p class="card-text"></p>
  </div>
</div>

3) Delete

  • urls.py
from django.urls import path
from . import views

app_name = 'posts'

urlpatterns = [
    path('<int:post_id>/delete/', views.delete, name='delete'),
]
  • views.py
def delete(request, post_id):
    #기본키를 뜻하는 pk=post_id를 써도 되고, 아니면 id를 써도됨. 
    post = Post.objects.get(id=post_id)
    post.delete()
    
    return redirect('posts:list')
  • _post.html
<div class="card" style="width: 18rem;">
    <!-- 장고 템플릿 문법을 사용하여 삭제하는 링크 설정 -->
    <a href="{% url 'posts:delete' post.id %}" class="btn btn-primary">Delete</a>
  </div>
</div>
  • get_object_or_404
    • url 주소로 이미 삭제 된 id를 이용하려는 접근할 경우 에러가 발생함.
    • 존재 하지 않는 id 값 기반의 url 주소로 접속할 경우, 에러가 발생하므로 Movie.objects.get 대신 get_object_or_404 를 사용하여 404 에러 메세지(Page not found)를 표시하게 코드 작성(사용자 친화적)
    • 사용을 위해 import 해올 것. from django.shortcuts import get_object_or_404
from django.shortcuts import render, redirect, get_object_or_404

def delete(request, post_id):
    # post = Post.objects.get(id=post_id)
    post = get_object_or_404(Post, id=post_id)
    post.delete()
    return redirect('posts:list')

4) update

  • urls.py
urlpatterns = [
    path('<int:post_id>/like/', views.like, name='like'),
]
  • views.py
    • create.html 을 그대로 사용
def update(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    
    if request.method == 'POST':
        postform = Postform(request.POST, instance=post)
        if postform.is_valid():
            postform.save()
            return redirect('posts:detail', post_id)
    
    else:
        postform = Postform(instance=post)
        return render(request, 'posts/create.html', {'postform':postform})
  • _post.html
    • 장고 템플릿 사용하여 수정 링크 추가
<a href="{% url 'posts:update' post.id %}" class="btn btn-info">Update</a>

Django 13 - Django Model Form

|

Model Form

Model 클래스를 이용하면, 직접 필드를 정의하고 위젯 설정을 할 필요 없이 Model과 연동되어 자동으로 폼 필드를 생성할 수 있음.

forms.py 작성

  • 메타 클래스: 우리가 만들 클래스에 대한 정보가 담긴 클래스 ( ArticleModelForm 클래스의 정보가 담김) (메타? 데이터를 설명하는 데이터)
    • model : 연동하고자 하는 모델 클래스를 저장
    • fields : 모델 클래스 내 연동하고자 하는 필드 명을 리스트의 형태로 저장
      • Model 클래스인 Article의 필드명 (title, content)를 바탕으로 input값을 입력함.
      • __all__ : 모든 필드를 가져와서 사용
#forms.py
from .models import Article

class ArticleModelForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content']        
        #cf) fields = '__all__'  => 모든 필드를 가져오겠다는 의미. 
        
#models.py
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

View 수정하기

  • 모델 폼 클래스 사용의 가장 큰 이점은 모델과 연동이 된다는 점임. 따라서 폼 클래스를 사용했던 것 처럼, 유효성 검증을 한 데이터를 일일이 입력할 필요 없음.
  • 사용자가 입력한 값이 request.POST 에 저장되어있으며, ArticleModelForm 인스턴스의 인자로 넘겨져 form 변수에 저장함. 이때, 이미 모델클래스와 연동이 되어 있으므로, 해당 데이터는 자동으로 DB에 생성됨.

모델 폼은 모델 클래스의 필드의 속성과 django의 ModelForm 이라는 클래스를 통해서 유효성 검증을 해주며, 에러메시지를 자동으로 생성해줌.

예) max_length가 100자로 제한된 경우에 글자를 입력할 경우, “이 값이 최대 100 개의 글자인지 확인하세요(입력값 108 자).” 라는 에러메세지가 뜨게 해줌.

  • 따라서, form.save() 를 통해 DB에 저장만 한 뒤, article 이라는 변수에 저장
  • article이라는 변수는 detail 페이지로 redirect 하기 위한 variable routing으로 사용
from .forms import ArticleModelForm 

def create(request):
    if request.method == 'POST':
        form = ArticleModelForm(request.POST)
        
        if form.is_valid():
            article = form.save()
            
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleModelForm()
    return render(request, 'create.html', {'form' : form})
  • 모델 클래스가 아니라 다른 필드명을 추가로 입력하여 오버라이드도 가능
    • 설정을 안할 경우, 모델 클래스의 필드를 그대로 가져와 사용함
#아래의 예시의 경우, title의 이름은 title이 아니라 "제목" 으로 화면에 표시됨.
class ArticleModelForm(forms.ModelForm):
    title = forms.CharField(label="제목")
    class Meta:
        model = Article
        fields = ['title', 'content']


Form vs Model Form

모델 폼과 폼의 가장 핵심적인 차이점은 모델(모델 클래스)과의 연동 유무임.

  • 폼은 모델과 연동이 되어있지 않기 때문에, request.POST 로 들어오는 input 에 대한 정보를 선별하여 각 변수에 저장하고 DB에 생성 및 저장을 해야함.
  • 그러나, 모델 폼의 경우, 모델과 연동이 되어있기 때문의 위의 과정을 거칠 필요 없이 DB에 저장만 하면됨.
#Form 클래스
if request.method == 'POST':
    form = ArticleModelForm(request.POST)
	
    if form.is_valid():
        title = form.cleaned_data.get('title')
        content = form.cleaned_data.get('content')
        article = Article.objects.create(title=title, content=content)  
        return redirect('articles:detail', article.pk)
    ...

#ModelForm 클래스
if request.method == 'POST':
    form = ArticleModelForm(request.POST)
	
    if form.is_valid():
        article = form.save()
        return redirect('articles:detail', article.pk)
    ...

모델 폼을 활용하여 Update 구현하기

  • 모델 폼을 사용하면 edit.html 을 따로 생성할 필요 없이 create.html 을 그대로 사용 하면 됨.
  • 함수의 반환 값으로 실행되는 html 문서는 글 생성과 수정을 모두 담고 있으므로, 파일명을 form.html으로 변경.
def update(request, article_id):
    article = Article.objects.get(pk=article_id)
    if request.method == 'POST':
        form = ArticleModelForm(request.POST, instance=article)
        
        if form.is_valid():
            article = form.save()
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleModelForm(instance=article)
        
    return render(request, 'form.html', {'form' : form})
  • 기본키를 바탕으로 데이터 가져와 article 이라는 변수에 저장함.
def update(request, article_id):
    article = Article.objects.get(pk=article_id)
  • 조건문 else => 기본 폼(수정을 위한 폼) 생성
    • 모델폼의 인스턴스인 ArticleModelForm 의 키워드인자 instance 의 값으로 article 을 넘겨주며, form 이라는 변수에 저장함.
    • 키워드인자 instancearticle 을 넘기게 되면, 기존의 값이 html 파일에 나타남. (article에 대한 정보를 모델 폼에 넘겨줌. 폼태그의 value와 같은 역할)
else:
    form = ArticleModelForm(instance=article) 
return render(request, 'form.html', {'form' : form})
  • 조건문 if => 폼데이터를 수정
    • request.POST 에는 사용자가 입력하는 input의 정보가 저장되어 있음
    • 키워드 인자 instance 에는 수정하고자 하는 데이터의 기본 정보가 담겨있음. 이를 통해 어떠한 객체에 대한 정보를 수정할껀지 그 주소를 알 수 있음.
    • 폼 인스턴스 ArticleModelForm() 의 인자로 request.POST & article 을 넘겨 form 변수에 저장함 => 모델 폼에서 정의된 구성에 따라 넘겨받은 정보를 분석하여 정리함.
    • 변수 form 을 DB 저장 한 후, 디테일 페이지로 redirect 시킴.
if request.method == 'POST':
    form = ArticleModelForm(request.POST, instance=article)

    if form.is_valid():
        article = form.save()
        return redirect('articles:detail', article.pk)

템플릿을 통해 HTML 폼 최종 수정

  • form 은 모델폼의 인스턴스로, 용도에 따라 아래와 같이 view에 정의되어있음
    • ArticleModelForm() : Create 실행 시
    • ArticleModelForm(instance=article) : update 실행 시
  • {{ form }} 으로 접근할 경우. 모든 input 값이 뭉쳐져있어서, 세부적인 커스터마이징에 어려움이 있음.
  • 뭉쳐진 form을 세분화 시키는 방법을 폼 인스턴스에서 제공함
    • form.non_field_errors : 필드에 국한되지 않은 포괄적인 전체 에러를 보여줄 때 설정
    • form.title.errors : title 필드의 error 메세지 표시
    • form.title.lable_tag : title 필드의 label(이름) 표시
    • form.title : title에 대한 input 상자 표시
  • form.as_p : 각각의 폼필드와 라벨을 p 태그로 감싸서 출력
  • form.as_table : 각각의 폼 필드와 라벨을 tr 태그로 감싸서 출력
  • form.as_ul : ul & li 태그로 감싸서 출력
{{ form.non_field_errors }}

<form method="POST">
    {% csrf_token %}
    <!--<input type="text" name="title" required/>-->
    <!--<input type="text" name="content" required/>-->

    <!--1. title -->
    <div>
        {{ form.title.errors }}  <!-- error message (ul, li tag) -->
        {{ form.title.label_tag }}  <!-- label tag -->
        {{ form.title }}  <!-- input tag -->
    </div>

    <!--2. content -->
    <div>
        {{ form.content.errors }}
        {{ form.content.label_tag }}
        {{ form.content }}
    </div>
    <input type="submit" value="Submit"/>
</form>

Django 12 - Django Form

|

Django Form

Django 를 사용하기 위한 기본적인 설정. 가상환경 설정 / 장고 설치 / 프로젝트 & 어플리케이션 생성 / settings.py 설정 / urls.py 설정 / models.py 설정 / migrate 설정

  • 모델 클래스 생성
    • 마이그레이션 적용
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

Create

  • views.py 내 create 함수 생성
    • pass 는 비어있는 코드로 아무런 역할을 하지 않음 (에러를 피하기 위해 사용)
    • 다른 언어(Java, C)의 경우, 조건문을 비워둬도 되나, python에서는 에러가 발생함
def create(request):
    if request.method == 'POST':
        pass
    else:
        return render(request, 'create.html')
  • url 경로 설정
#urls.py
from . import views

app_name = 'articles'

urlpatterns = [
    path('new/', views.create, name="create"),
]
  • Form 태그를 사용한 create.html 코드 작성
    • title 과 content의 내용이 반드시 들어가야하는 조건을 적용하기 위해 required 속성을 입력
<!-- create.html --> 

<h1>New Aricle </h1>
<form method="POST">
    {% csrf_token %}
    <input type="text" name="title" required/>
    <input type="text" name="content" required/>
    <input type="submit" value="Submit"/>
</form>
  • views.py 내 create 함수 재설정
    • Article.objects.create(title=title, content=content) : 데이터베이스에 input값 저장
from django.shortcuts import render, redirect
from .models import Article

def create(request):
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        article = Article.objects.create(title=title, content=content)  
        # 위의 코드는 아래의 코드와 동일함.
        # article = Article(title=title, content=content)
        # article.save()
        
        return redirect('articles:detail', article.pk)
    else:
        return render(request, 'create.html')

Detail

  • detail 함수 작성
def detail(request, article_id):
    article = Article.objects.get(pk=article_id)
    return render(request, 'detail.html' , {'article':article} )
  • url 경로 설정
path('<int:article_id>/', views.detail, name="detail")
  • detail.html 생성
<h1>article detail</h1>
<p> title: </p>
<p> content: </p>


Form 작성하기

Form 태그를 사용할 경우, input 값(제목, 내용 등)을 입력하지 않고 submit 할 경우, Error 가 발생함. 또한, 이 필드가 반드시 들어가야하는 필드라면, submit 입력이 막혀있어야함.

  • required를 넣으면, submit 입력을 막을 수 있음.

그러나, 개발자도구에서 HTML 코드를 직접 수정하여, 만약 required를 지워서 제출할 경우, 개발자가 설정한 방어책이 뚫릴 수 있게 된다. 따라서, 장고 서버 내부에서 검증을 해줘야므로, Form 태그가 아닌 Form class를 사용하여, 유효성 검증을 하거나, input을 자동으로 만드는 역할을 수행 할 수 있음.

forms.py 작성

  • 어플리케이션(articles) 내 생성
  • ArticleForm 클래스 생성
    • Django에서 구현해놓은 form 라이브러리를 import 해서 ArticleForm에 상속 시킴.
    • 상속받은 자식 클래스 ArticleFormform 라이브러리 내 메소드 및 설정들을 사용할 수 있게 됨. (models.py 에서 Django DB 내 models.Model을 상속해 와서, 기본설정만 하면 되는 것만 같은 맥락)
  • Form 클래스 내에서는 CharField 의 경우 max_length가 필수 인자가 아님.
from django import forms
class ArticleForm(forms.Form):
    title = forms.CharField(label="제목")
    content = forms.CharField(label="내용", widget=forms.Textarea(attrs={
        'rows':5,
        'cols':50,
        'placeholder':'내용을 입력하세요',
    }))
  • Form & input 태그의 속성값을 적용하여 추가적인 설정을 했던 것을 Form 클래스에서 설정 할 수 있음.따라서, input에 대한 추가적인 설정을 HTML 문서가 아니라 Forms.py에서 적용
    • label : 기본적으로 input의 이름(html 문서에 보여지는 제목) 을 커스터마이징
    • widget : 속성값을 줄 때 사용
  • content 필드의 경우,

    1) 모델 클래스 Article : TextField 사용 2) 폼 클래스 ArticleForm : CharField 사용함 (폼에서는 TextField가 없음) => widget 사용

    • widget 의 속성을 forms.Textarea 문법을 사용하여 textarea로 변경
    • textarea의 속성은 괄호안에 사용 attrs={} . 일반적으로 textarea 태그 내 작성하는 속성을 attrs 라는 딕셔너리로 넘겨주면 속성을 적용할 수 있음.

View & Tempate 수정하기

  • views.py
#ArticleForm 을 사용하기 위해 import 해옴
from .forms import ArticleForm 

def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        
        if form.is_valid():
            title = form.cleaned_data.get('title')
            content = form.cleaned_data.get('content')
            
            article = Article.objects.create(title=title, content=content)  
            
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleForm()
    return render(request, 'create.html', {'form' : form})
  • 조건문 else 수정 (GET 요청인 경우) => 기본 폼 생성
    • Forms.py 내 ArticleForm 인스턴스를 생성하여 form 변수에 저장
    • form 을 템플릿 변수로 create.html으로 넘겨줌.
    • 이를 통해, create.html에서는 ArticleForm 으로 형성된 input 양식을 사용할 수 있게 됨.
else:
    form = ArticleForm()
    return render(request, 'create.html', {'form' : form})
  • 조건문 if 수정 (POST 요청인 경우) => 폼 데이터를 처리
    • request.POST 에는 사용자가 입력하는 input의 정보가 저장되어 있음
    • form 인스턴스 ArticleForm() 의 인자로 request.POST 를 넘기고, form 변수에 저장함.

Form 클래스에서 정의된 구성에 따라서 입력된 내용을 ArticleForm 이라는 인스턴스의 인자로 넘기면, 인스턴스는 클래스의 양식에 맞춰서 내용을 분석한다.

인스턴스는 사용자가 입력한 정보를 전혀 갖고 있지 않으므로, 이걸 인자로 넘겨줘야 한다고 이해해보자!

if request.method == 'POST':
    form = ArticleForm(request.POST)
  • Form이 유효한지 체크 (유효성 검증) : 입력한 데이터가 유효할 경우, create에 대한 코드 적용.
  • form.cleaned_data.get 은 검정된 데이터를 가져옴.
    • cf) request.POST.get은 검증되지 않은 값을 가져옴.
if form.is_valid():
    title = form.cleaned_data.get('title')
    content = form.cleaned_data.get('content')
    article = Article.objects.create(title=title, content=content)  

    return redirect('articles:detail', article.pk)
  • 데이터가 유효성 검증에 실패할 경우, if form.is_valid(): 조건문에 들어가지 않으며, 조건문에서 최종적으로 벗어남. 기본폼 생성하는 create.html 으로 돌려주기 위해, return값을 else 문 아래가 아니라 내어쓰기로 조건문 밖으로 빼줌. (else에 적용되는 경우와 유효성 검증에 실패하는 경우를 만족시키기 위해)
else:
    form = ArticleModelForm()
return render(request, 'form.html', {'form' : form})
  • create.html 수정하기
    • input 태그로 작성한 내용을 ArticleForm 의 인스턴스가 저장된 form으로 대체
    • 개발자도구로 보면, 은 아래의 HTML 코드와 같음
<h1>New Aricle </h1>
<form method="POST">
    {% csrf_token %}
    <!--<input type="text" name="title" required/>-->
    <!--<input type="text" name="content" required/>-->
    
    <input type="submit" value="Submit"/>
</form>
<label for="id_title">Title:</label>
<input type="text" name="title" required="" id="id_title">
<label for="id_content">Content:</label>
<input type="text" name="content" required="" id="id_content">

Django 11 - REST API

|

REST API

HTTP URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것을 의미

Reference

본 포스팅은 아래 링크의 내용을 참조하여 작성하였습니다. https://meetup.toast.com/posts/92 https://ko.wikipedia.org/wiki/REST http://blog.remotty.com/blog/2014/01/28/lets-study-rest/ https://yangbongsoo.gitbooks.io/study/content/restc758_c774_d574_c640_c124_acc4.html http://spoqa.github.io/2012/02/27/rest-introduction.html

REST API의 탄생

REST는 Representational State Transfer라는 용어의 약자로서 2000년도에 로이 필딩 (Roy Fielding)의 박사학위 논문에서 최초로 소개되었습니다. 로이 필딩은 HTTP의 주요 저자 중 한 사람으로 그 당시 웹(HTTP) 설계의 우수성에 비해 제대로 사용되어지지 못하는 모습에 안타까워하며 웹의 장점을 최대한 활용할 수 있는 아키텍처로써 REST를 발표했다고 합니다.

REST 구성

  • 자원(RESOURCE) - URI 모든 자원에 고유한 ID가 존재하고, 이 자원은 Server에 존재한다. 자원을 구별하는 ID는 ‘/groups/:group_id’와 같은 HTTP URI 다. Client는 URI를 이용해서 자원을 지정하고 해당 자원의 상태(정보)에 대한 조작을 Server에 요청한다.

  • 행위(Verb) - HTTP METHOD HTTP 프로토콜의 Method를 사용한다. HTTP 프로토콜은 GET, POST, PUT, DELETE 와 같은 메서드를 제공한다.

  • 표현(Representations) Client가 자원의 상태(정보)에 대한 조작을 요청하면 Server는 이에 적절한 응답(Representation)을 보낸다. REST에서 하나의 자원은 JSON, XML, TEXT, RSS 등 여러 형태의 Representation으로 나타내어 질 수 있다. JSON 혹은 XML를 통해 데이터를 주고 받는 것이 일반적이다.

REST API 디자인 가이드

REST API 설계 시 가장 중요한 항목은 다음의 2가지로 요약할 수 있습니다. 첫 번째, URI는 정보의 자원을 표현해야 한다.
두 번째, 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다. 다른 것은 다 잊어도 위 내용은 꼭 기억하시길 바랍니다.

REST API 중심 규칙

1) URI는 정보의 자원을 표현해야 한다. (리소스명은 동사보다는 명사를 사용)

    GET /members/delete/1

위와 같은 방식은 REST를 제대로 적용하지 않은 URI입니다. URI는 자원을 표현하는데 중점을 두어야 합니다. delete와 같은 행위에 대한 표현이 들어가서는 안됩니다.

2) 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)로 표현

위의 잘못 된 URI를 HTTP Method를 통해 수정해 보면

    DELETE /members/1

으로 수정할 수 있겠습니다. 회원정보를 가져올 때는 GET, 회원 추가 시의 행위를 표현하고자 할 때는 POST METHOD를 사용하여 표현합니다.

회원정보를 가져오는 URI

    GET /members/show/1     (x)
    GET /members/1          (o)

회원을 추가할 때

    GET /members/insert/2 (x)  - GET 메서드는 리소스 생성에 맞지 않습니다.
    POST /members/2       (o)

예시

	GET /movies/show/1 (x)
	GET /movies/1      (o)
	GET /movies/create (x) - GET METHOD는 자원 생성에 부적합
	POST /movies   	   (o) 
	GET /movies/2/update (x) - GET 부적합
	PUT /movies/2		 (O) 

HTTP METHOD의 알맞은 역할

POST, GET, PUT, DELETE 이 4가지의 Method를 가지고 CRUD를 구현 할 수 있습니다.

METHOD 역할
POST POST를 통해 해당 URI를 요청하면 리소스를 생성합니다.
GET GET를 통해 해당 리소스를 조회합니다. 리소스를 조회하고 해당 도큐먼트에 대한 자세한 정보를 가져온다.
PUT PUT를 통해 해당 리소스를 수정합니다.
DELETE DELETE를 통해 리소스를 삭제합니다.

다음과 같은 식으로 URI는 자원을 표현하는 데에 집중하고 행위에 대한 정의는 HTTP METHOD를 통해 하는 것이 REST한 API를 설계하는 중심 규칙입니다.

그러나, 장고에서는 PUT, DELETE HTTP method는 지원하지 않음. 해당 메소드는 Rest Architecture을 표현하기 위해 근래에 생성된 메소드임. 장고에서는 해당 메소드들을 사용 할 수 없기 때문에 절충안으로 아래의 방법을 사용함.

GET   /movies/2/edit		- 수정 페이지 보여줌
POST  /movies/2/edit		- 수정 작업을 수행함

행위에 따라서 서로 다른 방식을 표현함. 그 외, rest의 권장 방법으로는 뒤에 슬래쉬를 넣지 않으나, 장고에서는 슬래쉬를 넣어야함.

urls.py 수정하기

  • 리스트 페이지는 GET 방식으로 모든 포스트를 다가져오므로 그대로 유지
  • 게시글 생성의 경우,
    • ‘new/’GET을 통해 해당 리소스(URI)를 조회함. 해당 다큐멘트에 대한 자세한 정보(새글을 작성하는 폼)를 가져옴. (새로운 페이지를 가져옴. GET에 적합)
    • ‘create/’의 경우, 새글을 작성(새로운 리소스 생성)하므로 POST방식에 맞음. 그런데, create라는 동사를 URL에 포함시키는것은 REST Architecture에 적합하지 않으므로 수정이 필요
    • 'new/' URI 를 기반으로, GET/POST 방식에 따라, 다른 코드가 적용되게 views.py 내용을 수정
    • 어떠한 요청의 방식이 POST이면, CREATE 안의 코드를 실행, 그렇지 않으면(GET일 경우), 새글 작성하는 폼이 있는 페이지를 보여주게끔, 구현 가능.
    • create/ 함수는 삭제 가능
  • 동일한 방법으로 edit & update 또한 수정이 가능함
    • 어떠한 요청의 방식이 POST이면, update안의 코드를 실행, 그렇지 않으면(GET일 경우), 글을 수정하는 폼이 있는 페이지를 보여주게끔, 구현 가능.
urlpatterns = [
    #GET
    path('', views.index, name='list'), 
    
    #GET(new), POST(create)
    path('new/', views.new, name='new'), 
    #path('create/', views.create, name='create'),
    
    #GET
    path('<int:post_id>/', views.detail, name='detail'), 
    
    #GET(conifrm) POST(delete)
    path('<int:post_id>/delete/', views.delete, name='delete'), 
   
    #GET(edit), POST(update)
    path('<int:post_id>/edit/', views.edit, name='edit'), 
    #path('<int:post_id>/update/', views.update, name='update'),
]

New/Create 수정

'new/' URI (리소스)를 기반으로, GET/POST 방식에 따라, 다른 코드가 적용되게 views.py 내용을 수정.

어떠한 요청의 방식이 POST이면, CREATE 안의 코드를 실행, 그렇지 않으면(GET일 경우), 새글 작성하는 폼이 있는 페이지를 보여주게 코드 수정.

  • views.py
    • 조건문을 통해 코드를 분기하여 작성 가능.request.method 는 http Method를 의미함.
    • create 함수는 삭제 가능
def new(request):
    if request.method == 'POST':
        #create
        title = request.POST.get('title')
        content = request.POST.get('content')
        image = request.FILES.get('image')
        
        post = Post(title=title, content=content, image=image)
        post.save()
    
        return redirect('posts:detail', post.pk)
    
    else:
        #new
        return render(request, 'new.html')
  • new.html

    • 동일한 URI를 사용하므로 create 함수를 실행시키는 URL를 작성하였던 action 속성을 삭제
      • cf) action="{% url 'posts:create' %}"
    • form 태그에 action 속성값이 존재 하지 않을 경우, 자기자신에게 요청을 보냄.
    • 이에 따라, views.py - new 함수 내 조건문에 따라 POST 방식에 대한 코드가 실행됨.
{% extends 'base.html' %}
{% block container %}
    <form method="POST">
        {% csrf_token %}
        <input type="text" name="title"/>
        <input type="text" name="content"/>
        <input type="submit" value="Submit"/>
    </form>
{% endblock %}
  • 기본 프로세스
    • 기본주소/posts/new 의 리소스(URI)가 요청이 들어오면, GET 방식을 통해 new.html 페이지가 열림. (new 함수 조건문에서 else가 실행됨)
    • 내용을 작성한 후, submit 을 할 경우, 자기자신에 POST 방식으로 요청을 보내게 되며, create 함수의 코드에 따라, 데이터베이스에 내용이 저장되고, detail.html 으로 redirect 됨. (new 함수 조건문에서 if 부분이 실행됨)

Edit & Update 수정

같은 방법으로, edit & update.html에 대한 urlpattern & views.py 내용도 수정 가능.

  • views.py
    #views.py
    def edit(request, post_id):
      post = Post.objects.get(pk=post_id)
        
      #update
      if request.method == 'POST':
          post.title = request.POST.get('title')
          post.content = request.POST.get('content')
          post.save()    
          return redirect('posts:detail', post.pk)
        
      else:
          #edit
          return render(request, 'edit.html', {'post':post})
    
  • edit.html
<-- edit.html -->
{% extends 'base.html' %}
{% block container %}
    <h1>Post Edit</h1>
    <form method="post">
        {% csrf_token %}
        <input type="text" name="title" value="{{ post.title }}"/>
        <input type="text" name="content" value="{{ post.content }}"/>
        <input type="submit" value="Submit"/>
    </form>
{% endblock %}

Delete.html 생성

장고에는 HTTP Method 중 하나인 delete를 지원하지 않기 때문에, GET과``POST` 메서드를 통해 대안으로 구현할 수 있음.

  • GET: 삭제에 대한 Confirm 유무를 보여주는 URI(리소스)를 조회함
  • POST: Delete 실행
  • urls.py
#GET(conifrm) POST(delete)
path('<int:post_id>/delete/', views.delete, name='delete'), 
  • views.py
def delete(request, post_id):
    if request.method == 'POST':
        post = Post.objects.get(pk=post_id)
        post.delete()
        return redirect('posts:list')
    
    else:
        return render(request, 'delete.html')
  • delete.html
<-- delete.html -->
{% extends 'base.html' %}
{% block container %}

<h1>정말로 삭제하겠읍니까?</h1>
<form method="post">
    {% csrf_token %}
    <input type="submit" value="삭제"/>
</form>

{% endblock %}

Django 10 - 템플릿 확장 및 상속

|

템플릿 확장 및 상속

templates 내 html 파일을 살펴보면, 많은 부분 (특히, head)의 코드가 중복되고 있음을 알 수 있음.

템플릿 확장(template extending)을 통해 웹사이트 안의 서로 다른 페이지에서 HTML의 일부를 동일하게 재사용 할 수 있게 됨. 이 방법을 사용하면 동일한 정보/레이아웃을 사용하고자 할 때, 모든 파일마다 같은 내용을 반복해서 입력 할 필요가 없게 됨.

기본 템플릿 생성

  • 기본 템플릿은 웹사이트 내 모든 페이지에 확장되어 사용되는 가장 기본적인 템플릿임.
  • 프로젝트 폴더(이하, crud) 안에 templates 폴더 생성 - base. html 생성
  • 동적으로 바뀌는 부분을 아래의 양식 안에 정의
    • {% block container %}
    • {% endblock %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>여기는 base.html!</h1>
    <!--동적으로 바뀔 부분을 아래에 정의-->
    {% block container %}
    {% endblock %}
</body>
</html>
  • settings.py 내 기본 템플릿 경로 설정
  • 'DIRS': [os.path.join(BASE_DIR, 'crud','templates')],
    • BASE_DIR : 최상위 ROOT 디렉토리를 의미함.
    • 2번째 이하 인자로는 ROOT 디렉토리 이하 기본 템플릿이 위치한 경로를 입력
    • 위의 의미는 다음과 같음; 기본주소\crud\templates 에 기본 템플릿이 위치하고 있음.
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'crud','templates')],
        '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',
            ],
        },
    },
]

각 html 파일에 base.html 상속 받기

  • base.html을 기본으로 깔고, {% block container %}/{% endblock %} `{사이에 내용을넣으면 됨.
  • 기본 템플릿을 상속받기 위해 {% extends 'base.html' %} 을 우선 입력.
  1. index.html
{% extends 'base.html' %}
{% block container %}
    <h1>Post Index</h1>
    <a href="{% url 'posts:new' %}">New - 새로운 글쓰기</a>
    <ul>
    {% for post in posts %}
        <li>
        <a href="{% url 'posts:detail' post.pk %}">{{ post.title }}
            
        </a></li>    
     {% endfor %}
    </ul>
{% endblock %}
  1. detail.html
{% extends 'base.html' %}
{% block container %}

    <h1>Post Detail</h1>
    <h2>Title : </h2>
    <p> Content : </p>
	
    <a href="{% url 'posts:list' %}">List</a>
    <a href="{% url 'posts:edit' post.pk %}">Edit</a>
    <a href="{% url 'posts:delete' post.pk %}">Delete</a>
	
    
    <hr>
	<form action="{% url 'posts:comments_create' post.pk %}" method="post">
        {% csrf_token %}
        댓글 : <input type="text" name="content"/>
			   <input type="submit" value="Submit"/>
    </form>
    <ul>
      {% for comment in post.comment_set.all %}
      <li> 
        - 
        <a href="{% url 'posts:comments_delete' post.pk comment.pk %}">Delete</a>
      </li>
      {% endfor %}
    </ul>

{% endblock %}
  1. edit.html
{% extends 'base.html' %}
{% block container %}
    <h1>Post Edit</h1>
    <form method="post">
        {% csrf_token %}
        <input type="text" name="title" value=""/>
        <input type="text" name="content" value=""/>
        <input type="submit" value="Submit"/>
    </form>
{% endblock %}
  1. new.html
{% extends 'base.html' %}
{% block container %}
    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="text" name="title"/>
        <input type="text" name="content"/>
        <input type="file" name="image" accept="image/*"/>
        <input type="submit" value="Submit"/>
    </form>
{% endblock %}