오늘 학습 내용 ✅
목표1
- 계좌(Account)는 ERD 그대로 “최소 정보만” 가지며,
- Account 모델에는 balance 같은 필드 절대 넣지 않음
- 모든 금액/입출금/잔액 관련 로직은 Transaction 테이블에서만 관리하는 구조
- 잔액(balance), 거래내역(+/−), 이후잔액(balance_after)은 모두 Transaction에서 계산/저장
목표2
- 거래 수정 시 balance_after 재계산
- 거래 삭제 시 auto rollback (즉, 뒤의 거래들의 balance_after가 자동으로 다시 계산됨)
- 즉, 특정 거래가 수정/삭제되면 해당 거래 이후의 모든 거래들이 다시 계산되어야 함
1. 공통 로직 — 계좌 전체 거래 재계산 함수
- 계좌 account에 대해 모든 거래를 transaction_at + id 순으로 정렬한 뒤
- balance_after를 처음부터 다시 계산
def recalc_account_transactions(account):
transactions = account.transactions.order_by('transaction_at', 'id')
balance = 0
for tx in transactions:
if tx.type == "INCOME":
balance += tx.amount
else:
balance -= tx.amount
tx.balance_after = balance
tx.save()
def perform_destroy(self, instance):
account = instance.account
instance.delete()
recalc_account_transactions(account)
- 2) 거래 수정 시 balance_after 재계산
def perform_update(self, serializer):
instance = self.get_object()
account = instance.account
serializer.save()
recalc_account_transactions(account)
어제의 코드 리뷰
accounts/urls.py
- path('', AccountListCreateView.as_view(), name='account_list')
- /accounts/ 로 들어오면 계좌 목록 조회 + 계좌 생성
- path('<int:pk>/', AccountDetailView.as_view(), name='account_detail')
- /accounts/3/ 같은 경로에서 특정 계좌 조회 / 수정 / 삭제
accounts/views.py
- AccountListCreateView
- ListCreateAPIView → GET(목록), POST(생성)
- IsAuthenticated → 인증 필요
- get_queryset() → 로그인한 사용자 본인의 계좌만 조회
- perform_create() → 계좌 생성 시 자동으로 user 설정
- AccountDetailView
- RetrieveUpdateDestroyAPIView → GET/PUT/PATCH/DELETE 가능
- 유저의 계좌만 조회 가능
transactions/urls.py
- /transactions/ → 목록 조회 + 생성
- /transactions/3/ → 조회 + 수정 + 삭제
오늘의 코드 리뷰
- 거래(Transaction) 생성 시 잔액(balance_after)을 자동 계산
def perform_create(self, serializer):
account = serializer.validated_data['account']
- 요청(body)에 들어온 account 값(FK)을 꺼내는 부분.
- 검증(validated)된 account 객체를 가져옴.
if account.user != self.request.user:
raise PermissionDenied("이 계좌에 거래를 생성할 수 없습니다.")
- 계좌 소유자 검증
- 로그인한 사용자(self.request.user) / 거래하려는 계좌(account.user)
- 두 사용자가 다른 경우 → 해당 계좌에 거래 생성 금지
last_transaction = (
account.transactions.order_by('-transaction_at', '-id').first()
)
- 마지막(가장 최근) 거래 찾기
- 의미: 해당 계좌(account)의 모든 거래 중 날짜 내림차순 / id 내림차순 정렬()
- 가장 맨 위 한 개 = 최신 거래 / -id = 거래시간이 같을 경우 id값이 큰것이 최신
last_balance = last_transaction.balance_after if last_transaction else 0
- 마지막 잔액 값 가져오기
- 이전에 거래가 있으면 → 마지막 거래의 balance_after 사용
- 거래가 아예 없으면 → 초기 잔액을 0으로 설정
amount = serializer.validated_data['amount']
type = serializer.validated_data['type']
- amount / type 가져오기
- 요청에서 받은 금액(amount)과 거래 타입(type: INCOME | EXPENSE) 가져옴.
if type == 'INCOME':
new_balance = last_balance + amount
else:
new_balance = last_balance - amount
serializer.save(
user=self.request.user,
balance_after=new_balance
)
- 새로운 잔액 계산
- 입금(INCOME) → 더하기 / 출금(EXPENSE) → 빼기 -> 즉, balance_after(거래 후 잔액)를 계산
- serializer.save()에서 balance_after 저장
- 핵심 3가지 데이터를 저장
- user: 로그인한 사용자 / balance_after: 계산된 잔액
- 나머지 validated_data 필드들 (amount, type, account, category...)
부가 기능
소비패턴 분석 / 소셜 로그인
소비패턴 분석

1. 현재 잔액 조회 API (GET /accounts/{id}/balance/)
class AccountBalanceView(GenericAPIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk):
try:
account = Account.objects.get(pk=pk, user=request.user)
except Account.DoesNotExist:
return Response(
{"detail": "계좌를 찾을 수 없습니다."},
status=status.HTTP_404_NOT_FOUND
)
last_tx = account.transactions.order_by('-transaction_at', '-id').first()
balance = last_tx.balance_after if last_tx else 0
return Response({
"account_id": account.id,
"account_name": account.name,
"current_balance": balance
})
path('<int:pk>/balance/', AccountBalanceView.as_view(), name='account_balance'),
새롭게 알게된 내용 ✅
1. Django에서는 기본적으로 Primary Key(PK) 가 필요
- 내가 따로 PK를 지정하지 않으면 Django가 자동으로 id = models.AutoField(primary_key=True) 를 생성
class Blog(models.Model):
title = models.CharField(max_length=100)
class Blog(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
- 즉, id가 아닌 다른 이름으로라도 pk값이 한개라도 존재한다면
2. 일반적으로는 user, account로 이름 짓고 Django가 자동으로 _id 붙임.
3. fields / read_only_fields 의 차이
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = [
'id','name', 'created_at',
]
read_only_fields = ['created_at']
{
"id": 1,
"name": "국민은행",
"created_at": "2025-11-24T05:32:00Z"
}
{
"name": "우리은행",
"created_at": "2023-01-01"
}
- fields = Serializer에서 어떤 필드를 응답/입력 대상으로 사용할지 결정하는 목록
- 클라이언트가 볼 수 있는 필드
- 클라이언트가 전송할 수 있는 필드
- 전체 출력 구조
- read_only_fields = 해당 필드는 클라이언트가 값을 수정하거나 전송할 수 없음!(오직 서버가 관리)
- POST/PUT/PATCH 요청에서 사용자가 값을 넣어도 무시됨
- Serializer에서 출력은 되지만 입력받지 않는 필드
어려운 내용(추가 학습 필요) ✅
오늘 발생한 문제(발생 했다면) ✅