이 글에서 다루는 내용
- shop 앱 생성 및 INSTALLED_APPS 등록
- Item, Order 모델 설계하기
- Django Admin에 모델 등록하고 superuser로 상품 데이터 추가하기
1. shop 앱 생성하기
상품과 주문 로직을 담당할 shop 앱을 생성합니다.
python manage.py startapp shop
생성 후 settings.py의 INSTALLED_APPS에 등록합니다.
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt.token_blacklist',
'drf_spectacular',
'accounts',
'shop', # 추가
]
2. shop/models.py — Item과 Order 모델 설계
from django.db import models
from django.conf import settings
class Item(models.Model):
name = models.CharField(max_length=100, verbose_name="상품명")
price = models.PositiveIntegerField(verbose_name="가격")
# v1: RDBMS에 의존하는 재고 관리
stock = models.PositiveIntegerField(default=0, verbose_name="잔여 재고")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.name} (재고: {self.stock})"
class Meta:
db_table = 'items'
class Order(models.Model):
class Status(models.TextChoices):
PENDING = 'PENDING', '결제 대기'
COMPLETED = 'COMPLETED', '결제 완료'
FAILED = 'FAILED', '결제 실패'
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders')
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='orders')
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
order_number = models.CharField(max_length=50, unique=True, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Order {self.id} - {self.user.username} - {self.item.name}"
class Meta:
db_table = 'orders'
Item 모델
상품 하나를 나타내는 모델입니다. 주목할 부분은 stock 필드의 주석입니다.
# v1: RDBMS에 의존하는 재고 관리
stock = models.PositiveIntegerField(default=0, verbose_name="잔여 재고")
v1이라고 명시한 이유가 있습니다. 현재는 PostgreSQL의 stock 컬럼을 직접 줄여가며 재고를 관리합니다. 이후 포스트에서 Redis를 활용한 방식으로 개선할 예정이며, 두 방식을 직접 비교해볼 수 있도록 의도적으로 단계를 나눴습니다.
auto_now_add=True는 레코드가 처음 생성될 때의 시각을 자동으로 저장하고, auto_now=True는 레코드가 저장될 때마다 현재 시각으로 갱신됩니다.
Order 모델
주문 하나를 나타내는 모델입니다. 몇 가지 설계 포인트를 살펴봅니다.
TextChoices로 상태값 관리
class Status(models.TextChoices):
PENDING = 'PENDING', '결제 대기'
COMPLETED = 'COMPLETED', '결제 완료'
FAILED = 'FAILED', '결제 실패'
주문 상태를 문자열 상수로 관리합니다. DB에는 'PENDING', 'COMPLETED', 'FAILED'가 저장되고, Django Admin이나 Serializer에서는 한글 레이블('결제 대기' 등)로 표시됩니다. 상태값을 코드 여기저기에 문자열로 직접 쓰는 대신 Order.Status.PENDING처럼 참조할 수 있어 오타로 인한 버그를 방지합니다.
ForeignKey 설계
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders')
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='orders')
settings.AUTH_USER_MODEL을 직접 참조해 User 모델을 가져옵니다. 'accounts.User'라고 직접 쓰는 대신 이 방식을 쓰면 나중에 User 모델이 바뀌어도 코드를 수정할 필요가 없습니다.
on_delete=models.CASCADE는 연결된 User나 Item이 삭제되면 해당 Order도 함께 삭제되도록 합니다.
related_name='orders'를 설정하면 반대 방향 참조가 가능합니다. 예를 들어 특정 유저의 모든 주문을 user.orders.all()로 조회할 수 있습니다.
order_number
order_number = models.CharField(max_length=50, unique=True, null=True, blank=True)
unique=True로 중복 주문 번호를 방지합니다. null=True, blank=True는 주문 생성 시점에는 비워두고, 이후 결제 처리 단계에서 채워 넣기 위한 설정입니다.
3. 마이그레이션 실행하기
모델을 작성했으면 마이그레이션 파일을 생성하고 DB에 적용합니다.
python manage.py makemigrations shop
python manage.py migrate
Migrations for 'shop':
shop/migrations/0001_initial.py
- Create model Item
- Create model Order
Running migrations:
Applying shop.0001_initial... OK
PostgreSQL에 items 테이블과 orders 테이블이 생성됩니다.
4. shop/admin.py — Django Admin에 모델 등록하기
from django.contrib import admin
from .models import *
@admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price', 'stock', 'created_at')
search_fields = ('name',)
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'item', 'status', 'order_number', 'created_at')
list_filter = ('status',)
@admin.register(Item) 데코레이터는 ItemAdmin 클래스를 Item 모델의 Admin 설정으로 등록합니다.
옵션 역할
| list_display | Admin 목록 페이지에 표시할 컬럼 지정 |
| search_fields | 검색창에서 검색할 필드 지정 |
| list_filter | 우측 사이드바에 필터 패널 추가 |
5. superuser 생성 및 상품 등록하기
superuser 생성
Django Admin에 접속하려면 superuser 계정이 필요합니다. Dev Container 터미널에서 아래 명령어를 실행합니다.
python manage.py createsuperuser
Username: admin
Email address: (생략 가능)
Password:
Password (again):
Superuser created successfully.
Django Admin에서 상품 등록
서버를 실행하고 http://localhost:8001/admin/에 접속합니다.
python manage.py runserver 0.0.0.0:8000
superuser 계정으로 로그인하면 Items와 Orders 메뉴가 보입니다. Items → Add Item에서 테스트용 상품을 등록합니다.
상품명: 나이키 한정판 조던 1
가격: 200000
잔여 재고: 100
저장하면 Admin 목록에 한정판 스니커즈 A (재고: 100)으로 표시됩니다. __str__ 메서드에 정의한 형식대로 표현되는 것을 확인할 수 있습니다.
6. 최종 폴더 구조
hot-item/
├── accounts/
├── shop/ # 새로 추가된 앱
│ ├── migrations/
│ │ └── 0001_initial.py
│ ├── admin.py # Item, Order Admin 등록
│ ├── models.py # Item, Order 모델
│ └── ...
├── config/
└── ...
7. 정리
오늘 한 작업을 요약하면:
- shop 앱을 생성하고 INSTALLED_APPS에 등록했습니다.
- Item 모델에 재고(stock)를 직접 관리하는 v1 방식으로 설계했습니다. 이후 Redis 방식과 비교할 예정입니다.
- Order 모델에 TextChoices로 상태값을 관리하고, ForeignKey로 User와 Item을 연결했습니다.
- shop/admin.py에서 두 모델을 Django Admin에 등록하고, superuser로 테스트 상품을 추가했습니다.
다음 포스트부터는 이 상품을 대상으로 한정판 선착순 구매 API를 구현합니다. DB 락을 활용한 동시성 제어, Redis 대기열까지 본격적인 핵심 로직이 시작됩니다. 🚀
'Hot Item' 카테고리의 다른 글
| [Hot Item] #9. v1 부하 테스트 — Race Condition을 눈으로 확인하다 (0) | 2026.03.31 |
|---|---|
| [Hot Item] #8. 선착순 구매 API v1 — 순수 RDBMS, 그리고 의도된 실패 (0) | 2026.03.25 |
| [Hot Item] #6. 회원가입 / 로그인 / 로그아웃 API 구현 및 Swagger UI 테스트 (0) | 2026.03.25 |
| [Hot Item] 트러블슈팅 #1. AUTH_USER_MODEL 변경 후 migrate 실패 — InconsistentMigrationHistory (0) | 2026.03.09 |
| [Hot Item] #5. DRF + Swagger 문서 자동화, 커스텀 User 모델, JWT 인증 설정 (0) | 2026.03.09 |