[Django] ORM

송수빈·2026년 5월 5일

SSAFY

목록 보기
8/15

ORM (Object Relational Mapping)

ORM

  • ORM
    • 객체 지향 프로그래밍 언어의 객체(Object)와 데이터베이스의 데이터를 매핑(Mapping)하는 기술
  • ORM의 역할
    • Django는 Python 언어를 사용하지만 데이터베이스는 SQL 언어를 사용함
    • ORM은 Django와 데이터베이스 사이에서 언어 번역자 역할을 수행
    • ORM은 Django 개발자를 위해 ‘QuerySet API’라는 특별한 도구를 제공함
      • QuerySet API는 ORM의 기능을 개발자가 Python 코드 안에서 객체 지향적이고 직관적인 방식으로 데이터베이스를 조작할 수 있도록 제공하는 인터페이스

QuerySet API

  • 데이터베이스의 복잡한 SQL 쿼리문을, 직관적인 Python 코드로 다룰 수 있게 해주는 강력한 번역기
    • 개발자는 SQL을 직접 작성하지 않고도, .filter(), .exclude(), .order_by() 등 파이썬다운 메서드를 사용하여 원하는 데이터를 손쉽게 생성, 조회, 수정, 삭제할 수 있다.
    • QuerySet API는 코드의 가독성을 높이고, 개발 생산성을 극대화하는 Django ORM의 핵심 기능이다.
  • QuerySet API와 ORM의 동작 방식
    1. Django → DB : Django(QuerySet API)에서 ORM을 통해 데이터베이스로 정보를 요청할 때
      • SQL 쿼리로 변환되어 데이터베이스로 전달됨
    2. DB → Django : 데이터베이스가 요청에 대한 응답을 보낼 때,
      • ORM은 이 SQL 결과를 다시 파이썬이 이해할 수 있는 Python Object
      • QuerySet 또는 Instance 형태로 변환하여 Django로 반환
  • QuerySet API 구문 기본 구조
    • Article (모델 클래스)
      • 역할: 데이터베이스 테이블에 대한 Python 클래스 표현
      • articles_article 테이블의 스키마(필드, 데이터 타입 등)를 정의하며, Django ORM이 데이터베이스와 상호작용할 때 사용하는 기본적인 구조체
    • .objects (매니저, manager)
      • 역할: 데이터베이스 조회(Query) 작업을 위한 기본 인터페이스
      • 모델 클래스가 데이터베이스 쿼리 작업을 수행할 수 있도록 하는 진입점
      • Django는 모든 모델에 objects라는 이름의 매니저를 자동으로 추가하며, 이 매니저를 통해 .all(), .filter() 등의 쿼리 메서드를 호출
    • .all() (QuerySet API 메서드)
      • 역할: 특정 데이터베이스 작업을 수행하는 명령
      • 매니저를 통해 호출되는 메서드로, 해당 모델과 연결된 테이블의 모든 레코드(rows)를 조회하라는 SQL 쿼리를 생성하고 실행
  • Query란?
    • 데이터베이스에 특정한 데이터를 보여 달라는 요청
    • “쿼리문을 작성한다.”
      • 원하는 데이터를 얻기 위해 데이터베이스에 요청을 보낼 코드를 작성한다.
    • Django에서 Query가 처리되는 과정 정리
      1. 파이썬 코드 → ORM: 개발자의 QuerySet API(파이썬 코드)가 ORM으로 전달
      2. ORM → SQL 변환: ORM이 이를 데이터베이스용 SQL 쿼리로 변환하여 데이터베이스에 전달
      3. DB 응답 → ORM: 데이터베이스가 SQL 쿼리를 처리하고 결과 데이터를 ORM에 반환
      4. ORM → QuerySet 변환: ORM이 데이터베이스의 결과를 QuerySet (파이썬 객체) 형태로 변환하여 우리에게 전달
  • QuerySet이란?
    • 데이터베이스에 전달받은 객체 목록(데이터 모음)
    • 순회 가능한 데이터로 1개 이상 데이터를 불러와 사용 가능함
    • Django ORM을 통해 만들어진 자료형
    • 단, 데이터베이스가 단일 객체를 반환할 때는 QuerySet이 아닌 모델(Class)의 인스턴스로 반환됨

QuerySet API 실습

  • QuerySet API 실습 사전 준비
    • 외부 라이브러리 설치 및 의존성 기록
      pip install ipython
      pip freeze > requirements.txt
      • ipython은 일반 파이썬 셸(명령창)보다 자동 완성 등 편리한 파이썬 작업 환경을 만들어주는 도구
    • Django Shell 접속하기
      python manage.py shell
      • Django Sell이란 Django 프로젝트의 코드를 명령창에서 바로 실행하고 테스트하는 특별한 파이썬 환경
      • Django 환경 내에서 실행되기 때문에 입력하는 QuerySet API 구문이 Django 프로젝트에 영향을 미침

CRUD

대부분의 소프트웨어가 가지는 기본적인 데이터 처리 기능인 생성(Create), 조회(Read), 수정(Upadate), 삭제(Delete)를 묶어 이르는 말

Create

  • 데이터 객체를 만드는(생성하는) 3가지 방법
    1. 빈 객체 생성 후 값 할당 및 저장

      article = Article()
      article.title = 'first'
      article.content = 'django!'
      article.save()
    2. 초기 값과 함께 객체 생성 및 저장

      article = Article(title='second', content='django!')
      article.save()
    3. create() 메서드로 한 번에 생성 및 저장

      Article.objects.create(title='third', content='django!')

Read

  • QuerySet 반환 메서드
    • all() : 전체 데이터 조회
    • filter() : 주어진 매개변수와 일치하는 객체를 포함하는 QuerySet 반환
  • QuerySet을 반환하지 않는 메서드
    • get() : 주어진 매개변수와 일치하는 객체를 반환 (Only one)
      • 객체를 찾을 수 없으면 DoseNotExist 예외를 발생시키고, 둘 이상의 객체를 찾으면 MultipleObjectsReturned 예외를 발생시킴
      • 위와 같은 특징을 가지고 있기 때문에 primary key와 같이 고유성(uniqueness)을 보장하는 조회에서 사용해야 함

  • filter(): QuerySet으로 반환 → iterable
  • get(): 인스턴스로 반환

Update

  • 데이터 수정 방법
    • 인스턴스 변수를 변경 후 save 메서드 호출
      # 수정할 인스턴스 조회 (주로 1개씩 수정 → get() 사용)
      article = Article.objects.get(pk=1)
      
      # 인스턴스 변수를 변경
      article.title = 'byebye'
      
      # 저장
      article.save()

Delete

  • 데이터 삭제 방법
    • 삭제하려는 데이터 조회 후 delete 메서드 호출
      # 삭제할 인스턴스 조회
      article = Article.objects.get(pk=1)
      
      # delete 메서드 호출
      article.delete()
      
      # 삭제한 데이터는 더이상 조회할 수 없음
      Article.objects.get(pk=1)  # DoesNotExist: Article matching query does not exist.

ORM with view

  • View 함수에서 QuerySet API 활용하기
    • 웹 페이지에 보여줄 데이터를 DB에서 가져올 때 사용함
    • 사용자가 입력한 새로운 데이터를 DB에 저장할 때 사용함

Read

전체 게시글 조회

  • config/urls.py
    from django.urls import path, include
    
    urlpatterns = [
    	path('admin/', admin.site.urls),
    	path('articles/', include('articles.urls')),
    ]
  • articles/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
    	path('', views.index, name='index'),
    ]
  • articles/views.py
    from .models import Article
    
    def index(request):
    	articles = Article.objects.all()
    	context = {
    		'articles': articles,
    	}
    	return render(request, 'articles/index.html', context)
  • templates/articles/index.html
    <h1>Articles</h1>
    <hr>
    {% for article in articles %}
    	<div>
    		<p>글 번호: {{ article.pk }}</p>
    		<p>글 제목: {{ article.title }}</p>
    		<p>글 내용: {{ article.content }}</p>
    	</div>
    	<hr>
    {% endfor %}

단일 게시글 조회

  • articles/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
    	...,
    	path('<int:pk>/' views.detail, name='detail'),
    ]
  • articles/views.py
    from .models import Article
    
    def detail(request, pk):
    	article = Article.objects.get(pk=pk)
    	context = {
    		'article': article,
    	}
    	return render(request, 'articles/detail.html', context)
  • 단일 게시글 조회
    • templates/articles/detail.html
      <h2>Detail</h2>
      <h3>{{ article.pk }} 번째 글</h3>
      <hr>
      <p>제목: {{ article.title }}</p>
      <p>내용: {{ article.content }}</p>
      <p>작성일: {{ article.created_at }}</p>
      <p>수정일: {{ article.updated_at }}</p>
      <hr>
      <a href="{% url 'articles:index' %}">[back]</a>
  • 단일 게시글 이동 페이지 링크 구현
    • templates/articles/index.html
      {% for article in articles %}
      	<div>
      		<p>글 번호: {{ article.pk }}</p>
      		<p>
      			글 제목: <a href="{% url "articles:detail" article.pk %}">{{ article.title }}</a>
      		</p>
      		<p>글 내용: {{ article.content }}</p>
      	</div>
      	<hr>
      {% endfor %}

Create

  • Create 로직 구현에는 두 개의 view 함수가 필요함
    • new: 사용자 입력 데이터를 받을 페이지를 렌더링
    • create: 사용자가 입력한 요청 데이터를 받아 DB에 저장
  • 페이지 렌더링 기능 구현 (new)
    • 게시글 생성 페이지 구현
      • articles/urls.py
        urlpatterns = [
        	...,
        	path('new/', views.new, name='new'),
        ]
      • articles/views.py
        def new(request):
        	return render(request, 'articles/new.html')
      • templates/articles/new.html
        <h1>NEW</h1>
        <form action="#" method="POST">
          <div>
            <label for="title">Title: </label>
            <input type="text" name="title" id="title">
          </div>
          <div>
            <label for="context">Content: </label>
            <textarea name="content" id="content"></textarea>
          </div>
          <input type="submit">
        </form>
        <hr>
        <a href="{% url 'articles:index' %}">[back]</a>
    • Index 페이지에 new 페이지로 이동할 수 있는 하이퍼링크 작성
      • templates/articles/index.html
        <h1>Articles</h1>
        <a href="{% url 'articles:new' %}">NEW</a>
        <hr>
        ...
  • 데이터 저장 기능 구현 (create)
    • articles/urls.py
      urlpatterns = [
      	...,
      	path('create/', views.create, name='create'),
      ]
    • articles/views.py
      def create(request):
      	title = request.POST.get('title')
      	content = request.POST.get('content')
      	article = Article(title=title, content=content)
      	article.save()
      	return render(request, 'articles/create.html')
    • templates/articles/create.html
      <h1>게시글이 작성되었습니다.</h1>
    • templates/articles/new.html
      <h1>NEW</h1>
      <form action="{% url 'articles:create' %}" method="POST">
        <div>
          <label for="title">Title: </label>
          <input type="text" name="title" id="title">
        </div>
        <div>
          <label for="context">Content: </label>
          <textarea name="content" id="content"></textarea>
        </div>
        <input type="submit">
      </form>
      <hr>
      <a href="{% url 'articles:index' %}">[back]</a>

HTTP request methods

  • HTTP란 네트워크 상에서 데이터(리소스)를 주고 받기 위한 약속
  • HTTP request methods란 데이터에 대해 수행을 원하는 작업(행동)을 나타내는 것
    • 서버에게 원하는 작업의 종류를 알려주는 역할
    • 대표적인 메서드
      • GET: 리소스 조회 (URL에 데이터가 노출됨)
      • POST: 데이터 생성/전송 (데이터 노출 없음)
  • GET 메서드
    • 서버로부터 데이터를 요청하고 받아오는 데(조회) 사용 (DB 변경 X)
    • GET 메서드는 주로 검색 쿼리 전송, 웹 페이지 요청, 그리고 API에서 데이터를 조회하는 것과 같이 서버로부터 데이터를 요청하고 받아오는 데 사용된다.
  • POST 메서드
    • 서버에 데이터를 제출하여 리소스를 변경(생성, 수정, 삭제)하는 데 사용
    • POST 메서드는 주로 로그인 정보 제출, 파일 업로드, 새 데이터 생성(예: 새 게시글 작성), 그리고 API에서 데이터 변경을 요청하는 것과 같이 클라이언트가 서버로 데이터를 전송하여 서버의 상태를 변경할 때 사용된다.
  • GET과 POST는 각각의 특성에 맞게 적절히 사용해야 함
    C → POST
    R → GET
    U → POST
    D → POST

HTTP response status code

  • 서버가 클라이언트의 요청에 대한 처리 결과를 나타내는 3자리 숫자
    • 클라이언트는 이 코드를 통해 요청이 성공했는지, 실패했는지, 아니면 추가적인 조치가 필요한지 즉시 파악할 수 있음
  • CSRF (사이트 간 요청 위조, cross site request forgery)
    • 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행동(글쓰기, 정보 수정, 송금 등)을 특정 웹사이트에 요청하게 만드는 해킹 방식
    • Django는 이러한 공격을 막기 위해 CSRF 토큰이라는 안전장치를 사용
  • DTL의 csrf_token 태그를 사용해 손쉽게 사용자에게 토큰 값을 부여
    • templates/articles/new.html
      <h1>NEW</h1>
      <form action="{% url 'articles:create' %}" method="POST">
      	{% csrf_token %}
      • 요청 데이터 + 인증 토큰 → 게시글 작성
  • 요청 시 CSRF Token을 함께 보내야 하는 이유
    • Django 서버는 해당 요청이 DB에 데이터를 하나 생성하는 요청에 대해 “Django가 직접 제공한 페이지에서 데이터를 작성하고 있는 것인지”에 대한 확인 수단이 필요한 것
    • 겉모습이 똑같은 위조 사이트나 정상적이지 않은 요청에 대한 방어 수단
  • 왜 POST일 때만 Token을 확인할까?
    • POST는 단순 조회(GET)와 달리 리소스의 변경(생성, 수정, 삭제)을 요청하는 의미와 기술적 특성을 지님
    • DB에 조작을 가하는 요청은 반드시 인증 수단이 필요
    • 데이터베이스에 대한 변경사항을 만드는 요청이기 때문에 토큰을 사용해 최소한의 신원 확인을 하는 것

Redirect

  • 게시글 작성 성공 후 적절한 응답 방법
    • 서버는 데이터 저장 후 페이지를 응답하는 것이 아닌 사용자를 적절한 기존 페이지로 보내야 한다.
      • 사용자를 보낸다 → 사용자가 GET 요청을 한 번 더 보내도록 해야 한다.
      • 실제로 서버가 클라이언트를 직접 다른 페이지로 보내는 것이 아닌 클라이언트가 GET 요청을 한 번 더 보내도록 응답하는 것
  • redirect() : 클라이언트가 인자에 작성된 주소로 다시 요청을 보내도록 하는 함수
    • articles/views.py
      def create(request):
      	title = request.POST.get('title')
      	content = request.POST.get('content')
      	article = Article(title=title, content=content)
      	article.save()
      	return redirect('articles:detail', article.pk)
    • redirect 동작 원리
      • redirect 응답을 받은 클라이언트는 detail url로 다시 요청을 보내게 됨
      • 결과적으로 detail view 함수가 호출되어 detail view 함수의 반환 결과인 detail 페이지를 응답 받게 되는 것
      • 결국 사용자는 게시글 작성 후 작성된 게시글의 detail 페이지로 이동하는 것으로 느끼게 됨
  • render vs. redirect
    • render는 출력(파일이 필요함/예: 'articles/new.html')
    • redirect는 경로를 재요청하는 기능을 담당함(url 주소가 필요함/예: 'articles:detail')

Delete

  • articles/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
    	...,
    	path('<int:pk>/delete' views.delete, name='delete'),
    ]
  • articles/views.py
    def delete(request, pk):
    	article = Article.objects.get(pk=pk)
    	article.delete()
    	return redirect('articles:index')
  • templates/articles/detail.html
    <h2>Detail</h2>
    ...
    <hr>
    <form action="{% url 'articles:delete' article.pk %}" method="POST">
    	{% csrf_token %}
    	<input type="submit" value="DELETE">
    </form>
    <a href="{% url 'articles:index' %}">[back]</a>

Update

  • Update 로직 구현에는 두 개의 view 함수가 필요함
    • edit: 사용자 입력 데이터를 받을 페이지를 렌더링
    • update: 사용자가 입력한 요청 데이터를 받아 DB에 저장
  • edit 기능 구현
    • articles/urls.py
      urlpatterns = [
      	...,
      	path('<int:pk>/edit/', views.edit, name='edit'),
      ]
    • articles/views.py
      def edit(request, pk):
      	article = Article.objects.get(pk=pk)
      	context = {
      		'article': article,,
      	}
      	return render(request, 'articles/edit.html', context)
    • templates/articles/edit.html → 수정 시 이전 데이터가 출력될 수 있도록 작성하기
      <h1>EDIT</h1>
      <form action="#" method="POST">
      	{% csrf_token %}
        <div>
          <label for="title">Title: </label>
          <input type="text" name="title" id="title" value="{{ article.title }}">
        </div>
        <div>
          <label for="context">Content: </label>
          <textarea name="content" id="content">{{ article.content }}</textarea>
        </div>
        <input type="submit">
      </form>
      <hr>
      <a href="{% url 'articles:index' %}">[back]</a>
      • input 요소(한 줄 텍스트): value 속성으로 기존 데이터 입력
      • textareat(여러 줄 텍스트): value 속성이 없고 태그 여닫는 사이에 값을 넣음
    • templates/articles/detail.html → edit 페이지로 이동할 수 있는 하이퍼링크 작성
      ...
      <a href="{% url 'articles:edit' article.pk %}">EDIT</a><br>
      <form action="{% url 'articles:delete' article.pk %}" method="POST">
      	{% csrf_token %}
      	<input type="submit" value="DELETE">
      </form>
      <a href="{% url 'articles:index' %}">[back]</a>
  • update 기능 구현
    • articles/urls.py
      urlpatterns = [
      	...,
      	path('<int:pk>/update/', views.update, name='update'),
      ]
    • articles/views.py
      def update(request, pk):
      	article = Article.objects.get(pk=pk)
      	article.title = request.POST.get('title')
      	article.content = request.POST.get('content')
      	article.save()
      	return redirect('articles:detail', article.pk)
    • templates/articles/edit.html
      <h1>EDIT</h1>
      <form action="{% url 'articles:update' article.pk %}" method="POST">
      	{% csrf_token %}
        <div>
          <label for="title">Title: </label>
          <input type="text" name="title" id="title" value="{{ article.title }}">
        </div>
        <div>
          <label for="context">Content: </label>
          <textarea name="content" id="content">{{ article.content }}</textarea>
        </div>
        <input type="submit">
      </form>
      <hr>
      <a href="{% url 'articles:index' %}">[back]</a>
profile
🌱 🐜

0개의 댓글