CS/모바일웹서비스프로젝트

4. Django, 이미지 블로그와 REST API

arsenic-dev 2025. 10. 11. 17:06

경희대학교 안기옥 교수님의 모바일/웹서비스프로젝트 수업을 기반으로 정리한 글입니다.

Ch 1. 개요

 

시스템 구성도

  • DJANGO: 웹 애플리케이션 서버로서 동작
  • NGINX: 웹 서버 및 리버스 프록시로서 동작

 

장고 자체에는 네트워크 트래픽을 세밀하게 제어하는 기능이 거의 없어서, 대량의 요청 공격(DDos)이 들어오면 별다른 방어 없이 그대로 받아 시스템이 중단될 수 있다. 그래서 트래픽을 관리해 줄 역할이 필요하고, 그 역할을 오픈 소스인 Nginx가 한다.

 

※ 개념 설명

  • 리버스 프록시: 클라이언트의 요청을 받아 실제 서버로 전달하고, 서버로부터 받은 응답을 다시 클라이언트에게 전달하는 서버
  • 로드밸런서:하나의 서버에 요청이 몰리지 않도록 여러 대의 서버로 트래픽을 분산시키는 소프트웨어

 

지금까지는 단순히 HTTP로 HTML 페이지를 렌더링해서 보여주었고, 

이제부턴 RESTful API 형태로 서비스를 제공하는 법에 대해 살펴본다.

  • HTTP: 웹에서 클라이언트와 서버가 데이터를 주고받는 통신 규약 (Request -> Response)
  • HTML: 웹페이지의 구조를 만드는 마크업 언어로, 서버가 클라이언트에게 보여줄 화면을 전달할 때 주로 사용
  • RESTful API: 서버가 데이터를 '화면' 대신 '정보(e.g., JSON)'로 제공하고, 클라이언트가 이를 받아 화면을 구성하는 방식

 

Ch 2. 이미지 블로그

0. 준비

기존 게시물 삭제

 

기존 게시물의 경우 이미지 데이터가 없어 오류 가능성이 있다.

때문에, 모델을 갱신하기 전에 모두 삭제해 주어야 한다.

 

1. 모델에 이미지 필드 추가하기

 

먼저, python 이미지 라이브러리인 Pillow를 설치한다.

$ source myvenv/Scripts/activate
(myvenv) $ python -m pip install Pillow

 

그후, 'blog/Models.py'에 모델 필드를 추가하고,

from django.conf import settings
from django.db import models
from django.utils import timezone

class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(default=timezone.now)
    published_date = models.DateTimeField(blank=True, null=True)
    image = models.ImageField(upload_to='blog_image/%Y/%m/%d', default='blog_image/default_error.png') # 추가된 코드

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def __str__(self):
        return self.title

 

모델 마이그레이션까지 완료한다.

(myvenv) $ python manage.py makemigrations
(myvenv) $ python manage.py migrate

 

마지막으로 환경 파일에 미디어 경로를 추가하고,

 

Static 및 Media 파일 접근을 위한 url 패턴을 추가한다.

 

이러한 과정으로 모델에 이미지 필드 추가를 완료하고,

Django 서버를 재실행하면 Image 필드가 추가된 것을 확인할 수 있다.

 

접속 후 post 추가
'VSCODE'에서 업로드된 이미지 확인

 

2. HTML template를 이용해 화면에 이미지 출력하기

 

base.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Web Service Programming blog</title>

    {% load static %}
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>

<body>
    <div class="page-header">
        <h1><a href="/">Web Service Programming blog</a></h1>
    </div>

    <div class="content container">
        {% block content %}
        {% endblock %}
    </div>
</body>
</html>

 

post_list.html

{% extends 'blog/base.html' %}

{% block content %}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                {{ post.published_date }}
            </div>

            <h1><a href="#">{{ post.title }}</a></h1>
            <p>{{ post.text|linebreaksbr }}</p>

            <!-- 이미지 출력 -->
            {% if post.image %}
                <img src="{{ post.image.url }}" class="rounded intruder-image-list" alt="{{ post.title }}">
            {% endif %}
        </div>
    {% endfor %}
{% endblock %}

 

blog.css

/* 공통 배경, 글꼴 */
body {
    font-family: "Helvetica", sans-serif;
    margin: 0;
    background-color: #fff;
}

/* 상단 헤더 */
.page-header {
    background-color: orange;
    padding: 20px;
}

.page-header h1 a {
    color: white;
    text-decoration: none;
    font-size: 24px;
}

/* 게시글 */
.post {
    margin: 20px auto;
    padding: 10px;
    max-width: 800px;
    border-bottom: 1px solid #ddd;
}

.date {
    color: #888;
    font-size: 14px;
}

/* 이미지 */
.blog-image-list {
    width: 100%;
    height: 400px;
    object-fit: cover; /* 이미지를 꽉 채워 자르기 */
    border-radius: 10px;
    margin-top: 10px;
}

 

views.py

from django.shortcuts import render
from .models import Post

# Create your views here.
def post_list(request):
    posts = Post.objects.all().order_by('-published_date')
    return render(request, 'blog/post_list.html', {'posts': posts}) # blog/post_list.html 템플릿을 보여준다는 의미

 

결과

 

이때 만약, 이미지가 출력되지 않는 등의 문제가 발생할 경우 "F12" 키를 이용해 디버깅이 가능하다.

브라우저 디버깅

 

Ch 3. Django REST framework

User -> Web Page(or API) -> DB

 

CRUD(데이터 생성·조회·수정·삭제)를 RESTful API라는 규칙에 따라 HTTP(POST, GET,  PUT, DELETE)로 웹에서 수행한다.

  • CRUD: 데이터베이스의 기본 네 가지 작업 (Create, Read, Update, Delete)
  • RESTful API: 웹에서 CRUD를 수행할 수 있도록 HTTP로 구현한 API(Application Programming Interface)
  • HTTP: 웹사이트를 열 때 브라우저가 서버에 요청(request)을 보내고, 서버가 응답(response)을 보내는 방식
  •  API: 서로 다른 프로그램이 데이터를 요청하고 응답할 수 있게 해주는 인터페이스이다.

 

CRUD 조작 RESTful API: HTTP Method DB: SQL
CREATE 생성 POST INSERT
READ 조회 GET SELECT
UPDATE 수정 PUT, PATCH UPDATE
DELETE 삭제 DELETE DELETE

 

REST API Model

 

views.py 코드

from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from .models import Post
from .forms import PostForm
from rest_framework import viewsets
from .serializers import PostSerializer

def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'posts': posts})

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})

def post_new(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})

def post_edit(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form})

class blogImage(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

 

forms.py

# blog/forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'text', 'image']

 

Django REST framework 설치

(myvenv) $ python -m pip install djangorestframework

 

※ pip install 과정 중, "Fatal error in launcher: Unable to create process using" 에러 방지를 위해선 "python -m"을 실행하고자 하는 pip install 명령어 앞에 붙여주면 된다.

 

설정 코드 추가

주석 처리된 부분을 사용하면 admin 사용자일 때만 접근이 가능

 

데이터 직렬화

  • Serializers: models 객체와 querysets 같은 복잡한 데이터를 JSON, XML과 같은 native 데이터로 바꿔주는 역할

 

 

이렇게 완료한 후, 브라우저를 이용한 API 테스트를 해볼 수 있다.

브라우저를 이용한 테스트

 

Ch 4. 테스트

1. CLI를 이용한 테스트: CURL

 

 
 

CURL 문법

 

1. 기본 구조

curl [옵션] [URL]
  • 옵션: 요청 방식, 헤더, 데이터, 인증 등 설정
  • URL: 요청할 서버 주소

 

2. 주요 옵션

옵션 약자 설명 예시
-X --request HTTP 요청 방식 지정
(GET, POST, PUT, DELETE 등)
curl -X POST https://example.com
-H --header HTTP 헤더 지정 -H "Content-Type: application/json"
-d --data 전송할 데이터를 지정
(POST 요청에서 사용)
-d '{"name":"soyeon"}'
-F --form form-data 전송,파일 업로드 가능 -F "image=@경로;type=image/jpeg"
-u --user Basic 인증
(username:password)
-u "user:pass"
-S --show-error 에러가 발생하면 메시지를 표시 curl -S

 

※ CURL Download: https://curl.se/windows/

 

이미지를 포함한 Post 전송

 

$ curl -X POST -S \ // mong.jpg가 있는 디렉토리에서  curl
-H "Accept: application/json" \
-u 'username:password' \ 
-F "author=1" \ 
-F "title=제목" \
-F "text=내용" \ 
-F "created_date=2022-06-07T18:34:00+09:00" \
-F "published_date=2022-06-07T18:34:00+09:00" \
-F "image=@mong.jpg;type=image/jpeg" \
"http://127.0.0.1:8000/api_root/Post/"

 

하지만 이렇게 curl을 보낼 경우, 사용자 ID/Password 노출로 인한 보안 문제가 발생한다.

 

사용자 ID/Password를 대체 할 토큰 이용

 

모델 마이그레이션 및 토큰 획득

 

이렇게 하면, 토큰을 이용해 Post를 전송할 수 있게 되어 보안이 강화된다. (유출 이후 시스템 로그인 불가능)

 

토큰을 이용한 Post 전송

 

$ curl -X POST -S \
-H "Accept: application/json" \
-H "Authorization: Token ab0dca1170a025cf24e78810a5d472cd6346a633" \
-F "author=1" \ // token에 해당하는 user의 id 값
-F "title=제목" \
-F "text=내용" \
-F "created_date=2022-06-07T18:34:00+09:00" \
-F "published_date=2022-06-07T18:34:00+09:00" \
-F "image=@mong.jpg;type=image/jpeg" \
"http://127.0.0.1:8000/api_root/Post/"

 

2. Python code를 이용한 테스트

 

VSCODE에서 새로운 Window를 열어준 후, 아래 코드를 실행해 주면 post가 추가된 것을 확인할 수 있다.

import requests

HOST = 'http://127.0.0.1:8000'

# 로그인해서 토큰 발급
res = requests.post(HOST + '/api-token-auth/', {
    'username': 'username',
    'password': 'password',
})
res.raise_for_status()
token = res.json()['token']
print("Token:", token)

# 인증 헤더
headers = {
    'Authorization': 'Token ' + token,
    'Accept': 'application/json'
}

# 데이터
data = {
    'author': 1, # user의 id
    'title': '제목 by code',
    'text': 'API내용 by code',
    'created_date': '2024-06-03T18:34:00+09:00',
    'published_date': '2024-06-03T18:34:00+09:00'
}

# 이미지 파일 업로드
file = {'image': open('C:/Users/sy040/Desktop/다운로드.jpg', 'rb')}
res = requests.post(HOST + '/api_root/Post/', data=data, files=file, headers=headers)

print(res.status_code)
print(res.json())

'CS > 모바일웹서비스프로젝트' 카테고리의 다른 글

3. Debugging Django with VSCODE  (0) 2025.10.09
2. Django 웹 프레임워크  (0) 2025.10.09
1. Git & GitHub  (0) 2025.10.03