2025/11/23 Django - Project 1

김기훈·2025년 11월 23일

TIL

목록 보기
63/191

프로젝트 전체 구조

django-is-awsome/
│
├── accounts/
│   ├── migrations/
│   │   └── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── serializers.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
│
├── transactions/
│   ├── migrations/
│   │   └── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── serializers.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
│
├── core/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
│
│
├── docker/
│   (폴더 내부 내용 생략 — Docker 관련 설정 파일들이 위치)
│
├── .gitignore
├── .python-version
├── db.sqlite3
├── manage.py
├── poetry.lock
├── pyproject.toml
└── README.md

파일 간단 설명


core (프로젝트 설정 폴더)

  • core/settings.py

    • Django 프로젝트 전체 설정 파일 (앱 등록, 데이터베이스, REST Framework 설정 포함).
  • core/urls.py

    • 프로젝트의 최상위 URL 매핑
      • accounts/ , transactions/ 앱 URL을 include해서 연결.
  • core/wsgi.py

    • WSGI 서버 실행 진입점 (배포용).
  • core/asgi.py

    • ASGI 서버 실행 진입점 (웹소켓/비동기 지원).

accounts 앱

  • accounts/models.py

    • 사용자의 은행 계좌를 저장하는 모델.
      • User(FK)
      • 은행명
      • 계좌번호
      • 잔액
      • 생성일자
  • accounts/serializers.py

    • Account 모델을 JSON으로 변환하는 Serializer.
    • 필드: id, bank_name, account_number, balance.
  • accounts/views.py

    • 은행 계좌 CRUD API:
    • AccountListCreateView : 로그인 유저의 계좌 목록 조회 & 생성
    • AccountDetailView : 한 계좌 조회·수정·삭제
    • 유저 본인의 계좌만 접근하도록 제한하는 로직 포함.
  • accounts/urls.py

    • /accounts/ 경로에 대해:
      • GET/POST → 계좌 리스트/생성
      • GET/PUT/DELETE → 상세 조회·수정·삭제

transactions 앱

  • transactions/models.py

    • 거래 내역(Transaction) 모델:
      • 계좌(FK)
      • 유저(FK)
      • 금액 (+입금 / –출금)
      • 카테고리(식비, 교통 등)
      • 메모
      • 거래 시각
      • 생성/수정 시간
      • 정렬 기준: 최신 거래가 먼저
  • transactions/serializers.py

    • Transaction 모델 변환 Serializer
    • read_only: user, created_at, updated_at
  • transactions/views.py

    • 거래 CRUD API:
    • TransactionListCreateView
      • 로그인 유저의 거래만 조회
      • 필터링: category, 날짜(start/end)
      • ordering: 거래 시간, 금액
      • 생성 시 계좌의 소유자 확인 (권한 체크)
    • TransactionDetailView
      • 특정 거래 조회·수정·삭제
  • transactions/urls.py

    • /transactions/ 경로 처리:
    • GET/POST → 거래 목록·생성
    • <pk>/ → 단건 조회·수정·삭제

파일 상세 설명

accounts

accounts/urls.py

from django.urls import path  
from .views import AccountListCreateView, AccountDetailView  

app_name = 'accounts'  

urlpatterns = [
    path('', AccountListCreateView.as_view(), name='account_list'),
    path('<int:pk>/', AccountDetailView.as_view(), name='account_detail'), 
]
  • app_name = 'accounts' → URL 네임스페이스 설정

  • path('', AccountListCreateView.as_view(), name='account_list')

    • /accounts/ → 계좌 목록 조회 & 생성
  • path('<int:pk>/', AccountDetailView.as_view(), name='account_detail')

    • /accounts/<pk>/ → 계좌 상세 조회/수정/삭제

accounts/views.py

from rest_framework.permissions import IsAuthenticated  
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView  
from .models import Account  
from .serializers import AccountSerializer  
class AccountListCreateView(ListCreateAPIView):
    serializer_class = AccountSerializer  
    permission_classes = [IsAuthenticated]  

    def get_queryset(self):
        return Account.objects.filter(user=self.request.user)  # 현재 로그인 유저의 계좌만 반환

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)  # 계좌 생성 시 자동으로 user 연결
  • ListCreateAPIView

    • 계좌 목록 조회 + 생성 기능 제공
  • permission_classes = [IsAuthenticated] → 인증 필요

    • IsAuthenticated
      • 인증된 사용자만 접근 가능하도록 하는 권한 클래스
  • serializer_class = AccountSerializer → Serializer 지정

    • AccountSerializer
      • Account 데이터를 직렬화하는 Serializer
class AccountDetailView(RetrieveUpdateDestroyAPIView):  
    serializer_class = AccountSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return Account.objects.filter(user=self.request.user)  # 본인의 계좌만 접근 허용
  • RetrieveUpdateDestroyAPIView

    • 단일 계좌 조회·수정·삭제

accounts/serializers.py

from rest_framework import serializers  
from .models import Account 

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account  
        fields = ['id', 'bank_name', 'account_number', 'balance']  
  • serializers.ModelSerializer

    • 모델 기반 Serializer 정의
  • fields

    • 응답/요청에 포함할 필드

accounts/models.py

from django.db import models  # Django 모델 기능 import
from django.contrib.auth import get_user_model  
User = get_user_model()  
class Account(models.Model): 
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='accounts')
    bank_name = models.CharField(max_length=50) 
    account_number = models.CharField(max_length=30, unique=True)
    balance = models.IntegerField(default=0)  # 잔액 (기본값 0)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.user.username} - {self.bank_name}"  

transactions

transactions/views.py

from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.permissions import IsAuthenticated
from .models import Transaction
from .serializers import TransactionSerializer
from django_filters.rest_framework import DjangoFilterBackend  
from rest_framework.filters import OrderingFilter  
from rest_framework.exceptions import PermissionDenied  
  • DjangoFilterBackend / OrderingFilter / PermissionDenied
    • 필터 기능 제공 / 정렬 기능 제공 / 권한 오류 응답
class TransactionListCreateView(ListCreateAPIView): 
    serializer_class = TransactionSerializer
    permission_classes = [IsAuthenticated]

    filter_backends = [DjangoFilterBackend, OrderingFilter]
    filterset_fields = ['category']  
    ordering_fields = ['transacted_at', 'amount']  
  • ListCreateAPIView -> 거래 조회 + 생성 API
  • filter_backends = [DjangoFilterBackend, OrderingFilter] -> 필터/정렬 백엔드 적용
  • filterset_fields = ['category'] -> /?category=food 필터
  • ordering_fields = ['transacted_at', 'amount'] -> 정렬 필드 허용
    def get_queryset(self):
        user = self.request.user  # 현재 로그인 유저
        qs = Transaction.objects.filter(user=user)  # 본인 거래만 조회

        start = self.request.query_params.get("start")  # 날짜 필터: 시작
        end = self.request.query_params.get("end")  # 날짜 필터: 끝

        if start:
            qs = qs.filter(transacted_at__gte=start)  # 지정한 날짜 이후
        if end:
            qs = qs.filter(transacted_at__lte=end)  # 지정한 날짜 이전

        return qs
    def perform_create(self, serializer):
        account = serializer.validated_data['account']  # body에서 account 필드 추출
        if account.user != self.request.user:  # 본인 계좌인지 확인
            raise PermissionDenied("이 계좌에 거래를 생성할 수 없습니다.")  # 아니면 오류 발생
        serializer.save(user=self.request.user)  # 거래 생성 시 user 자동 등록
class TransactionDetailView(RetrieveUpdateDestroyAPIView):
    serializer_class = TransactionSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return Transaction.objects.filter(user=self.request.user)  # 본인 거래만 접근 가능
  • RetrieveUpdateDestroyAPIView
    • 단일 거래 조회·수정·삭제

transactions/urls.py

from django.urls import path  
from .views import TransactionListCreateView, TransactionDetailView 

app_name = 'transactions' 

urlpatterns = [
    path('', TransactionListCreateView.as_view(), name='transaction_list'),
    path('<int:pk>/', TransactionDetailView.as_view(), name='transaction_detail'),  
]
  • /transactions/
  • /transactions/<pk>/

transactions/serializers.py

from rest_framework import serializers  
from .models import Transaction  

class TransactionSerializer(serializers.ModelSerializer):  
    class Meta:
        model = Transaction  
        fields = [
            'id', 'account','user' ,'amount', 'category', 'memo',
            'transacted_at', 'created_at', 'updated_at'
        ]
        read_only_fields = ['user', 'created_at', 'updated_at']
  • read_only_fields -> 수정 불가 필드

transactions/models.py

from django.db import models   
from django.contrib.auth import get_user_model
from accounts.models import Account 

User = get_user_model()

class Transaction(models.Model):
    CATEGORY_CHOICES = [ 
        ('food', '식비'),
        ('transport', '교통'),
        ('shopping', '쇼핑'),
        ('income', '수입'),
        ('etc', '기타'),
    ]
    account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="transactions") # 거래된 계좌
    user = models.ForeignKey(User, on_delete=models.CASCADE)  # 거래 주인 (로그인 유저)

    amount = models.IntegerField()  # 금액 (+입금 / -출금)
    category = models.CharField(max_length=20, choices=CATEGORY_CHOICES) 
    memo = models.CharField(max_length=255, blank=True)  # 메모 (선택)

    transacted_at = models.DateTimeField()  # 실제 거래 시각

    created_at = models.DateTimeField(auto_now_add=True)  # 생성 시각 자동 기록
    updated_at = models.DateTimeField(auto_now=True)  # 수정 시각 자동 기록

    class Meta:
        ordering = ["-transacted_at"]  # 최신 거래 먼저

    def __str__(self):
        return f"{self.account} / {self.amount}"  # 관리자 페이지 등에서 보일 문자열

core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),  # /admin/ 관리자 페이지
    path("accounts/", include('accounts.urls')),  # accounts 앱 URL 연결
    path("transactions/", include('transactions.urls')),  # transactions 앱 URL 연결
]

테스트

중요사항 ⭐️

# settings.py 
'DEFAULT_AUTHENTICATION_CLASSES': [
    'rest_framework.authentication.SessionAuthentication',
    'rest_framework.authentication.BasicAuthentication'
]

# Views.py 
permission_classes = [IsAuthenticated]
  • 반드시 로그인(세션 인증) 필요

계좌 기능(accounts)

  • 계좌 목록 조회 & 생성 -> http://127.0.0.1:8000/accounts/
  • 특정 계좌 조회/수정/삭제 -> http://127.0.0.1:8000/accounts/1/

거래 기능(transactions)

  • 거래 목록 조회 & 생성 -> http://127.0.0.1:8000/transactions/
  • 특정 거래 조회/수정/삭제 -> http://127.0.0.1:8000/transactions/1/

상세 테스트

카테고리별 필터

  • http://127.0.0.1:8000/transactions/?category=food -> 식비만 보기
  • http://127.0.0.1:8000/transactions/?category=shopping -> 쇼핑만 보기

기간 필터

start = self.request.query_params.get("start")
end = self.request.query_params.get("end")

if start:
    qs = qs.filter(transacted_at__gte=start)
if end:
    qs = qs.filter(transacted_at__lte=end)
  • 특정 날짜 이후 거래만 보기
    • http://127.0.0.1:8000/transactions/?start=2025-01-01
  • 특정 날짜 이전 거래만 보기
    • http://127.0.0.1:8000/transactions/?end=2025-01-31
  • 특정 기간 전체 보기
    • http://127.0.0.1:8000/transactions/?start=2025-01-01&end=2025-01-31

카테고리 + 기간 동시 필터

  • http://127.0.0.1:8000/transactions/?category=food&start=2025-01-01&end=2025-01-15
    • 식비 중에서 2025년 1월 1일 ~ 2025년 1월 15일 거래만 보기

정렬까지 같이 사용

ordering_fields = ['transacted_at', 'amount']
  • 식비 중에 최근 거래순 정렬
    • /transactions/?category=food&ordering=-transacted_at
  • 쇼핑 중 금액 적은순 정렬
    • /transactions/?category=shopping&ordering=amount
  • 기간+카테고리+정렬 모두 적용
    • /transactions/?category=transport&start=2025-01-01&end=2025-01-31&ordering=-amount

drf-yasg 설정

    1. poetry add drf-yasg
    1. settings.py 설정 -> INSTALLED_APPS -> 'drf_yasg'
    1. core/urls.py 수정
    1. http://127.0.0.1:8000/swagger/ / http://127.0.0.1:8000/redoc/

filterset

  • FilterSet 적용시 이점
    • 1) DRF 브라우저 페이지에 필터 UI 자동 생성됨
      • (카테고리 드롭다운, 날짜 입력칸이 나타남)
    • 2) 프론트 개발 시 API 호출만 하면 됨
      • React / Vue / Next.js에서 그대로 사용됨.
    • 3) 코드 유지보수비용 감소
      • 조회 기능이 깔끔하게 정리됨.

profile
안녕하세요.

0개의 댓글