๐Ÿš€ Django๋กœ AI ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ์Šค๋งˆํŠธ ๋ธ”๋กœ๊ทธ ๋งŒ๋“ค๊ธฐ

Hyeonio_oยท2025๋…„ 7์›” 10์ผ

BackEnd

๋ชฉ๋ก ๋ณด๊ธฐ
5/9
post-thumbnail

๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

SmartBlog๋Š” OpenAI API๋ฅผ ํ™œ์šฉํ•œ AI ๊ธฐ๋Šฅ์ด ํƒ‘์žฌ๋œ Django ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋” ์‰ฝ๊ณ  ํšจ์œจ์ ์œผ๋กœ ๋ธ”๋กœ๊ทธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค์–‘ํ•œ AI ์–ด์‹œ์Šคํ„ดํŠธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽฏ ์ฃผ์š” ๋ชฉํ‘œ

  • Django ํ”„๋ ˆ์ž„์›Œํฌ ์ˆ™๋ จ๋„ ํ–ฅ์ƒ
  • ์™ธ๋ถ€ API ์—ฐ๋™ ๊ฒฝํ—˜ (OpenAI API)
  • ํ’€์Šคํƒ ๊ฐœ๋ฐœ ์—ญ๋Ÿ‰ ๊ฐ•ํ™”
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX) ๊ฐœ์„ 

โœจ ์ฃผ์š” ๊ธฐ๋Šฅ

๐Ÿค– AI ์–ด์‹œ์Šคํ„ดํŠธ ๊ธฐ๋Šฅ

  • ์ œ๋ชฉ ์ถ”์ฒœ: ๊ธ€ ๋‚ด์šฉ์„ ๋ถ„์„ํ•˜์—ฌ ๋งค๋ ฅ์ ์ธ ์ œ๋ชฉ 4๊ฐœ ์ œ์•ˆ
  • ์ž๋™ ์™„์„ฑ: ์ž‘์„ฑ ์ค‘์ธ ๊ธ€์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ด์–ด์„œ ์ž‘์„ฑ
  • ํƒœ๊ทธ ์ถ”์ฒœ: ๊ธ€์˜ ์ฃผ์ œ์— ๋งž๋Š” ํƒœ๊ทธ 5๊ฐœ ์ž๋™ ์ƒ์„ฑ
  • ์š”์•ฝ ์ƒ์„ฑ: ๊ธด ๊ธ€์„ 200์ž ์ด๋‚ด๋กœ ํ•ต์‹ฌ ์š”์•ฝ

๐Ÿ“ ๋ธ”๋กœ๊ทธ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

  • ๊ฒŒ์‹œ๊ธ€ CRUD: ์ž‘์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ
  • ๋Œ“๊ธ€ ์‹œ์Šคํ…œ: ๋Œ“๊ธ€ ๋ฐ ๋Œ€๋Œ“๊ธ€ ๊ธฐ๋Šฅ
  • ์ข‹์•„์š” ๊ธฐ๋Šฅ: Ajax๋ฅผ ํ™œ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ์ข‹์•„์š”
  • ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ๋ง: ์ œ๋ชฉ, ๋‚ด์šฉ, ํƒœ๊ทธ๋กœ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ

๐Ÿ‘ฅ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ

  • ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ: Django ๊ธฐ๋ณธ ์ธ์ฆ ์‹œ์Šคํ…œ ํ™œ์šฉ
  • ํ”„๋กœํ•„ ๊ด€๋ฆฌ: ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •
  • ํŒ”๋กœ์šฐ ์‹œ์Šคํ…œ: ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž ํŒ”๋กœ์šฐ/์–ธํŒ”๋กœ์šฐ
  • AI ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์ : ๊ฐœ์ธ๋ณ„ AI ๊ธฐ๋Šฅ ์‚ฌ์šฉ ํ†ต๊ณ„

๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

Backend

  • Django 5.2: Python ์›น ํ”„๋ ˆ์ž„์›Œํฌ
  • SQLite: ๊ฐœ๋ฐœ์šฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
  • Django ORM: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ถ”์ƒํ™”

Frontend

  • HTML5/CSS3: ๋งˆํฌ์—… ๋ฐ ์Šคํƒ€์ผ๋ง
  • JavaScript: ๋™์  ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • Bootstrap: ๋ฐ˜์‘ํ˜• UI ํ”„๋ ˆ์ž„์›Œํฌ

AI Integration

  • OpenAI API: GPT-4o-mini ๋ชจ๋ธ ํ™œ์šฉ
  • Custom AI Service: API ๋ž˜ํ•‘ ๋ฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

Tools & Others

  • Git: ๋ฒ„์ „ ๊ด€๋ฆฌ
  • django-environ: ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ด€๋ฆฌ
  • Logging: ๋””๋ฒ„๊น… ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

๐Ÿ— ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

smartblog/
โ”œโ”€โ”€ accounts/           # ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ์•ฑ
โ”‚   โ”œโ”€โ”€ models.py      # CustomUser, Follow ๋ชจ๋ธ
โ”‚   โ”œโ”€โ”€ views.py       # ์ธ์ฆ, ํ”„๋กœํ•„, ํŒ”๋กœ์šฐ ๋ทฐ
โ”‚   โ””โ”€โ”€ admin.py       # ๊ด€๋ฆฌ์ž ์„ค์ •
โ”œโ”€โ”€ blog/              # ๋ธ”๋กœ๊ทธ ๋ฉ”์ธ ์•ฑ
โ”‚   โ”œโ”€โ”€ models.py      # Post, Comment, Like, Tag, AIUsageLog ๋ชจ๋ธ
โ”‚   โ”œโ”€โ”€ views.py       # ๊ฒŒ์‹œ๊ธ€, ๋Œ“๊ธ€, ์ข‹์•„์š” ๋ทฐ
โ”‚   โ”œโ”€โ”€ ai_views.py    # AI ๊ธฐ๋Šฅ API ๋ทฐ
โ”‚   โ””โ”€โ”€ ai_service.py  # OpenAI API ์„œ๋น„์Šค ํด๋ž˜์Šค
โ”œโ”€โ”€ templates/         # HTML ํ…œํ”Œ๋ฆฟ
โ”œโ”€โ”€ static/           # CSS, JS, ์ด๋ฏธ์ง€ ํŒŒ์ผ
โ””โ”€โ”€ media/            # ์‚ฌ์šฉ์ž ์—…๋กœ๋“œ ํŒŒ์ผ

๐Ÿ’ก ํ•ต์‹ฌ ๊ตฌํ˜„ ๋‚ด์šฉ

1. ์ปค์Šคํ…€ ์‚ฌ์šฉ์ž ๋ชจ๋ธ

class CustomUser(AbstractUser):
    bio = models.TextField(max_length=500, blank=True)
    ai_usage_count = models.IntegerField(default=0)
    
    def get_follower_count(self):
        return self.followers.count()
    
    def toggle_follow(self, user):
        follow, created = Follow.objects.get_or_create(
            follower=self, following=user
        )
        if not created:
            follow.delete()
            return False, user.get_follower_count()
        return True, user.get_follower_count()

ํŠน์ง•:

  • Django์˜ AbstractUser๋ฅผ ํ™•์žฅํ•˜์—ฌ ์ปค์Šคํ…€ ํ•„๋“œ ์ถ”๊ฐ€
  • ํŒ”๋กœ์šฐ ๊ด€๊ณ„๋ฅผ ์œ„ํ•œ ManyToMany ๊ด€๊ณ„ ๊ตฌํ˜„
  • AI ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์ ์„ ์œ„ํ•œ ai_usage_count ํ•„๋“œ

2. AI ์„œ๋น„์Šค ํด๋ž˜์Šค

class OpenAIService:
    def __init__(self):
        self.client = OpenAI(api_key=settings.OPENAI_API_KEY)
        self.model = "gpt-4o-mini"
    
    def generate_title_suggestions(self, content: str, count: int = 5):
        messages = [
            {
                "role": "system",
                "content": "ํ•œ๊ตญ์–ด ๋ธ”๋กœ๊ทธ ์ œ๋ชฉ์„ ์ถ”์ฒœํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค."
            },
            {
                "role": "user", 
                "content": f"๋‹ค์Œ ๊ธ€์— ๋Œ€ํ•œ {count}๊ฐœ์˜ ์ œ๋ชฉ์„ ์ถ”์ฒœํ•ด์ฃผ์„ธ์š”: {content}"
            }
        ]
        response = self._make_request(messages)
        return self._parse_titles(response)

ํŠน์ง•:

  • ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์œผ๋กœ API ํด๋ผ์ด์–ธํŠธ ๊ด€๋ฆฌ
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ: API ํ•œ๋„ ์ดˆ๊ณผ, ์ธ์ฆ ์‹คํŒจ ๋“ฑ ์˜ˆ์™ธ ์ƒํ™ฉ ๋Œ€์‘
  • ๋”๋ฏธ ๋ชจ๋“œ: API ํ‚ค๊ฐ€ ์—†์„ ๋•Œ ํ…Œ์ŠคํŠธ์šฉ ์‘๋‹ต ์ œ๊ณต
  • ๋กœ๊น…: API ์‚ฌ์šฉ๋Ÿ‰ ๋ฐ ์—๋Ÿฌ ์ถ”์ 

3. Ajax๋ฅผ ํ™œ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ

// ์ข‹์•„์š” ํ† ๊ธ€
function toggleLike(postId) {
    fetch(`/blog/like/${postId}/`, {
        method: 'POST',
        headers: {
            'X-CSRFToken': getCookie('csrftoken'),
            'Content-Type': 'application/json'
        }
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            updateLikeButton(postId, data.is_liked, data.like_count);
            showMessage(data.message, 'success');
        }
    });
}

ํŠน์ง•:

  • ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์—†์ด ์ข‹์•„์š”, ๋Œ“๊ธ€, AI ๊ธฐ๋Šฅ ๋™์ž‘
  • CSRF ํ† ํฐ ์ฒ˜๋ฆฌ๋กœ ๋ณด์•ˆ ๊ฐ•ํ™”
  • ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ์œ„ํ•œ ์‹ค์‹œ๊ฐ„ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ

4. ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ

def get_queryset(self):
    return Post.objects.select_related("author")\
                      .prefetch_related("tags", "likes")\
                      .annotate(likes_count=Count('likes'))\
                      .order_by("-created_at")

ํŠน์ง•:

  • select_related: ์™ธ๋ž˜ํ‚ค ๊ด€๊ณ„ ์ตœ์ ํ™”
  • prefetch_related: ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„ ์ตœ์ ํ™”
  • annotate: ์ง‘๊ณ„ ํ•จ์ˆ˜๋กœ ์ข‹์•„์š” ์ˆ˜ ๊ณ„์‚ฐ

๐ŸŽจ UI/UX ๊ณ ๋ฏผ

1. ๋ฐ˜์‘ํ˜• ๋””์ž์ธ

  • Bootstrap Grid System์„ ํ™œ์šฉํ•œ ๋ชจ๋ฐ”์ผ ์นœํ™”์  ๋ ˆ์ด์•„์›ƒ
  • ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ๋กœ ๋‹ค์–‘ํ•œ ํ™”๋ฉด ํฌ๊ธฐ ์ง€์›

2. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ 

  • ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ: AI ๊ธฐ๋Šฅ ์‚ฌ์šฉ ์‹œ ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์•ˆ๋‚ด
  • ์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ๋ฐฑ: ์„ฑ๊ณต/์‹คํŒจ ๋ฉ”์‹œ์ง€ ์ฆ‰์‹œ ํ‘œ์‹œ
  • ์ ์ง„์  ํ–ฅ์ƒ: JavaScript ๋น„ํ™œ์„ฑํ™” ์‹œ์—๋„ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๋™์ž‘

3. ์ ‘๊ทผ์„ฑ ๊ณ ๋ ค

  • ์‹œ๋งจํ‹ฑ HTML: ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์ง€์›
  • ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜: ๋งˆ์šฐ์Šค ์—†์ด๋„ ๋ชจ๋“  ๊ธฐ๋Šฅ ์ ‘๊ทผ ๊ฐ€๋Šฅ
  • ์ ์ ˆํ•œ ์ƒ‰์ƒ ๋Œ€๋น„: ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ

๐Ÿšง ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ๋งˆ์ฃผํ•œ ๋„์ „

1. ๋Œ€๋Œ“๊ธ€ ๊ตฌ์กฐ ์„ค๊ณ„

๋ฌธ์ œ: ๋Œ“๊ธ€์˜ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ์–ด๋–ป๊ฒŒ ํ‘œํ˜„ํ• ๊นŒ?

ํ•ด๊ฒฐ์ฑ…:

class Comment(models.Model):
    parent = models.ForeignKey("self", on_delete=models.CASCADE, 
                              null=True, blank=True)
  • Self-Referencing FK: ๊ฐ™์€ ๋ชจ๋ธ์„ ์ฐธ์กฐํ•˜์—ฌ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„ ๊ตฌํ˜„
  • ํ…œํ”Œ๋ฆฟ์—์„œ ์žฌ๊ท€ ์ฒ˜๋ฆฌ: JavaScript๋กœ ์ค‘์ฒฉ ๋Œ“๊ธ€ ๋ Œ๋”๋ง

2. Ajax CSRF ํ† ํฐ ์ฒ˜๋ฆฌ

๋ฌธ์ œ: Ajax ์š”์ฒญ์—์„œ CSRF ํ† ํฐ ์—๋Ÿฌ ๋ฐœ์ƒ

ํ•ด๊ฒฐ์ฑ…:

function getCookie(name) {
    // ์ฟ ํ‚ค์—์„œ CSRF ํ† ํฐ ์ถ”์ถœ
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

๐Ÿ’ป ์‹คํ–‰ ๋ฐฉ๋ฒ•

ํ™˜๊ฒฝ ์„ค์ •

# 1. ํ”„๋กœ์ ํŠธ ํด๋ก 
git clone <repository-url>
cd smartblog

# 2. ๊ฐ€์ƒํ™˜๊ฒฝ ์ƒ์„ฑ ๋ฐ ํ™œ์„ฑํ™”
python -m venv venv
Windows: venv\Scripts\activate # MacOS : source venv/bin/activate

# 3. ํŒจํ‚ค์ง€ ์„ค์น˜
pip install django python-environ openai

# 4. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ • (.env ํŒŒ์ผ ์ƒ์„ฑ)
DEBUG=True
OPENAI_API_KEY=your_openai_api_key_here
DATABASE_URL=sqlite:///db.sqlite3
ALLOWED_HOSTS=127.0.0.1,localhost

์‹คํ–‰

# 1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
python manage.py makemigrations
python manage.py migrate

# 2. ์Šˆํผ์œ ์ € ์ƒ์„ฑ
python manage.py createsuperuser

# 3. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
python manage.py runserver

์ ‘์†


๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌ

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด Django ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๊ฐ•๋ ฅํ•จ์„ ์‹ค๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ORM์˜ ํŽธ๋ฆฌํ•จ๊ณผ MTV ํŒจํ„ด์˜ ๋ช…ํ™•ํ•œ ๊ตฌ์กฐ๊ฐ€ ๊ฐœ๋ฐœ ํšจ์œจ์„ฑ์„ ํฌ๊ฒŒ ๋†’์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

OpenAI API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋„์›€์ด ๋˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์„œ ๋ฟŒ๋“ฏํ–ˆ์Šต๋‹ˆ๋‹ค.

์•ž์œผ๋กœ๋„ ์‚ฌ์šฉ์ž ์ค‘์‹ฌ์˜ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์ง€์†์ ์œผ๋กœ ํ•™์Šตํ•˜๊ณ  ๊ฐœ์„ ํ•ด๋‚˜๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค!


๐Ÿ”— ์ฐธ๊ณ  ์ž๋ฃŒ

0๊ฐœ์˜ ๋Œ“๊ธ€