My Profile Photo

DongChanS's blog


수학과 학생의 개발일지


쟝고(Django) 2편 - Model

이 포스트는 Django 2버전을 기준으로 하여 작성되었습니다.

1) Django ORM- Model

저번에는 Diango MTV 중에서 T(Templates)와 V(View)에 대해서만 다뤘는데,

오늘은 데이터를 관리해주는 역할을 하는 M(Model)에 대해서 알아보겠습니다.

1-1. 기본 셋팅

일단 DB라는 폴더 안에 가상환경 -> 그 안에 db라는 프로젝트를 만들고, articles라는 앱을 만들어보자.

(이전편 참고)

성공적으로 만들었다면 이런식의 폴더구조가 나올 것이다.

(db-venv) start666:~/workspace/DB $ tree
.
├── articles
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── db
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
└── manage.py

여기서 articles 앱 안의 models.py에 들어가보면 비어있는 것을 확인할 수 있다.

models.py는 앞으로 사용될 데이터베이스의 생김새에 대한 내용을 정의하는 곳이다.

즉 테이블의 schema들만 따로 저장하는 곳이다.

이번에 우리는 articles라는 게시판 역할을 하는 테이블을 만들고 싶다.

1-2. Schema 만들기

이런 sql문을 토대로 하는 테이블을 만들어보자.

CREATE TABLE articles (
	id INTEGER PRIMARY KEY,
    title TEXT,
    content TEXT
)

이것을 models.py에서는 이렇게 만들 수 있습니다.

from django.db import models

# Create your models here.
class Article(models.Model):
    title = models.TextField()
    content = models.TextField()
  1. models.Model을 상속받는 클래스(플라스크의 db.Model과 비슷함)

  2. django는 똑똑하게 id를 넣지 않아도 자동으로 인식한다.

    그러므로 title, content 컬럼에 해당하는 스키마만 만들어줘도 된다.

  3. TextField() : 텍스트 자료형을 쓰겠음을 명시하는 것과 같다.

  4. 테이블 이름을 정해줄 필요가 없다 (자동으로 쟝고가 정해줌. ex: Article 클래스 -> articles 테이블)

이와 비슷하게 쟝고에는 필드를 통해서 데이터타입을 지정하는데 아래처럼 필드의 종류가 굉장히 많으니 필요한 데이터타입에 따라서 적절한 필드를 검색해보는게 편할것이다.

101

1-3. migrations

이렇게 테이블의 스키마를 정의했으면 이제 쟝고에게 청사진을 넘겨줘야한다.

  1. makemigrations

    그 작업을 makemigrations라는 스크립트를 통해서 실행가능하다.

    (db-venv) start666:~/workspace/DB $ python manage.py makemigrations
    Migrations for 'articles':
      articles/migrations/0001_initial.py
        - Create model Article
    
    ├── articles
    ...
    │   ├── migrations
    │   │   ├── 0001_initial.py
    │   │   ├── __init__.py
    ...
    ├── db
    │   ├── __init__.py
    ...
    

    그러면 migrations 폴더가 생겼음을 확인할 수 있다.

    • migrate는 Git같이 디비의 버전관리를 하는 느낌이다. 만약 잘못되었다면 이전 migrate로 돌아갈 수 있게하기 위해서 모델상태를 지속적으로 저장한다.
    • 이런 migration number(지금은 0001)를 통해서 버전을 관리하기 때문에 직접 db.sqlite3안에 들어가서 테이블을 지우거나 변경하는 것은 좋지 않다.

    생성된 0001_initial.py 폴더를 보게 되면,

    # Generated by Django 2.1.7 on 2019-02-14 01:36
    from django.db import migrations, models
       
    class Migration(migrations.Migration):
        initial = True
        dependencies = [
        ]
       
        operations = [
            migrations.CreateModel(
                name='Article',
                fields=[
                    ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                    ('title', models.TextField()),
                    ('content', models.TextField()),
                ],
            ),
        ]
    

    그러면 자동으로 id라는 컬럼이 만들어진 것을 확인할 수 있다.

    다른건 잘 모르겠지만 primary_key의 역할을 하는 것을 알 수 있다. 아마도 auto_created는 AUTOINCREMENT가 아닐까 조심스레 추측해본다.

  2. sqlmigrate

    그러면 이제 스키마를 한번 확인해보자.

    스키마를 확인하기 위해서는 sqlmigrate라는 스크립트를 시행해주어야 한다.

    (db-venv) start666:~/workspace/DB $ python manage.py sqlmigrate articles 0001                                         
    BEGIN;
    --
    -- Create model Article
    --
    CREATE TABLE "articles_article" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" text NOT NULL, "content" text NOT NULL);
    COMMIT;
    

    보시면 알겠지만 디폴트값이 not null이다!!

    하지만 원래 있었던 db.sqlite3 파일을 확인해보면 아직 테이블이 저장되어있지 않다.

    (db-venv) start666:~/workspace/DB $ sqlite3 db.sqlite3
    SQLite version 3.8.2 2013-12-06 14:53:30
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite> .tables
    sqlite> .exit
    

    이전까지 쟝고에게 넘겨주었던 청사진을 실제 디비에 적용하지 않았기 때문인데,

    이를 적용하는 것을 migration이라고 한다.

  3. migrate

    migration은 migrate라는 스크립트를 통해서 실행시킬 수 있다.

    (db-venv) start666:~/workspace/DB $ python manage.py migrate
    Operations to perform:
      Apply all migrations: admin, articles, auth, contenttypes, sessions
    Running migrations:
      Applying contenttypes.0001_initial... OK
      Applying auth.0001_initial... OK
      Applying admin.0001_initial... OK
      Applying admin.0002_logentry_remove_auto_add... OK
      Applying admin.0003_logentry_add_action_flag_choices... OK
      Applying articles.0001_initial... OK # !!!
      Applying contenttypes.0002_remove_content_type_name... OK
      Applying auth.0002_alter_permission_name_max_length... OK
      Applying auth.0003_alter_user_email_max_length... OK
      Applying auth.0004_alter_user_username_opts... OK
      Applying auth.0005_alter_user_last_login_null... OK
      Applying auth.0006_require_contenttypes_0002... OK
      Applying auth.0007_alter_validators_add_error_messages... OK
      Applying auth.0008_alter_user_username_max_length... OK
      Applying auth.0009_alter_user_last_name_max_length... OK
      Applying sessions.0001_initial... OK
    

    엄청 많은 것들이 적용되었다고 뜨는데..

    나머지는 잘 모르겠지만 articles.0001_initial이 적용된것을 확인할 수 있다.

    (db-venv) start666:~/workspace/DB $ sqlite3 db.sqlite3
    SQLite version 3.8.2 2013-12-06 14:53:30
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite> .tables
    articles_article            auth_user_user_permissions
    auth_group                  django_admin_log          
    auth_group_permissions      django_content_type       
    auth_permission             django_migrations         
    auth_user                   django_session            
    auth_user_groups          
    sqlite> 
    

이제 db.sqlite3 파일에서 확인해보면 엄청 많은 테이블이 있는데,

나머지들을 신경 쓸 필요 없이 articles_article 테이블만 보면 된다.

2) Django ORM

이제 articles 테이블을 Django의 법칙 안에서 다룰 준비가 전부 끝났다.

역시 Django에서도 데이터베이스의 기능을 파이썬 객체로 맵핑한 ORM이 존재하는데 이를 Django ORM이라고 한다.

하지만 파이썬 객체를 다루려면 그냥 sqlite3로는 안되고, 파이썬과 쟝고가 필요하다.

2-1. shell

파이썬과 쟝고의 휘하에서 db를 다루기 위해서는 shell 스크립트를 사용하면 된다.

(db-venv) start666:~/workspace/DB $ python manage.py shell
Python 3.6.7 (default, Feb 13 2019, 02:17:15) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 

실행해보면 파이썬 shell과 똑같이 생겼는데,

이건 파이썬에 쟝고의 환경이 로드되어 직접 런타임으로 데이터를 넣고뺄수 있는 좋은 shell이다.

여기서 파이썬 언어처럼 Article 클래스를 들고와서 객체를 다룰 수 있다.

( DB 폴더 입장에서는 articles.models.py에 Article클래스가 있다. )

>>> from articles.models import Article
>>> a = Article(title="happy", content="hacking")
>>> a.save()

이제 하나의 row를 생성해서 저장하였다.

보시면 알겠지만 SQLAlchemy와는 달리 세션을 통해서 하는게 아닌 save를 객체의 메서드로도 충분하다.

정확히 말하면 세션을 숨겨놓은 것인데,

SQLAlchemy는 사용자에게 각 세션을 할당하여 세션을 다룰 것을 요구했다면,

Django는 조금 더 큰 프레임워크이기 때문에 세션을 다루는 것도 자체적으로 해주기 때문이다.

물론 이런식으로도 row를 생성할 수 있다.

>>> Article.objects.create(title="hey", content="create")
제목: hey, 내용 : create

하지만 save() 했는지 확인해야할 필요가 있기 때문에 보통 위의 방법을 추천한다.

2-2. CRUD 해보기

  1. Read : 모든 데이터를 가져오고싶으면?

    >>> Article.objects.all()
    <QuerySet [<Article: Article object (1)>]>
    

    SQLAlchemy에서 query라고 적었던 것을 obejcts라고만 적으면 거의 비슷하다.

    QuerySet이라고 나오는데 이 쿼리셋은 리스트의 상위호환이라고 생각하면 된다.

    따라서 인덱싱을 통해서 각 원소에 접근할 수 있다.

    역시 len함수도 사용가능하다.

    >>> Article.objects[0]
    <Article: Article object (1)>
    >>> Article.objects[0].title
    'happy'
    >>> len(Article.objects.all())
    2
    

    각 row는 인스턴스이므로 값을 보기 위해서는 직접 멤버변수에 접근하면된다.

    >>> a = Article(title="와 여기서도 글이 써진다!", content="장고 짱짱맨")
    >>> a.save()
    >>> Article.objects.all()
    <QuerySet [<Article: Article object (1)>, <Article: Article object (2)>]>
    

    그런데 보여지는 모습이 뭔가 적응이 잘 안된다.

    역시 Article은 클래스이기 때문에 repr이나 str을 통해서 인스턴스를 보여줄 모습을 정의할 수 있다.

    다시 models.py로 돌아가서 repr을 작성해보자.

  2. repr & str

    class Article(models.Model):
        title = models.TextField()
        content = models.TextField()
           
        def __repr__(self):
            return f"제목: {self.title}, 내용 : {self.content}"
           
        def __str__(self):
            return f"제목: {self.title}, 내용 : {self.content} 출력됨!"
    

    그러면 다시 쉘을 껐다가 켜주면 원하는대로 값들이 보여진다.

    >>> Article.objects.all()
    <QuerySet [제목: happy, 내용 : hacking, 제목:  여기서도 글이 써진다!, 내용 : 장고 짱짱맨]>
    >>> Article.objects.all()[1]
    제목:  여기서도 글이 써진다!, 내용 : 장고 짱짱맨 # 인스턴스 그자체는 __repr__
    >>> for a in Article.objects.all():
    ...     print(a)
    ... 
    제목: happy, 내용 : hacking 출력됨!
    제목:  여기서도 글이 써진다!, 내용 : 장고 짱짱맨 출력됨 # 프린트는 __str__
    
  3. where : filter

    조건문을 넣기 위해서는 filter메서드를 사용하면 된다.

    >>> Article.objects.filter(title="happy").all()
    <QuerySet [제목: happy, 내용 : hacking]>
    
    >>> Article.objects.filter(title="happy").count()
    1
    

    SQLAlchemy와 매우 비슷함을 확인할 수 있다.

    역시 get메서드도 제공한다.

    >>> Article.objects.get(id=1) # Article.objects.filter(id=1).first()와 비슷!
    제목: happy, 내용 : hacking
    

    그런데 두개가 다른게 있는데,

    get 메서드는 검색하고자 하는 값이 없으면 에러를 반환하지만,

    all이나 first메서드는 없으면 None을 반환한다.

  4. update & delete

    수정 : 인스턴스의 멤버변수를 수정한다 -> save

    삭제 : delete메서드를 통해 소멸자를 불러온다.

    >>> Article.objects.get(id=1)
    제목: happy, 내용 : hacking
    >>> a = Article.objects.get(id=1)
    >>> a.content = "happy Thursday"
    >>> a.save()
    >>> Article.objects.get(id=1)
    제목: happy, 내용 : happy Thursday
    
    >>> a = Article.objects.get(id=2)
    >>> a.delete()
    (1, {'articles.Article': 1})
    
  5. Sort : order_by

    >>> Article.objects.order_by('id').all()
    <QuerySet [제목: happy, 내용 : happy Thursday, 제목: title test, 내용 : content test, 제목: hey, 내용 : create]>
    >>> Article.objects.order_by('-id').all()
    <QuerySet [제목: hey, 내용 : create, 제목: title test, 내용 : content test, 제목: happy, 내용 : happy Thursday]
    

    “-id” 라고 하면 역순으로 정렬해서 제공한다.

    물론 그냥 뽑아도 쿼리셋이라는 클래스가 리스트와 비슷하기 때문에 리스트의 메소드를 이용해서 섞어도 무방하다.

    그런데 일반적으로 디비가 섞는걸 더 잘하는 경우가 많으니 디비 차원에서 섞는 것을 추천한다.

    (아마존 RDS 최하성능 디비처럼 디비 자체성능이 별로 안좋지 않다면…)

2-3. 요약

요약하자면 데이터베이스를 다루는 것은 이 과정으로 할 수 있다.

  1. 테이블의 schema 생성하기
  2. migrations 폴더에 schema 반영하기
  3. migrate 명령 시행하기
  4. Views.py에서 각 요청에 따른 데이터베이스 조작하기

Views.py에서 데이터베이스를 다루기 위해서는

from .models import Article

이런식으로 Article 테이블 클래스를 가져와서 Django ORM을 적절히 사용해서 원하는 요청에 따른 데이터베이스의 CRUD명령을 수행할 수 있다.


보너스1 : Admin

admin은 관리자사이트를 만들어주는 용도이다.

admin.py에 들어가게 되면 이런 주석이 보인다.

from django.contrib import admin

# Register your models here.

모델을 등록해달라고 하니까 models.py에 있는 아무 테이블을 등록해보자

from .models import Fish

# Register your models here.
admin.site.register(Fish)

이제 이 결과를 확인하기 위해서 createsuperuser 스크립트를 실행해보자.

(db-venv) start666:~/workspace/DB $ python manage.py createsuperuser

스크립트를 실행하면 유저네임, 이메일, 패스워드를 입력하는 창이 나오는데,

이메일은 넣을 필요가 없고, 유저네임과 패스워드를 입력해보자.

그리고 다시 서버를 돌려서 원래 있었던 /admin 주소로 들어가면

102

요상한 로그인창이 있으며, 로그인을 하면

103

우리의 테이블을 관리할 수 있는 창이 보여진다.

104

나의 Fish 테이블에 있는 오브젝트 두개가 보여진다.

그런데, 무슨 컬럼이 있는지 보고싶으면 다음과같이 명명할 수 있다.

class FishAdmin(admin.ModelAdmin):
    list_display = ('name','job')

# Register your models here.
admin.site.register(Fish,FishAdmin)

105

그러면 오브젝트의 각 컬럼별로 보여진다.

보너스2 : Discriptive programming

일반적으로 접했던 프로그래밍은 처음부터 하나하나 모든것을 명령해주는 형식이었다.

예를 들어서, 알고리즘 문제를 풀고싶으면 모든 예외케이스 하나하나 빠짐없이 처리해줘야하는것과 같이 모든 경우의 수를 생각하고 컴퓨터에게 명령을 내리는 방식이 될 것이다.

이런것을 명령형 프로그래밍이라고 하는데, 반대로 묘사형 프로그래밍(discriptive programming)이라는 것이 있다.

명령형 프로그래밍에서는 컴퓨터에 명령을 내리는 것 보다, 대략적으로 묘사를 내리는 느낌으로 동작하는데

예를들면 위의 admin.py에서

class FishAdmin(admin.ModelAdmin):
    list_display = ('name','job')

# Register your models here.
admin.site.register(Fish,FishAdmin)

list_display = ( ‘name’,’job’ ) 라는 단순히 ‘name’,’job’ 으로 리스트를 표현해주세요 하는 묘사만 컴퓨터에게 해주면 컴퓨터의 프레임워크가 알아서 구성을 해주는 것이다.

(물론 내부에서 구성을 해주는 로직이 따로 있겠지만, 사용자가 직접적으로 명령하는 부분은 적어서 묘사적 프로그래밍이라고 불리는 듯 하다.)

요즘 핫한 React나 Vue.js같은 언어는 이런 묘사형 프로그래밍 원칙을 따르고 있으니 여기에 잘 적응하는 것이 중요할 것 같다.

보너스3 : CSS파일을 쟝고가 인식하게 하는 법

  1. static 폴더를 만들고 css/style.css 파일을 만든다.

    프로젝트 안이 아니라 앱 폴더 안에서 만들면 된다.

    1

  2. 그리고 static/css 폴더 안에 style.css 파일을 만들고,

    style.css 폴더를 적용하기 위해서 html파일안에 link태그를 만들 수 있다.

       
    <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>
        <link rel="stylesheet" href="{% static 'css/style.css' %}">
    </head>
       
    

    이 말은 STATIC 이라고 지정된 곳을 불러와서 거기 안의 css/style.css 경로에 있는 링크를 참조하겠다는 뜻이다. (경로를 정확하게 명시하지 않는것을 보니 역시 이것도 위에서 배운 discriptive programming 느낌이 난다.)

  3. STATIC 이라고 지정된 곳의 위치는 우리 settings.py에 적혀있는데,

    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/2.1/howto/static-files/
       
    STATIC_URL = '/static/'
    

    기본값으로 /static/ 으로 STATIC_URL이 지정되어있기 때문에 우리가 static폴더를 만들면 쟝고가 그 안을 참조하는 것이다.

  4. 마지막으로 STATIC이라는 것을 불러오겠다고 선언해야한다.

    html파일 맨 위에 우리가 쟝고에게 STATIC을 load하겠다는 의도를 내포하자.

    django template 문법으로 load ‘static’ 이라고 적으면 된다.

    	
     {% load static %}
    
     <!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>
         <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">
         <link rel="stylesheet" href="{% static 'style.css' %}">
     </head>
    	
    
comments powered by Disqus