Django CRUD

지니🧸·2022년 10월 16일
0

Django

목록 보기
2/9

Django CRUD

CRUD

Create 생성, Read 조회, Update 수정, Delete 삭제

웹사이트의 가장 핵심적인 기능

(예) 게시글/댓글 작성, 조회, 삭제, 수정 등

프로젝트 생성 및 설정

프로젝트 생성

mkdir django
cd django
pyenv local django-envs
django-admin start project costory
cd costory
code .

프로젝트 설정

#settings.py

TIME_ZONE = 'UTC' #지우고
TIME_ZONE = 'Asia/Seoul'
  • 한국 시간을 사용하려면 TIME_ZONE 값을 바꿔야함
    • ‘Asia/Seoul’

블로그 포스트 관리 앱 생성

  • terminal → new terminal
python manage.py startapp posts
  • settings.py의 installed app에 생성된 앱 추가
  • migrate해서 기존의 앱들이 필요한 데이터 구조 생성
python manage.py migrate

URL 구조 만들기

‘’(root)블로그 홈페이지
/posts/전체 포스트 조회(Read)
/posts/<post_id>개별 포스트 조회(Read)
/posts/new포스트 작성(Create)
/posts/<post_id>/edit포스트 수정(Update)
/posts/<post_id>/delete포스트 삭제(Delete)

URL 설정하기

#project app의 urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('posts.urls')),
]
#posts app에 urls.py 생성 후 사용
from django.contrib import admin
from django.urls import path, include
from . import views

urlpatterns = [
    # path('', views.index),
    # path('posts/', views.post_list),
    # path('posts/new', views.post_create),
    # path('posts/<int:post_id>/',views.post_detail),
    # path('posts/<int:post_id>/edit', views.post_update),
    # path('posts/<int:post_id>/delete', views.post_delete)
]

Model 만들기

#project app의 models.py
from django.db import models

# Create your models here.
class Post(models.Model):
    #글의 제목, 내용, 작성일, 마지막 수정일
    title = models.CharField(max_length=50)
    content = models.TextField() 
    dt_created = models.DateTimeField(verbose_name="Date Created", 
																												auto_now_add = True)
    dt_modified = models.DateTimeField(verbose_name = 'Date Modified', 
																												auto_now=True)

		def __str__(self):
        return self.title
  • TextField()
    • 최대 길이 정의 필요 X
  • DateTimeField()의 인자들
    • verbose_name: 사람이 읽기 좋은 필드명 지정하는 인자
    • auto_now=True: 포스트가 마지막으로 저장될 때 시간으로 자동적으로 해당 필드에 저장
      • 데이터의 마지막 수정일
    • auto_now_add=True: 포스트가 처음 생성될 때의 시간을 자동적으로 해당 필드에 저장
      • 데이터의 생성일
    • auto_now & auto_now_add가 모두 True면 에러 발생

필드 유형

필드 옵션

admin.py에 모델 등록

#admin.py
from django.contrib import admin
from {project_app}.models import {model_name}
admin.site.register({model_name})
#admin.site.register(Page)

Migrate하기

python manage.py makemigrations
python manage.py migrate
  • 포스트의 데이터구조가 데이터베이스에 반영 되었음

데이터 조작하기

Terminal에서 shell 실행: terminal → new terminal → 터미널에 python manage.py shell 입력

데이터 추가하기

from posts.models import Post
Posts.objects.create(title="첫 포스팅", content="오늘 새 맥북 프로가 왔어요!!!")

전체 데이터 조회

Posts.objects.all().values()

데이터 수정하기

post = Post.objects.get(id=1)
post.title="맥북 프로와 함께하는 첫 포스팅(수정)"
post.save()

관리자 페이지로 데이터 추가하기

  1. 관리자 계정 생성
python manage.py createsuperuser

Django Model API

Django에서 Model을 정의하면 ORM을 통해 데이터베이스와 소통할 수 있는 API 제공

API: epdlxjqpdltmfmf whwkrgkf Eo tkdydgoTejs dkfodhk rkxdms ahems audfuddj

<model>.objects.all() #모든 데이터 가져오기
<model>.objects.get() #조건에 맞는 데이터 1개 가져오기
  • API: Application Programming Interface
    • 어플리케이션에서 시스템의 기능을 제어할 수 있도록 만든 인터페이스
    • 어떤 기능을 쉽게 사용할 수 있도록 만든 체계

Queryset: Django Model의 데이터가 담겨있는 목록

  • 파이썬의 리스트와 비슷한 형태
<model>.objects.all() #<model>의 모든 데이터 Queryset 가져오기
  • objects는 Model Manager라고도 불림
    • Model과 데이터베이스 간에 연산을 수행하는 역할
  • Queryset: objects를 통해 데이터베이스와 연산해서 얻은 여러 모델 데이터가 담겨있는 것

Queryset API

Queryset: 데이터베이스로부터 가져온 여러개의 model 데이터

Queryset을 반환하는 API

하나의 데이터 객체를 반환하는 API

Others

필드 조건 옵션 (Field Lookups): Queryset 연산을 할 때 사용할 수 있는 여러 필드 조건 옵션

  • 필드명 뒤에 __를 쓰고 사용할 옵션 인자를 적어주면 됨

필드 조건 옵션

Lazy Evaulation, 지연 연산

위에서 작성한 Django의 모든 Query 연산은 병합 (Chain)이 가능함

Post.objects.filter(id__gte=10, content__contains='codeit').order_by('-dt_created').last()
  • 너무 많은 연산을 묶는 것은 지양해야 함
  • 모든 코드는 명확해야 함
  • 여러 줄에 걸쳐서 코드를 작성해도 코드가 느려지지 않음
    • Django의 Query는 지연 연산을 지원하기 때문
      • 지연 연산: 실제로 데이터가 필요하기 전까지 Query 연산을 수행하지 않고 지연되는 것

포스트 목록 페이지 만들기

#posts app의 views.py
from django.shortcuts import render
from posts.models import Post #데이터베이스와 소통하기 위해 models을 가져온다

# Create your views here.
def post_list(request):
    posts = Post.objects.all()
    context = {"posts":posts}
    return render(request, 'posts/post_list.html', context=context)

포스트 상세 페이지

#views.py
from django.shortcuts import render
from posts.models import Post

def post_detail_view(request, post_id):
    post = Posts.objects.get(id=post_id)
    context = {"post":post}
    return render(request, 'posts/post_detail.html', context=context)
#post_details.html
{% extends './base.html' %}

{% block post_container %}
<h2>{{post.title}}</h2>
<div>작성일: {{post.dt_created}}</div>
<hr>
<div>{{post.content|linebreaksbr}}</div>
<hr>

{% endblock post_container %}
  • linebreaksbr: 줄바꿈 문제 해결

사이트 연결하기

#post_list.html
{% extends './base.html' %}
{% load static %}

{% block post_container %}
    <h1>글 목록 페이지에요</h1>
    <table>
        <tr>
            <td>제목</td>
            <td>작성일</td>
            <td>수정일</td>
        </tr> 
    {% for post in posts %}
         
        <tr>
            <td>**<a href="/posts/{{post.id}}">**{{post.title}}**</a><**/td>
            <td>{{post.dt_created}}</td>
            <td>{{post.dt_modified}}</td>
        </tr>
    {% endfor %}
    </table>
{% endblock %}
#post_detail.html
{% extends './base.html' %}

{% block post_container %}
<h2>{{post.title}}</h2>
<div>작성일: {{post.dt_created}}</div>
<hr>
<div>{{post.content|linebreaksbr}}</div>
<hr>
<a href='/posts/'>돌아가기</a>
{% endblock post_container %}

URL의 name 속성: 각각의 URL에 이름을 붙여주는 것

#urls.py
from django.contrib import admin
from django.urls import path, include
from . import views

urlpatterns = [
    # path('', views.index),
    path('posts/', views.post_list, name='post-list'),
    # path('posts/new', views.post_create),
    path('posts/<int:post_id>/',views.post_detail, name='post-detail'),
    # path('posts/<int:post_id>/edit', views.post_update),
    # path('posts/<int:post_id>/delete', views.post_delete)
]
#post_detail.html
{% extends './base.html' %}

{% block post_container %}
<h2>{{post.title}}</h2>
<div>작성일: {{post.dt_created}}</div>
<hr>
<div>{{post.content|linebreaksbr}}</div>
<hr>
<a **href={% url 'post-list' %}**>돌아가기</a>
{% endblock post_container %}
{% extends './base.html' %}
{% load static %}

{% block post_container %}
    <h1>글 목록 페이지에요</h1>
    <table>
        <tr>
            <td>제목</td>
            <td>작성일</td>
            <td>수정일</td>
        </tr> 
    {% for post in posts %}
         
        <tr>
            <td><a href={% url 'post-detail' post.id %}>{{post.title}}</a></td>
            <td>{{post.dt_created}}</td>
            <td>{{post.dt_modified}}</td>
        </tr>
    {% endfor %}
    </table>
{% endblock %}

HTML Form

폼: 웹페이지에서 사용자의 데이터를 입력받을 수 있는 입력 양식

label & input

  • 폼은 form태그 안에 사용자의 입력을 받는 input태그와 설명을 위한 label 태그의 쌍으로 구성됨
<form>
		<label>이름</label>
		<input type="text">
</form>

for & id

  • 각각의 input태그와 label태그를 묶어주기 위해서 label태그에는 for 속성, input태그에는 id 사용
<form>
    <label for="title">제목</label>
    <input type="text" id="title">
</form>
  • 아래 코드로 대체도 가능
<form>
		<label>제목
				<Input type="text">
		</label>
</form>

name

  • 입력된 데이터를 서버로 전송할 때, 서버에서 각각의 데이터를 구분하기 위한 속성
  • name 속성이 있는 양식 요소만 값이 서버로 전달
<form>
    <label for="title">제목</label>
    <input type="text" id="title" name="title">
</form>

type

  • 입력할 값에 따른 유형을 나타내는 속성
  • 이 Type에 따라 사용자가 브라우저에서 값을 입력하는 형식인 위젯(widget)이 달라짐
  • 자주 사용되는 type: email, password
    • email

      <label for="email">이메일</label>
      <input type="email" id="email" name="email">
    • password

      <label for="pwd">비밀번호</label>
      <input type="password" id="pwd" name="pwd">
    • button

      <input type="button" value="버튼입니다">
    • radio

      <input type="radio" id="male" name="gender" value="male">
      <label for="male">남자</label><br>
      <input type="radio" id="female" name="gender" value="female">
      <label for="female">여자</label><br>
      <input type="radio" id="other" name="gender" value="other">
      <label for="other">기타</label>
    • checkbox

      <input type="checkbox" id="lang1" name="lang1" value="Python">
      <label for="lang1">파이썬(Python)</label><br>
      <input type="checkbox" id="lang2" name="lang2" value="JAVA">
      <label for="lang2">자바(JAVA)</label><br>
      <input type="checkbox" id="lang3" name="lang3" value="Go">
      <label for="lang3">고(Go)</label><br>
    • date

      <label for="birthday">생년월일</label>
      <input type="date" id="birthday" name="birthday">
    • file

      <label for="userfiles">파일선택</label>
      <input type="file" id="userfiles" name="userfiles" multiple>
    • submit

      <input type="submit" value="전송하기">

form 속성

  • action: 입력된 데이터를 전송할 서버의 URL 지정
  • method: http 전달 방식을 지정

Django Form

폼, Form: 사용자가 웹에 입력한 데이터를 서버로 전송하기 위한 방식

<form action="next/" method="post">
		<label for="name">이름</label>
		<input type="text" id="name" name="name"/>
		<label for="email">이메일</label>
		<input type="email" id="email" name="email"/>
		<label for="birth">생일</label>
		<input type="date" id="birth" name="birth"/>
	<input type="submit" value="전송">
</form>
  • form 태그의 속성: action=”next/” method=”post”
    • 폼에 입력된 데이터를 post 요청을 통해 action에 해당하는 url로 보내겠다

클라이언트는 서버에게 요청(Request), 서버는 요청을 받아 처리한 후 응답(Response)

대표적인 Request 두가지:

  • Get: 서버로부터 데이터 조회
    • Read
    • (예) 검색, 동영상 시청, etc.
    • 필요한 데이터를 URL의 끝에 담아서 보냄 → 쿼리 스트링, query string
    • (ex) http://example.com/user/profile?name=지웅&email=jw@gmail.com&birth=212
      • 물음표(?)로 url의 끝을 표시
    • query string: 서버에 요청할 때 원하는 것을 상세하게 표현할 때 사용
    • 하지만 민감한 데이터가 URL에 보여지면 안되기 때문에 POST
  • Post: 서버의 데이터 변경
    • 클라이언트와 서버가 통신하는 메세지의 안쪽에 데이터를 담음
    • Create, Update, Delete
    • (ex) 회원가입, 블로그 글쓰기, 트윗 쓰기, etc.

Form 처리 과정

  1. 사용자가 폼을 작성하기 위해 서버에 폼 양식을 요청 - 폼양식 조회, GET
  2. 서버가 처음 제공하는 폼은 언바운드 폼, Unbound form
    • 데이터가 아직 폼에 묶여있지 않기 때문에
  3. 사용자가 데이터를 입력하고 폼을 전송
    • 폼의 속성에 명시된 method으로 action에 명시된 url로 전송
  4. Binding: 서버에서 입력된 데이터와 폼을 합쳐서 하나의 형태로 만듬
    • Bound form: binding을 통해 데이터와 합쳐진 폼
  5. bound form의 데이터가 잘못됐으면 사용자에게 다시 폼을 입력하도록 한다
    • 데이터가 유효할 때까지 2~5 반복
  6. 입력된 데이터가 유효하다면 다음으로 서버에서 지정한 로직 수행
  7. 작업이 끝나면 새로운 페이지 안내 (Response)

Django에 Form 코딩하기

posts app에 forms.py 생성

  • 이 파일에 작성하는 하나의 폼 클래스가 하나의 폼이 되는 것
#forms.py
from django import forms

class PostForm(forms.Form):
    title = forms.CharField(max_length=50, label='제목')
    content = forms.CharField(label='내용', widget=forms.Textarea)

views.py에 폼을 이용할 function define하기

#views.py
from posts.forms import PostForm
from django.shortcuts import render

def post_create(request):
    post_form = PostForm()
    return render(request, 'posts/post_form.html', {'form':post_form})

post_form.html 생성

#post_form.html
<form method="post">{% csrf_token %}
    {{form.as_ul}}
    <input type="submit" value="전송">
</form>
  • form.as_ul : form을 Unordered list로 렌더
    • as_table, as_p 등
  • form의 action을 따로 정의하지 않으면 현재 url로 전송
  • csrf_token: 템플릿 태그의 종류로 Cross site request forgery의 약자
    • 교차 사이트 위조 검증: 내가 하지 않은 요청을 내가 한것처럼 방지하기 위한 보안 기술

서버에서 데이터를 받아서 처리하는 과정

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

def post_create(request):
    if request.method == "POST":
        title = request.POST['title']
        content = request.POST['content']
        new_post = Post(
            title = title,
            content = content
        )
        new_post.save()
				return redirect('post-detail', post_id=new_post.id)
		else:
				post_form = PostForm()
		    return render(request, 'posts/post_form.html', {'form':post_form})
  • redirect: 다른 페이지로 연결
    • 첫 인자: 어디로 이동할지
    • post-detail는 post_id가 꼭 필요하기 때문에 데이터베이스에 넣어줌
  • else statement
    • 유저가 처음 폼을 요청할때

CSRF 방지

CSRF, Cross-Site Request Forgery, 크로스 사이트 요청 위조: 웹사이트에서 유저가 서버로 요청을 보내는 행위를 악의적으로 변경해서 요청 전송

위조 방지 토큰 (i.e., csrf_token): 서버로부터 폼을 요청할 때 발행되어 유저가 폼에 데이터를 입력하고 서버로 전송할 때 이 토큰 값을 함께 전달해서 서버에서 토큰 값을 비교한 뒤 요청을 처리하는 방식

  • aka request verification token
<form action="/user" method="post">{% csrf_token %}
...
</form>

Django Form Field

Django Form Field

필드 옵션

  • label
# forms.py
from django import forms

class UserForm(forms.Form):
    name = forms.CharField()
    age = forms.IntegerField(label='Your age')
<!-- html -->
<label for="id_name">Name:</label> 
<input type="text" name="name" required id="id_name">
<label for="id_age">Your age:</label>
<input type="number" name="age" required id="id_age">
  • label_suffix
# forms.py
from django import forms

class UserForm(forms.Form):
    name = forms.CharField()
    age = forms.IntegerField(label_suffix='=')
<!-- html -->
<label for="id_name">Name:</label>
<input type="text" name="name" required id="id_name">
<label for="id_age">Age=</label>
<input type="number" name="age" required id="id_age">
  • help_text
# forms.py
from django import forms

class UserForm(forms.Form):
    name = forms.CharField(help_text='한글 이름을 작성해주세요.')
    age = forms.IntegerField()
<!-- html -->
<label for="id_name">Name:</label>
<input type="text" name="name" required id="id_name">
<span class="helptext">한글 이름을 작성해주세요.</span>
<label for="id_age">Age:</label>
<input type="number" name="age" required id="id_age">

Model Form

웹서비스에서의 폼은 유저에게 데이터 입력을 받아 데이터베이스에 저장하는 경우가 많아서 모델을 기반으로 만드는 경우가 많음

Model Form: 우리가 작성한 모델을 기반으로 폼을 만들어주는 기능

Model Form 작성

#forms.py
from django import forms
from .models import Post #이용할 모델 import

class PostForm(forms.ModelForm):
    
    class Meta:
        model = Post
        fields = ['title', 'content']
  • class Meta: PostForm이라는 클래스를 만들 때 적용할 여러 옵션을 적어주는 클래스
  • (class Meta 내의) fields =
    • all’ : 모든 필드
    • [’file_name1’, ‘field_name2’]

View 로직을 모델폼에 맞게 수정

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

def post_create(request):
    if request.method == "POST":
        post_form = PostForm(request.POST) #유저가 입력하자마자 바인딩
        new_post = post_form.save() #데이터베이스에 저장
        return redirect('post-detail', post_id=new_post.id)
    else:
        post_form = PostForm()
        return render(request, 'posts/post_form.html', {'form':post_form})

데이터 유효성 검사

데이터 유효성: 데이터가 우리가 원하는 규격에 맞는지 확인하는 과정

(예) 입력된 데이터 type이 맞는지, 필수 입력칸에 입력했는지, 입력하면 안되는 기호가 들어갔는지 등

유효성 검증, validation:

  • Field 인자로 유효성 검증하기: Field를 정의할 때 필요한 내장 Field옵션 인자로 주기
    • 우리는 Model Form을 사용하기 때문에 Model의 Field 옵션을 정의하면 됨
    1. 기본 옵션 인자 사용:

      #models.py
      from django.db import models
      
      class Post(models.Model):
      		title = models.CharField(max_length = 50, unique = True,
      								error_messages = {'unique':'이미 있는 제목이네요!'})
      		content = models.TextField()
      		...
      • blank: 폼에서 비어있는 값을 허용할지 설정
        • 모든 필드의 기본 옵션 인자
        • 기본 값은 False → 빈칸을 허용하지 않음
      • null: 데이터에 빈 값을 null로 저장하는 것을 허용할지 설정
        • null=True 일 경우: 데이터값으로 빈 값이 들어왔을 때 null로 저장
        • 문자열 기반에서는 권장 X
      • unique: 데이터베이스에서 같은 형식의 데이터를 저장할 수 있게 할지 설정
        • unique = True 일 경우: 데이터베이스에서 같은 형식의 데이터 저장 X
      • error_messages : 에러가 발생했을 때 보여줄 메시지를 상황별로 dict형식으로 저장
        • key값에는 장고 내장 옵션 인자 또는 custom error code 입력
    2. post_create 뷰에 유효성 검증 단계 추가하기

      def post_create(request):
          if request.method == "POST":
              post_form = PostForm(request.POST) #유저가 입력하자마자 바인딩
              **if post_form.is_valid():
                  new_post = post_form.save()
      						return redirect('post_detail', post_id=new_post.id)**
          else:
              post_form = PostForm()
          return render(request, 'posts/post_form.html', {'form':post_form})
      • is_valid() : post_form의 데이터가 유효한지 확인해줌
        • valid하면 redirect
        • invalid하면 다시 데이터 입력
  • Validator로 유효성 검증하기: 임의의 값을 받아서 내부의 기준을 충족하지 않으면 ValidationError를 발생시키는 함수
    • 여러의 필드에서 사용 가능
    • 장고의 built-in validator 또는 직접 만든 validator 사용
    1. Built-in validator 사용

      #models.py
      import django.db import models
      **from django.core.validators import MinLengthValidator**
      
      class Post(models.Model):
      		title = models.CharField(max_length = 50, unique = True,
      								error_messages = {'unique':'이미 있는 제목이네요!'})
      		content = models.TextField(**validators=[MinLengthValidator(10, 
      									'너무 짧군요! 10자 이상 적어주세요!'**)])
      		...
      • MinLengthValidator: 입력된 값이 limit_value보다 짧으면 ValidationError 발생시킴
        • MinLengthValidator(limit_value, message)
    2. 직접 Validator 만들기

      validators.py 생성

      #validators.py
      from django.core.exceptions import ValidationError
      
      def validate_symbols(value):
          if ("@" in value) or ("#" in value):
              raise ValidationError('"@"와 "#"은 포함될 수 없습니다.', code='symbol-err')

      models.py에 내가 만든 validator 사용하기

      #models.py
      from django.db import models
      from django.core.validators import MinLengthValidator
      **from .validators import validate_symbols**
      
      # Create your models here.
      class Post(models.Model):
          #글의 제목, 내용, 작성일, 마지막 수정일
          title = models.CharField(max_length=50, unique=True, error_messages={'unique':'이미 있는 제목이네요!'})
          content = models.TextField(validators=[MinLengthValidator(10, message='너무 짧군요! 10자 이상 적어주세요!'), 
                                                  **validate_symbols**]) 
          dt_created = models.DateTimeField(verbose_name="Date Created", auto_now_add = True) 
          dt_modified = models.DateTimeField(verbose_name = 'Date Modified', auto_now=True)
          
          def __str__(self):
              return self.title

      views.py에서 데이터가 유효검사를 통과했을 때에만 데이터베이스에 저장하도록 수정

  • Form에서 유효성 검증하기: 일반 폼을 쓰는 경우에 필드옵션과 validator 작성 가능
    #forms.py
    from django import forms
    from .models import Post
    from .validators import validate_symbols
    
    class PostForm(forms.ModelForm):
        **memo = forms.CharField(max_length=80, validators=[validate_symbols])**
        class Meta:
            model = Post
            fields = ['title', 'content']
    OR
    #forms.py
    from django import forms
    from .models import Post
    from .validators import validate_symbols
    
    class PostForm(forms.ModelForm):
        
        class Meta:
            model = Post
            fields = ['title', 'content']
        
        def clean_title(self):
            **title = self.cleaned_data['title']
    				if '*' in title:
                raise ValidationError('*는 포함될 수 없습니다.')
            return title**
    • 모든 form class는 기본적으로 cleaned_data를 갖고 있음
      • cleaned_data 안에는 폼 필드를 정의할 때 넣어준 유효성 검증을 통과한 데이터가 들어있음
      • 폼 필드를 사용하지 않았다면 유저가 입력한 데이터가 그대로 넘어옴
        • 모델 필드에 유효성 검증을 넣어주면 입력한 데이터가 그대로 넘어옴
      • clean_{field-name} vs. Validator
        • clean_{field-name} 는 하나의 필드에서만 유효성 검사가 가능함
        • clean_{field-name} 유효성 검증 에러와는 관계없이 항상 return title해서 cleaned_data에서 가져온 데이터를 리턴해줘야함
        • validator는 한번 정의하면 모델과 필드에서 모두 사용이 가능하고 여러 필드에서 사용 가능

Form 다루기

#post_form.html
<form method="post">{% csrf_token %}
    <h3>제목</h3>
    <p>{{**form.title**}}</p>
    {% for error in **form.title.errors** %}
        <p>{{error}}</p
    {% endfor %}
    <h3>내용</h3>
    <p>{{**form.content**}}</p>
    {% for error in **form.content.errors** %}
        <p>{{error}}</p>
    {% endfor %}
    <input type="submit" value="전송">
</form>

Form에 CSS 적용하기

#form.css
.title {
		width: 400px;
}
#forms.py
from django import forms
from .models import Post
from .validators import validate_symbols
from django.core.exceptions import ValidationError

class PostForm(forms.ModelForm):
    
    class Meta:
        model = Post
        fields = ['title', 'content']
        **widgets = {'title':forms.TextInput(attrs={
						'class':'title', 
						'placeholder': '제목을 입력하세요.'})
						'content': forms.Textarea(attrs={
								'placeholder':'내용을 입력하세요.'})}**
    
    def clean_title(self):
        title = self.cleaned_data['title']
        if '*' in title:
            raise ValidationError('*는 포함될 수 없습니다.')
        return title
  • widgets: 사전형 변수로, 필드 이름을 key로 하고 적용할 위젯을 value로 설정
    • 폼 필드마다 위젯을 직접 명시할 수 있게 해줌
    • 기본 위젯 말고 다른 위젯을 사용하거나 직접 위젯에 접근해야 할 때 사용
  • attrs: 사전형 변수로 속성 결정

포스트 수정 페이지

post detail 페이지에 수정하기 버튼 추가

#post_detail.html
...
<div class="btn">
    <div class="btn_link"><a href={% url 'post-list' %}>목록으로</a></div>
    <div class="right-btn">
        <div class="btn_modify">
            <a href="{% url 'post-update' post.id %}">수정하기</a>
        </div>
    </div>
</div>
...

views.py에 post_update 뷰 추가

...
def post_update(request, post_id):
    post = Post.objects.get(id=post_id)
    if request.method == "POST":
        post_form = PostForm(request.POST, instance=post)
        if post_form.is_valid():
            post_form.save()
            return redirect('post-detail', post_id=post.id)
    else: #get 방식일때는 사용자가 데이터를 수정할 수 있도록 폼을 생성해서 제공함
			#기존 모델의 데이터를 가져와서 채운 상태의 폼 제공   
        post_form = PostForm(instance=post)
    return render(request, 'posts/post_form.html', {'form':post_form})
  • post_form = PostForm(instance=post)
    • 작성된 데이터와 폼의 바인딩
    • 새로운 모델 객체를 갖는 폼을 생성하는게 아니라 기존에 작성되었던 Post model instance와
  • instance: 기존 모델을 폼에 넣어주는 인자
    • post data가 폼에 바인드 된 상태로 post_form이 만들어짐
  • request.post: A dictionary-like object containing all given HTTP POST parameters

포스트 삭제하기

post detail 페이지에 삭제하기 버튼 추가

post를 삭제할건지 확인하는 페이지 추가

  • post_confirm_delete.html 생성
{% extends './base.html' %}
{% load static %}

{% block css %}
    <link rel="stylesheet" href="{% static 'posts/css/post_confirm_delete.css' %}">
{% endblock css %}

{% block content %}
<div class="confirm">
    <p class="title">[{{post.title}}]</p>
    <p>삭제하시겠습니까?</p>
    <form method="POST">{% csrf_token %}
        <div class="confirm_btn">
            <input type="submit" value="삭제하기">
        </div>
    </form>
</div>
{% endblock content %}

views.py에 post_delete 정의하기

...
def post_delete(request, post_id):
    post = Post.objects.get(id=post_id)
    if request.method == "POST":
        post.delete()
        return redirect('post-list')
    else:
        return render(request, 'posts/post_confirm_delete.html', {'post':post})

메인페이지 (index 페이지) URL 설정하기

views.py에 index뷰 정의하기

def index(request):
		return redirect('post-list')

OR

urls.py에서 재설정하기

...
urlpatterns = [
    #path('', views.index, name='index'),
    path('', views.post_list, name='post-list'),
		...
]

포스트 전체 삭제하기

  • admin으로 로그인해서 포스트 삭제
  • shell에서 삭제
from posts.models import Post
Post.objects.all().delete()

데이터가 없을 때

post_list.html 수정

{% if posts %} 
        <div class="post_container">
            {% for post in posts %}
            <div class="post"><a href={% url 'post-detail' post.id %}>         
                <h2 class="title">{{post.title}}</h2>
                <p class="date">{{post.dt_created}}</p>
                <p class="text">{{post.content|slice:':100'}}</p>
            </a></div>
            {% endfor %}
        </div>
    {% else %}
        <div class="blank">
            <p>보여줄 글이 없어요.<br>첫 글을 작성해보면 어떨까요?</p>
        </div>
    {% endif %}

가져와야 할 데이터가 존재하지 않을 때

views.py의 post_detail 뷰를 수정하여 에러 예상 및 대처

#view.py
...
from django.http import Http404
...
def post_detail(request, post_id):
    **try: #오류가 날 수도 있는 코드 삽입
        post = Post.objects.get(id=post_id)
    except Post.DoesNotExist:
        raise Http404()**
    context = {"post":post}
    return render(request, 'posts/post_detail.html', context=context)

OR

#view.py
...
from django.shortcuts import render, redirect, **get_object_or_404
...**
def post_detail(request, post_id):
    **post = get_object_or_404(Post, id=post_id)**
    context = {"post":post}
    return render(request, 'posts/post_detail.html', context=context)

초기에 필요한 데이터

Seeding, 시딩: 사용할 데이터를 데이터베이스에 추가하는 것

  • 테스트에 필요한 데이터 준비에 유용
  • 초기 데이터를 입력해야 하는경우 유용
#shell
python manage.py loaddata <seed_file>
  • <seed_file>
    • JSON, XML 등의 파일 형식 가능
      • JSON: 데이터를 표현하기 위해 사용하는 대표적 포맷
      • 사전형과 비슷하게 name과 value로 데이터 표현
      • (예) {”title”: “title_data_01”,}
      • 중첩 가능 (ex) json_object[’data_01’][’title’]

장고로부터 데이터 하나를 json형식으로 추출해보기

#shell
python manage.py dumpdata posts --indent=2 > posts_data.json
  • dumpdata: 데이터를 파일로 추출하는 명령어
  • —indent=2: 두 단어마다 엔터

생성된 json 파일 안에서 데이터 늘리기

json 파일을 데이터베이스에 업로드하기

#shell
python manage.py loaddata posts_data.json

대량 시드데이터가 필요할 때 → Django seed

Django seed

  • Model에 정의된 각각의 Field를 보고 임의의 데이터를 자동으로 생성해주는 패키지

settings.py의 INSTALLED_APPS에 django_seed 추가

#settings.py
...
INSTALLED_APPS = [
...
'django_seed',
]

shell에 명령어 입력하여 씨드 생성하기

#shell
python manage.py seed posts --number=50

뒤늦게 유효성 검증 추가

기존에 저장돼있던 데이터를 다시 유효성 검증에 맞추기

posts app 내에 validate_data.py 파일 생성

  1. 모든 포스트 데이터 가져오기

  2. 각각의 포스트 데이터를 보면서 내용 유효성 검증하기

  3. (예) 존재하면 안되는 글자가 있다면 해당 글자 제거

  4. 데이터 저장하기

from .models import Post

def validate_post():
    # 1. 모든 포스트 데이터 가져오기
    posts = Post.objects.all()
    # 2. 각각의 포스트 데이터를 보면서 내용 유효성 검증하기
    for post in posts:
        if '&' in post.content:
            print(post.id, '번 글에 &가 있습니다.')
    # 3. (ex) 존재하면 안되는 글자가 있다면 해당 글자 제거
            post.content = post.content.replace('&', '')
    # 4. 데이터 저장하기
            post.save()
        if post.dt_modified < post.dt_created:
            print(post.id, '번 글의 수정일이 생성일보다 과거입니다.')
            post.save()

shell 입장

python manage.py shell
>>> from posts.validate_data import validate_post
>>> validate_post()

페이지네이션

shell 입장

python manage.py shell

장고에서 제공하는 페이지네이터 객체 가져오기

>>> from django.core.paginator import Paginator

데이터베이스에서 포스트 가져오기

>>> from posts.models import Post
>>> posts = Posts.objects.all()
>>> posts.count() #post 수 확인

페이지네이터로 페이지 나누기

>>> posts = Paginator(posts, 6) #6 포스트마다 페이지 나누기
>>> pages.page_range #페이지 수 확인

페이지 function들

>>> page = pages.page(1)
  • 특정 페이지 가져오기: page.object_list
  • 이 페이지 뒤에 또 페이지가 있는지 확인하기: page.has_next()
  • 이 페이지 전에 페이지가 있는지 확인하기: page.has_previous()
  • 이 뒤 페이지의 페이지 넘버 찾기: page.next_page_number()

Pagination 구현하기

  1. Django의 Paginator 가져오기
#views.py
...
from Django.core.paginators import Paginator
  1. post_list 뷰 수정하기
#views.py
...
def post_list(request):
    posts = Post.objects.all()
    **paginator = Paginator(posts, 6)
    curr_page_number = request.GET.get('page') #요청한 URL의 querystring 가져오기
    if curr_page_number is None:
        curr_page_number = 1
    page = paginator.page(curr_page_number)
    return render(request, 'posts/post_list.html', {'page':page})**
  • request.GET.get('<keyword>')
    • 현재 들어온 요청(request)의 쿼리스트링으로 부터 보여줄 페이지의 번호를 가져오기
    • 쿼리스트링의 페이지 번호에 대한 키워드 값은 'page’
  1. post_list 템플릿 수정하기
#post_list.html
...
		{% if page.object_list %} 
        <div class="post_container">
            {% for post in page.object_list %}
            <div class="post"><a href={% url 'post-detail' post.id %}>         
                <h2 class="title">{{post.title}}</h2>
                <p class="date">{{post.dt_created}}</p>
                <p class="text">{{post.content|slice:':100'}}</p>
            </a></div>
            {% endfor %}
        </div>
        <div class="paginator">
            {% if  page.has_previous %}
                <a href="?page=1" class="first">first</a>
                <a href="?page={{page.previous_page_number}}" class="prev">prev</a>
            {% endif %}
                <span>
                    <p>{{page.number}} of {{page.paginator.num_pages}}</p>
                </span>
            {% if page.has_next %}
                <a href="?page={{page.next_page_number}}" class="next">next</a>
                <a href="?page={{page.paginator.num_pages}}" class="last">last</a>
            {% endif %}
        </div>
	   {% else %}

Pagination 메소드

shell에서는 자동 사용 가능

하지만 code 파일에서는 views.py에서 직접 구현해야 함

Pagination 메소드

Class-Based Views

class: 변수와 함수의 집합 (예) models.py

  • 클래스는 함수와 다르게 상속 가능
    • class <클래스 이름>(<상속>):
    • 상속: 상속해주는 class의 모든 변수, 함수를 상속 받는 클래스에서 쓸 수 있음

클래스형 뷰(Class based view): 개발자들이 자주 쓸만한 view를 클래스로 만들어둔 것

  • 클래스형 뷰를 이용해서 Django에서 제공하는 view를 상속받기만 하면, 대부분의 로직 완성 가능
    • (ex) Django는 CRUD 각각의 기능의 클래스형 뷰 제공
  1. Django에서 Views 가져오기
#views.py
...
from django.core.paginator import Paginator

class PostCreateView(View):
    def get(self, request):
        post_form = PostForm()
        return render(request, 'posts/post_form.html', {'form':post_form})

    def post(self, request):
        post_form = PostForm(request.POST) #유저가 입력하자마자 바인딩
        if post_form.is_valid():
            new_post = post_form.save()
            return redirect('post-detail', post_id=new_post.id)
        return render(request, 'posts/post_form.html', {'form':post_form})
...
  1. urls.py에서 연결 수정하기
#urls.py
...

urlpatterns = [
    ...
    path('posts/new', **views.PostCreateView.as_view()**, name='post-create'),
    ...
]
  • views.PostCreateView.as_view()
    • class는 꼭 as_view()를 붙여서 불러야 함

Generic View

Generic View: 개발자들이 자주 쓸만한 view를 하나의 형태로 만들어둔 것

  • 자주 사용하는 기능이 구현돼 있음
  • 제네릭 뷰를 상속하면 원하는 기능을 빠르게 짜임새 있는 구조로 제작가능
  1. views.py에 Django가 제공하는 generic CreateView 가져오기

  2. views.py에 Django가 제공하는 reverse 가져오기

#views.py
...
from django.views.generic import CreateView
from django.urls import reverse
  1. Create View 구현
#views.py 
...
class PostCreateView(CreateView):
    model = Post
    form_class = PostForm
    template_name = 'posts/post_form.html' #렌더할 템플릿

		#정상적으로 작성이 된 후, 이동할 URL을 제공하는 기능
    def get_success_url(self):
        return reverse('post-detail', kwargs={'post_id':self.object.id})
  • reverse
    • 인자로 입력된 url name으로부터 거슬러올라가서 url을 찾는다
    • kwargs (keyword arguments): 사전형으로 키워드를 이용해서 값을 전달할 때 사용하는 인자
    • self.object: 현재 새로 생성된 포스트 데이터 객체에 접근 가능

A. 포스트 목록 뷰를 제네릭 뷰로 생성하기

  1. views.py에 Django가 제공하는 generic ListView 가져오기
#views.py
...
from django.views.generic import CreateView, ListView
  1. PostListView 클래스 구현하기
#views.py
class PostListView(ListView):
    model = Post
    template_name = 'posts/post_list.html'
    context_object_name = 'posts'
    #글 최신순으로 정렬하기 
    ordering = ['-dt_created']
    paginate_by = 6 
    page_kwarg = 'page' #쿼리스트링에서 찾아야할 키워드
  • -dt_created
    • 최신순으로 정렬
    • 앞의 ‘-’을 지우면 오름차순으로 정리
  1. post-list.html 수정

  2. urls.py에서 PostListView.as_view() 로 변경

B. 포스트 디테일 뷰를 제네릭 뷰로 생성하기

  1. views.py에 Django가 제공하는 generic ListView 가져오기
#views.py
...
from django.views.generic import CreateView, ListView, DetailView
  1. PostDetailView 클래스 구현하기
#views.py
class PostDetailView(DetailView):
    model = Post
    template_name = 'posts/post_detail.html'
    pk_url_kwarg = 'post_id'
    context_object_name = 'post'
  • pk_url_kwarg: url에서 view로 넘겨주는 키워드
    pk_url_kwarg = <데이터를 조회할 조건(key)>
  • context_object_name
    • 설정하지 않으면 object_list 라는 변수에 객체를 할당
  1. urls.py에서 연결 변경

C. 포스트 수정 뷰를 제네릭 뷰로 생성하기

  1. views.py에 Django가 제공하는 generic UpdateView 가져오기
#views.py
...
from django.views.generic import CreateView, ListView, DetailView, UpdateView
  1. PostUpdateView 클래스 구현하기
#views.py
class PostUpdateView(UpdateView):
    model = Post
    form_class = PostForm
    template_name = 'posts/post_form.html'
    pk_url_kwarg = 'post_id'

    def get_success_url(self):
        return reverse('post-detail', kwargs={'post_id':self.object.id})
  1. urls.py에서 연결 변경

D. 포스트 삭제 뷰를 제네릭 뷰로 생성하기

  1. views.py에 Django가 제공하는 generic DeleteView 가져오기
#views.py
...
from django.views.generic import CreateView, ListView, DetailView, UpdateView, DeleteView
  1. PostDeleteView 클래스 구현하기
#views.py
class PostDeleteView(DeleteView):
    model = Post
    template_name = 'posts/post_confirm_delete.html'
    pk_url_kwarg = 'post_id'
    context_object_name = 'post'
    def get_success_url(self):
        return reverse('post-list')
  1. urls.py에서 연결 변경

Generic View의 CONTEXT 파헤치기

ViewTemplate으로 전달되는 데이터예시
ListView‘object_list’, ‘<모델명>_list’‘post_list’
DetailView'object’, ‘<모델명>’'post’
CreateView--
UpdateView'object’, ‘<모델명>’‘post’
DeleteView'object’, ‘<모델명>’'post’

context_object_name: view에서 template으로 전달하는 model 데이터에 대한 이름 지정

  • 따로 지정하지 않으면 두번째 칼럼의 이름으로 전달
  • 지정하면 그 키값으로 전달

ListView에서 template으로 전달하는 context

{
	'paginator': <Django에서 제공하는 Paginator 객체>, #default으로 None값
	'page_obj': <Page 객체>
	'is_paginated': False, 
	'object_list': [데이터 목록이 담겨 있는 QuerySet], 
	'<모델명>_list': [데이터 목록이 담겨 있는 QuerySet],
}

예시)

  • paginated_by = 6
    • ‘paginator’ : Paginator 객체
    • ‘page_obj’ : 현재 Page 객체
    • ‘is_paginated’ : True
    • ‘object_list’ & ‘<모델명>_list : 현재 Page에 있는 데이터 목록

A. ListView의 기본값

  • template_name = ‘<app명>/<모델명>_list.html’
  • context_object_name = ‘post_list’
  • page_kwarg = ‘page’

간단한 코드 버전

class PostListView(ListView):
    model = Post
    ordering = ['-dt_created']
    paginate_by = 6

list view가 pagination을 적용한 데이터의 목록을 object_list라는 키로 템플릿에 전달

  • (page_list.html) page_obj.object_list 대신에 object_list 사용 가능

B. DetailView의 기본값

  • template_name = '<app명>/<모델명>_detail.html’
  • pk_url_kwarg = ‘pk’
    • pk: primary key의 약자로 다른 데이터와 구분되게 하는
  • context_object_name = ‘post’
class PostDetailView(DetailView):
		model = Post
		#pk_url_kwarg = 'post_id' **--> urls.py에서 post_id를 pk로 수정하면 삭제 가능**

C. CreateView의 기본값

  • template_name = '<app명>/<모델명>_form.html’
class PostCreateView(CreateView):
    model = Post
    form_class = PostForm #'form' 키워드로 접근 가능

    def get_success_url(self):
        return reverse('post-detail', kwargs={'pk':self.object.id})

D. UpdateView의 기본값

  • template_name = '<app명>/<모델명>_form.html’
class PostUpdateView(UpdateView):
    model = Post
    form_class = PostForm

    def get_success_url(self):
        return reverse('post-detail', kwargs={'pk':self.object.id})

E. UpdateView의 기본값

  • template_name = '<app명>/<모델명>_confirm_delete.html’

Indew 뷰를 Generic Class로 수정하기

ReverseView 가져오기

#views.py
...
from django.views.generic import (
    CreateView, ListView, DetailView, UpdateView, DeleteView, RedirectView
)

IndexRedirectView 만들기

#views.py
class IndexRedirectView(RedirectView):
    pattern_name = 'post-list'

urls.py에서 연결 수정하기

...
urlpatterns = [
    path('', views.IndexRedirectView.as_view(), name='index'),
		...
]

Generic View 정리하기

Generic View

Base Views

  • RedirectView: 들어온 요청을 새로운 URL로 이동시키는 기능 Untitled
  • TemplateView: 주어진 템플릿을 렌더링해서 보여주는 기능 Untitled

Display Views

  • DetailView: 하나의 데이터를 보여주는 기능 (예) 상세 보기 Untitled
  • ListView: 여러 데이터를 보여주는 기능 (예) 목록 보기 Untitled

Editing Views

  • FormView: Form을 렌더링해서 보여주는 기능 Untitled
  • CreateView: 새로운 데이터 생성 Untitled
  • UpdateView: 기존 데이터 개체의 수정 Untitled
  • DeleteView: 기존 데이터 삭제 기능 Untitled

Context 정리하기

Context: View에서 Template으로 전달되어 렌더링시 사용할 수 있는 사전형 데이터 변수

  • (함수형 뷰 이용시) render 함수의 세번째 파라미터

모델(Model) 데이터

  • 기본적으로 모델 데이터는 Template에 context으로 전달
  • 하나의 데이터를 다루는 view: ‘object’라는 키워드로 전달
  • 여러개의 데이터를 다루는 view: ‘object_list’라는 키워드로 전달
  • 위와 같은 데이터를 ‘model’ 클래스 변수에 명시한 Model을 보고 소문자로 변형해 함께 전달
    • 하나의 데이터를 접근하기 위해 ‘object’/’object_list’ 키워드 또는 ‘post’/’post_list’ 키워드 사용 가능
      • (예) {{object.title}} == {{post.title}}
      • (예) {{object_list}} == {{post_list}}
  • context_object_name을 명시할 경우
    • context_object_name = ‘posts’ 일 때: ‘post_list’가 ‘posts’로 변경되어 전달
    • Post 데이터목록이 ‘object_list’와 ‘posts’로 전달

그 외 Context 목록


참고: 코드잇 장고 강의

profile
우당탕탕

0개의 댓글