풀스택 개발자 과정 90일차

너구·2026년 9월 22일

풀스택 성장과정

목록 보기
93/106

python

오늘은 장고를 이용해 간단한 블로그 페이지를 만드는 것을 하였다.
어드민 아이디, 그리고 게시판 정도만 간단하게 기능 구현하였다.

urls.py

"""
URL configuration for MyDjango project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/6.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from os import name

from django.contrib import admin
from django.urls import path
from blog import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path("", views.post_list, name="post_list"),
]

setting.py

"""
Django settings for MyDjango project.

Generated by 'django-admin startproject' using Django 6.1.1.

For more information on this file, see
https://docs.djangoproject.com/en/6.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.1/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-3z--74l#vmo52=m@^541(gxwny(7%zvp=9wlo7)u58du5$bw=&'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog', # 쉼표 필수
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'MyDjango.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'MyDjango.wsgi.application'


# Database
# https://docs.djangoproject.com/en/6.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/6.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/6.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


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

STATIC_URL = 'static/'


# Email
# https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration

MAILERS = {
    'default': {
        'BACKEND': 'django.core.mail.backends.console.EmailBackend',
    },
}

views.py

# blog/view.py
from django.http import HttpResponse
from django.shortcuts import render
from .models import Post

def hello(request):
    return HttpResponse("안녕하세요, 홍길동!")

def post_list(request):
    posts = Post.objects.all()
    return render(request, 'blog/post_list.html', {'posts': posts})

models.py

# blog/models.py
from django.db import models

class Post(models.Model):
    # max_length 최대 길이 100자
    # CharField max_length 필수 입력 해야 함 안그러면 에러
    title = models.CharField("제목", max_length=100)
    content = models.TextField("내용")
    # auto_now_add - 처음 지정할 때만 시각 넣기
    # auto_now - 저장할 때마다 갱신
    created_at = models.DateTimeField("작성일", auto_now_add=True)
    views = models.PositiveIntegerField("조회수", default=0)

class Meta:
    ordering = ["-created_at"] # 항상 최신순

def __str__(self):
    return self.title

admin.py

from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'created_at', 'views')

# Register your models here.

base.html

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<nav class="navbar navbar-dark bg-dark"></nav>
<div class="container my-4">
{% block content %}{% endblock %}
</div>

post_list.html

{% extends 'base.html' %}
{% block content %}
<h1>내 블로그</h1>
{% for post in posts%}
<h2>{{ post.title }}</h2>
<p>{{ post.created_at|date:"Y.m.d" }}</p>
{% endfor %}
{% endblock %}

java를 사용했을 때보다 훨씬 간편하고 프레임워크가 제대로 되어있다라는 느낌을 받았다.
간소화된 언어인 것은 알고 있었지만 이렇게까지 편리할지는 몰랐다.


개인 프로젝트

전/월세 사기 방지 프로그램 개발 1일차

  1. 기획서 검토 및 개발 범위 확정
  • 기존 기획서를 검토하고 개발 가능성 판단
  • 서류 업로드 방식을 파일 업로드 + 카메라 촬영(OCR) 두 가지로 확장
  • 지원 대상을 아파트뿐 아니라 오피스텔·빌라(연립·다세대)까지 확장 (다가구주택은 구조가 복잡해
    이번엔 제외)
  • Flutter 앱 개발은 서버/웹이 안정된 뒤로 순서 조정
  1. 개발 환경 구축
  • Git 저장소 생성, GitHub 연동
  • 브랜치 전략: 혼자 하는 프로젝트라 develop 브랜치 없이 트렁크 기반(기능 브랜치 → PR → main
    merge)으로 단순화
  • Spring Boot(백엔드) + Next.js(웹) 프로젝트 뼈대 생성
  1. 외부 API 신청 및 연동
  • 국토교통부 실거래가 API(아파트/오피스텔/연립다세대), 건축물대장 API, 도로명주소 API, Google
    Cloud Vision OCR까지 총 6개 API 준비
  • 민감한 키 값은 전부 .env로 분리하고 저장소에는 절대 올라가지 않도록 처리
  1. 등기부등본 PDF 파싱
  • 실제 등기부등본 PDF로 텍스트 추출 테스트 → 일반적인 오픈소스 도구(poppler)는 한글이 다 깨지는
    문제 발견
  • 원인은 PDF 폰트가 서브셋 임베딩 + 특수 인코딩 방식이라, Java용 Apache PDFBox로 교체해서 해결
  • 등기부등본의 갑구(소유권)·을구(근저당권) 항목을 자동으로 구조화하고, "말소" 표시까지 인식해서
    현재 유효한 권리관계만 걸러내는 파서 구현
  1. 위험 판단 규칙 엔진
  • "법적으로 위험을 확정할 수 없다"는 전제 하에, 모든 위험 신호에 근거 출처를 명시하는 구조로 설계
    • 등기부에 기록된 객관적 사실 (미말소 압류/가압류 등)
    • 정부·공공기관 권고 기준 (전세가율, 선순위채권 비율 등 — 법적 기준 아님을 명시)
    • 법적 근거 (주택임대차보호법 시행령상 소액임차인 최우선변제금 — 실제 법령 자료로 수치 검증 후
      반영)
  1. 실거래가 시세 조회 연동
  • 주소 → 법정동코드 변환 → 실거래가 조회 → 단지명/면적 매칭까지 이어지는 파이프라인을 실제 라이브
    API로 검증 완료 오늘 배운 점 / 트러블슈팅
  • 공공데이터 API는 문서에 나온 End Point만으로는 호출이 안 되고, 실제 호출해보며 세부 경로를
    찾아야 하는 경우가 있었음
  • 최신 프레임워크 버전(Spring Boot 4)에서는 라이브러리 패키지 구조가 바뀌어서 흔한 예제 코드가
    그대로 안 먹히는 경우 존재
  • .env 파일은 만들어두는 것과 실제로 애플리케이션이 읽어들이는 것은 별개라 별도 설정이 필요했음

0개의 댓글