[Django] Model

6 minute read


DJANGO WEB DEVELOPMENT

4. Model


Model

  • 사용할 데이터 고려하여 데이터 모델링 후, 이에 맞는 field 작성
# 'foods' directory의 models.py
from django.db import models

# Create your models here.
class Menu(models.Model):
    name = models.CharField(max_length=50)
    description = models.CharField(max_length=100)
    price = models.IntegerField()
    img_path = models.CharField(max_length=255)
    
    def __str__(self):
        return self.name

Django Model Field

  • Field - 데이터 테이블에서의 열(column), 즉 데이터의 속성을 의미
  • CharField - 제한된 길이의 문자열을 위한 필드
    • class CharField(max_length=50)
      • max_length 필수 인수이며 입력할 최대 길이를 설정
  • IntegerField - 정수 값을 위한 필드이며 -2147483648 부터 2147483647 범위를 지원
    • class IntegerField()
  • BooleanField - Boolean 값을 위한 필드
    • class BooleanField()
  • DateField - 파이썬의 datetime.date 객체 형태로 표시되는 날짜 필드
    • class DateField(auto_now=False, auto_now_add=False)
      • auto_now - true로 설정되면 해당 객체가 변경(save) 될 때마다 자동으로 필드 값을 지금으로 수정 (마지막 수정 시간 등)
    • auto_now_add
      • 모델이 처음 생성될 때 한 번, 자동으로 필드 값을 지금으로 설정, ‘생성된 시간’을 저장하기 위해 많이 사용
  • DateTimeField - 파이썬의 datetime.datetime 객체 형태로 표시되는 날짜 필드, DateField와 인수 옵션은 같음
    • class DateTimeField(auto_now=False, auto_now_add=False)
  • EmailField - CharField의 하위 클래스로 문자열이 이용 가능한 이메일 주소인지 EmailValidator를 통해 확인
    • https://docs.djangoproject.com/en/3.1/ref/validators/#django.core.validators.EmailValidator
  • FileField - 파일 업로드를 위한 필드
    • class FileField(upload_to=None, max_length=100)
      • upload_to 업로드될 경로를 지정하는 필드로 Storage.save() 함수로 값이 전달되어 저장됨
      • https://docs.djangoproject.com/en/3.1/ref/files/storage/#django.core.files.storage.Storage.save
  • ImageField - FileField를 상속하여 구현되는 파일 업로드를 위한 필드이며 기본적으로 최대 길이가 100인 문자열 형식으로 생성됨, FileField에서 업로드된 파일이 정상적인 이미지 파일인지 확인하는 과정이 추가된 필드로 이미지 처리를 위한 Pillow 라이브러리가 필수적으로 필요
    • class ImageField(upload_to=None, height_field=None, width_field=None, max_length=100)
    • height_field & width_field 객체가 저장 될 때 이미지의 높이와 너비값이 자동으로 채워짐
  • GenericIPAddressField - IP 주소 체크
  • JSONField - JSON 파일을 위한(Django 3.1 버전 이상부터 지원) 필드
  • FilePathField - 특정 폴더의 파일 패스를 표현하는 필드
  • URLField - URL을 위한 필드
  • 그 외 필드 - https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.Field.default

Django Model Field 옵션

  • null - 기본 값은 False이며 Null 값 허용 여부를 선택
    • Field.null
    • ’ ‘ (빈 문자열)과 Null 모두 해당 필드의 데이터가 없다는 것을 의미
    • 따라서 null 옵션을 적용할 필드가 문자열 기반 필드 (Char, Text)일 경우에는 주의해서 사용해야 함
    • 일반적으로 데이터가 없다는 것을 의미하는 값은 하나여야 하므로 Django는 문자열 기반 필드가 데이터가 없음을 표시할 때는 ‘ ‘(빈 문자열)을 사용하도록 권장
  • blank - 기본 값은 False이며 True로 설정한 경우 필드 값을 빈 값으로 설정할 수 있음
    • Field.blank
    • null은 온전히 데이터베이스와 관련된 사항이고 blank는 데이터의 유효성 검사와 관련된 옵션
    • 예를들어 blank가 True라면 해당 필드에 데이터를 입력하지 않아도 유효성 검사를 통과하게 됨
  • default - 필드의 기본값을 설정하는 옵션으로 값 또는 함수가 들어갈 수 있음
    • Field.default
  • db_column
    • Field.db_column
    • 해당 필드에 사용할 데이터베이스 속성 명을 지정, 따로 지정하지 않을 경우 일반적으로 필드의 이름을 사용
  • 그 외 옵션 - https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.Field.default

Migrations

* 모델(Model)의 변경 사항, 즉 Django 프로젝트의 데이터 구조 변경 사항을 관리하기 위한 Django만의 관리 방법
* 변경될 때마다 히스토리를 하나씩 만들어 두고 마치 블럭을 갈아 끼우듯 생성한 히스토리를 실제 데이터베이스에 반영
* 이 모든 과정은 Django의 ORM(Object-Relational Mapping)을 통해 진행됨
* makemigrations 
    * 모델의 변경 사항을 인식해서 새로운 마이그레이션을 만듦, 이때 마이그레이션은 각 앱 디렉토리 내 migrations 디렉토리 안쪽에 생성
* migrate
    * 생성된 최신 버전의 마이그레이션을 데이터베이스에 반영, 
    * 만약 이전 마이그레이션으로 되돌리고 싶다면 python manage.py migrate [app_name] {migration_number}를 사용할 수 있음
* showmigrations
    * 현재 django 프로젝트의 모든 마이그레이션과 반영 상태를 나타냄
    * 특정 앱에 대한 것만 보고 싶다면 python manage.py showmigrations [app_name]을 사용할 수 있음
* sqlmigrate
    * 인수로 넘겨준 마이그레이션이 ORM을 통해 변경된 SQL문을 출력
    * sqlmigrate를 통해 모델이 의도한 대로 SQL문으로 변경되어 데이터베이스에 반영되었는지 확인할 수 있음
* 공식 문서 : https://docs.djangoproject.com/en/2.2/topics/migrations/
# CMD
# 생성/변경된 model을 django에 알려주기
python manage.py makemigrations

# 장고에서 제공하는 여러 데이터구조 반영
python manage.py migrate

# Migration 목록 보기, [X]는 Django에 반영이 된 것을 의미
python manage.py showmigrations

# Migration 파일이 어떻게 OMR을 통해 어떻게 SQL로 변하게 되었는지 보기(python manage.py sqlmigrate [app_name] [migration_number])
python manage.py sqlmigrate foods 0001

데이터 CRUD

  • Django는 Model을 정의하면 ORM을 통해서 데이터베이스에 데이터를 추가하고(Create) 조회하고(Read) 수정하고(Update) 삭제(Delete)할 수 있도록 하는 편리한 기능을 제공
  • 이 기능들은 Model의 ‘objects’를 이용해서 수행하게 됨
  • 쉽게 말해, objects는 Model을 정의하면 생성되는 데이터베이스 관리 매니저로 데이터베이스와 관련된 편리한 기능을 제공하는 객체

데이터 Create

* Django Shell에서 진행
* Django 쉘은 Django 프로젝트를 쉘에서 바로 접근 할 수 있도록 하는 환경 설정이 더해진 것
# CMD
# Django Shell 실행
python manage.py shell

# 앱의 모델 가져오기
from foods.models import Menu

# 데이터 저장 방법 1 '[model].objects.create([field_name=value, ...])'
Menu.objects.create(name=코딩에 빠진 ,
... description=주머니가 가벼운 당신의 마음까지 생각한 가격,
... price=10000,
... img_path=foods/images/chicken.jpg)

# 잘 저장되었는지 확인 - 모든 데이터 조회 ''
Menu.objects.all()

# 상세 데이터 확인
Menu.objects.all().values()

# Django Shell 종료
exit()

Data Read

  • order_by - 정렬 후 데이터 Read
  • count - 데이터의 row 개수 Read
  • exclude - 특정 조건 제외하고 데이터 Read
  • get + 조건 키워드 - 하나의 데이터만 Read, 여러개인 경우 error 발생
  • filter + 조건 키워드 - 여러 데이터를 Read
  • 조건 키워드 - 필드명__조건키워드 = “조건”
    • __contains - 일정 문자열을 포함한 데이터 Read
    • __icontains - 지정한 문자열을 대소문자를 구분하지 않고 데이터 Read
    • __range - 일정 범위의 데이터 Read
    • __exact - 대소문자를 구분해서 조건과 정확히 일치 하는지 데이터 Read
    • __iexact - 대소문자를 구분 하지 않고 일치하는 데이터 Read
    • __lt - less than
    • __lte - less than or equal
    • __gt - greater than
    • __gte - greater than or equal
    • __in - 주어진 리스트 안에 존재하는 자료를 조회

    • 그 외 : https://docs.djangoproject.com/en/2.2/ref/models/querysets/#field-lookups
# Django Shell 실행 후, foods 앱의 Menu Class 임포트 후

# 'price' 가격만 Read
Menu.objects.all().values('price')

# 'price'에 따라 오름차순으로 정렬하고 데이터 Read
Menu.objects.order_by('price') # 내림차순 Menu.objects.order_by('-price')

# 전체 데이터 개수 세기 '[model].objects.count()'
Menu.objects.count()

# 데이터 중 "코딩에 빠진 닭"을 제외하고 데이터 Read
Menu.objects.exclude(name="코딩에 빠진 닭")

# id가 1인 데이터 가져오기
Menu.objects.get(id=1)

# 음식 이름 중에 "코"가 들어간 모든 데이터 Read
Menu.objects.filter(name__contains = "코")

# 음식 가격이 2000원에서 10000원 사이인 데이터 Read
Menu.objects.filter(price__range=(2000,10000))

# 대소문자를 구분하지 않고 음식의 이름(name)이 'chicken'인 데이터를 모두 조회
Menu.objects.filter(name__iexact='chicken')

# age가 25세 이상인 데이터 조회
Menu.objects.filter(age__gt=25)

# age가 21세, 25세, 27세인 데이터 조회
Menu.objects.filter(age__in=[21,25,27])

# 참고
# price가 10000원인 데이터를 name에 따라 Read (방법1)
Menu.objects.filter(price=10000).order_by('name')

# price가 10000원인 데이터를 name에 따라 Read (방법2) - 가독성이 좋기에 방법2 추천
data = {model}.objects.filter(price=10000)
data = data.order_by('name')

Data Update

# Django Shell 실행 후, foods 앱의 Menu Class 임포트 후

# data에 변경하고 싶은 데이터 저장
data = Menu.objects.get(id=1)

# 확인
data

# data update(데이터 이름을 "코빠닭'으로 수정)
data.name = "코빠닭"

# 확인

# 저장
data.save()

# 전체 데이터 조회해서 저장 잘 되었는지 확인
Menu.objects.all()

Data Delete

# Django Shell 실행 후, foods 앱의 Menu Class 임포트 후

# data에 삭제하고 싶은 데이터 저장
data = Menu.objects.get(id=3)

# 확인
data

# 삭제
data.delete()

# 전체 데이터 조회해서 잘 삭제되었는지 확인
Menu.objects.all()

Queryset

  • Django의 ORM을 이용해 database와 소통할 때 발생하는 자료형으로 파이썬의 list와 유사
  • Python code를 작성하면(ex. Menu.objects.all().values(‘price’))
    • Django의 ORM이 이를 자동으로 SQL로 변환
    • 이를 바탕으로 database와 소통
    • 그 결과 조회된 data가 QuerySet으로 반환
    • 이 QuerySet을 리스트처럼 다루어 변환 가능
# 작성된 파이썬 코드가 어떻게 SQL로 변했는지 확인

# Django Shell 실행 후, foods 앱의 Menu Class 임포트 후

# SQL로 변환된 것을 확인하고 싶은 Python code 작성
data = Menu.objects.filter(name__contains="코")

# SQL 확인
print(data.query)

Admin 도구 사용

# CMD
python manage.py createsuperuser
# 사용자 이름, 이메일, 비밀번호 입력

# 관리자 페이지에서 작업 원하는 model 추가
# 'foods' directory의 admin.py 파일

from foods.models import Menu

admin.site.register(Menu)

# 서버 실행 후, localhost:8000/admin 으로 접속 

Model 적용하기

  • 사례1

# 'foods' directory의 views.py 파일

# 모델 import
from foods.models import Menu

# index 함수 수정
def index(request):
    context = {}
    today = datetime.today().date
    menus = Menu.objects.all()
    context["date"] = today
    context["menus"] = menus
    return render(request, "foods/index.html", context=context)

# 'foods/templates/foods/' directory의 index.html 파일
...
{% for menu in menus %}
    <div class="food">
          <img src={% get_static_prefix %}{{menu.img_path}} width="300px" height="200px"/>
          <div class="info">
            <h3>{{menu.name}}</h3>
            <P>{{menu.description}}</p>
            <a href="#">메뉴 보기</a>
          </div>
        </div>
  {% endfor %}
...

  • 사례 2

# 'menus' directory의 views.py 파일
# index 함수 수정
from django.shortcuts import render
from datetime import datetime
from .models import Menu

def index(request):
    today = datetime.now().date()
    context = {'today': today}
    menus = Menu.objects.all()
    context['menus'] = menus
    return render(request, 'menus/index.html', context)

# 'menus/templates/menus' directory의 index.html 파일
# for문 넣기
...
<div class="food_container">
   {% for menu in menus %}
      <div class="food">
         <img src={%get_static_prefix%}{{menu.img_path}}>
         <div class="info">
             <h3>{{menu.name}}</h3>
             <p>{{menu.description}}</p>
             <a href="#">메뉴 보기</a>
          </div>
        </div>
   {% endfor %}
</div>
...

# 'menus' directory의 urls.py 파일
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index),
    path('food/<int:pk>/', views.detail)
]

# 'menus' directory의 views.py 파일
# detail 함수 추가
def detail(request, pk):
    menu = Menu.objects.get(id=pk)
    return render(request, 'menus/detail.html', {'menu': menu})

# 'menus/templates/menus' directory의 detail.html 파일
...
<div class="menu_img">
   <img src={%get_static_prefix%}{{menu.img_path}}>
</div>
<div class="menu_info">
   <h3>{{menu.name}}</h3>
   <h4>{{menu.name_eng}}</h4>
   <p class="menu-desc">{{menu.description}}</p>
   <p class="price">&#8361;{{menu.price}}</p>
</div>
<div class="back-main">
   <a href="/menus">돌아가기</a>
</div>
...

# 'menus/templates/menus' directory의 index.html 파일
...
<a href="food/{{menu.id}}">메뉴 보기</a>
...