데이터 타입에 대해 이해하기 위해, Oracle과 SQL Server의 데이터 타입을 비교해보겠다. 이 두 데이터베이스 시스템은 업계에서 가장 널리 사용되는 RDBMS이지만, 각각의 특성과 사용 방법에는 중요한 차이가 있다.
개요
데이터베이스 설계에서 가장 중요한 결정 중 하나는 적절한 데이터 타입의 선택이다. 잘못된 데이터 타입 선택은 성능 저하, 저장 공간 낭비, 데이터 정확성 손실 등의 문제를 일으킬 수 있다. Oracle과 SQL Server는 각각 고유한 데이터 타입 체계를 가지고 있다.
데이터 타입 비교표
1. 숫자형 데이터 타입
분류
Oracle
SQL Server
설명
정수
NUMBER(p,0)
INT
정수형 데이터 (-2,147,483,648 ~ 2,147,483,647)
십진수
NUMBER(p,s)
DECIMAL/NUMERIC(p,s)
정밀도(p)와 스케일(s)을 지정할 수 있는 숫자형
부동소수점
FLOAT
FLOAT/REAL
부동 소수점 숫자
이진 부동소수점
BINARY_FLOAT
-
32비트 부동 소수점
BINARY_DOUBLE
-
64비트 부동 소수점
통화
-
MONEY
통화 데이터
2. 문자형 데이터 타입
분류
Oracle
SQL Server
설명
가변 길이
VARCHAR2(n)
VARCHAR(n)
Oracle: 최대 4000바이트 SQL Server: 최대 8000바이트
고정 길이
CHAR(n)
CHAR(n)
Oracle: 최대 2000바이트
유니코드 가변
NVARCHAR2(n)
NVARCHAR(n)
유니코드 문자열 저장
대용량 텍스트
CLOB
TEXT
Oracle: 최대 4GB SQL Server: 최대 2GB
3. 날짜/시간형 데이터 타입
분류
Oracle
SQL Server
설명
날짜+시간
DATE
DATETIME
Oracle: 날짜와 시간 포함 SQL Server: 1753-01-01 ~ 9999-12-31
고정밀 날짜/시간
TIMESTAMP
DATETIME2
더 높은 정밀도 제공
날짜만
-
DATE
날짜 정보만 저장
시간만
-
TIME
시간 정보만 저장
기간
INTERVAL
-
시간 간격 저장
4. 이진 데이터 타입
분류
Oracle
SQL Server
설명
대용량 이진
BLOB
IMAGE
대용량 이진 데이터 저장
소용량 이진
RAW
VARBINARY
작은 크기의 이진 데이터
파일스트림
-
FILESTREAM
파일 시스템에 저장되는 이진 데이터
주요 차이점 상세 분석
1. 문자열 처리의 차이
Oracle의 VARCHAR2와 SQL Server의 VARCHAR는 비슷해 보이지만 중요한 차이가 있다. Oracle에서는 VARCHAR2를 사용하는 것이 권장되는데, 이는 향후 VARCHAR의 구현이 변경될 수 있기 때문이다. 또한, Oracle의 VARCHAR2는 실제 저장된 문자열의 길이만큼만 저장 공간을 사용하지만, SQL Server의 VARCHAR는 지정된 길이만큼의 공간을 미리 할당한다.
2. 숫자 데이터 처리
Oracle은 NUMBER 타입 하나로 대부분의 숫자 데이터를 처리할 수 있다. 반면 SQL Server는 더 세분화된 숫자 타입(TINYINT, SMALLINT, INT, BIGINT 등)을 제공한다. 이는 각각의 장단점이 있다:
Oracle의 접근방식: 단순하고 유연하지만 저장 공간 최적화가 어려울 수 있음
SQL Server의 접근방식: 더 세밀한 제어가 가능하고 저장 공간을 최적화할 수 있지만, 설계 시 더 많은 고려가 필요함
3. 날짜와 시간 처리
날짜와 시간 처리에서도 두 시스템은 큰 차이를 보인다:
Oracle의 DATE는 기본적으로 시간 정보를 포함
SQL Server는 DATE와 TIME을 별도로 제공하여 더 명확한 구분이 가능
SQL Server의 DATETIME2는 Oracle의 TIMESTAMP와 비슷한 정밀도를 제공
실무에서의 선택 기준
데이터 타입을 선택할 때는 다음 사항을 고려해야 한다:
데이터의 특성
저장할 데이터의 크기와 형식
필요한 정밀도 수준
예상되는 데이터 증가량
성능 요구사항
검색 속도
저장 공간 효율성
인덱싱 전략
애플리케이션 요구사항
다른 시스템과의 호환성
향후 마이그레이션 가능성
개발 팀의 익숙도
결론
Oracle과 SQL Server는 각각의 장단점을 가지고 있다. Oracle은 단순하고 일관된 데이터 타입 체계를 제공하는 반면, SQL Server는 더 세분화되고 명시적인 데이터 타입을 제공한다.
데이터베이스 설계자는 프로젝트의 요구사항, 성능 목표, 그리고 팀의 경험을 고려하여 적절한 데이터 타입을 선택해야 한다. 특히 대규모 시스템에서는 초기의 데이터 타입 선택이 향후 시스템의 성능과 확장성에 큰 영향을 미칠 수 있음을 항상 명심해야 한다.
마지막으로, 데이터 타입 선택은 한 번의 결정으로 끝나는 것이 아니라, 시스템의 요구사항 변화에 따라 지속적으로 검토하고 최적화해야 하는 과정임을 기억해야 한다.
SQL은 관계형 데이터베이스를 다루는 데 있어 필수적인 도구이다. 본 글을 통해 SQL 명령어의 전체적인 구조를 이해할 수 있도록, SQL의 기본적인 명령어들을 알기 쉽게 정리해봤다.
SQL 명령어의 4 가지 종류
SQL 명령어는 용도에 따라 크게 네 가지로 분류된다. 각각의 특징과 주요 명령어들을 살펴보자.
1. 데이터 조작어(DML)
DML(Data Manipulation Language)은 데이터의 조회/입력/수정/삭제와 같이 실제 데이터를 다루는 데 사용되는 명령어들이다. 개발자들이 가장 자주 사용하는 명령어들이 여기에 속한다.
SELECT: 데이터 조회
INSERT: 새로운 데이터 삽입
UPDATE: 기존 데이터 수정
DELETE: 데이터 삭제
SELECT * FROM users; -- 데이터 조회
INSERT INTO users VALUES ('Kim', 30); -- 데이터 삽입
UPDATE users SET age = 31 WHERE name = 'Kim'; -- 데이터 수정
DELETE FROM users WHERE name = 'Kim'; -- 데이터 삭제
2. 데이터 정의어(DDL)
DDL(Data Definition Language)은 데이터베이스의 구조를 정의하는 명령어들이다. 테이블을 생성하거나 수정할 때 사용된다.
CREATE: 새로운 데이터베이스 객체 생성
ALTER: 기존 데이터베이스 객체 구조 수정
DROP: 데이터베이스 객체 삭제
TRUNCATE: 테이블의 모든 데이터 삭제
RENAME: 객체 이름 변경
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
age INT
);
ALTER TABLE users ADD COLUMN email VARCHAR(100);
DROP TABLE users;
TRUNCATE TABLE users;
3. 데이터 제어어(DCL)
DCL(Data Control Language)은 데이터베이스의 접근 권한을 관리한다. 주로 데이터베이스 관리자가 사용하는 명령어들이다.
GRANT: 사용자에게 권한 부여
REVOKE: 사용자의 권한 회수
GRANT SELECT ON users TO employee;
REVOKE SELECT ON users FROM employee;
4. 트랜잭션 제어어(TCL)
TCL(Transaction Control Language)은 데이터베이스의 트랜잭션을 관리한다. 데이터의 일관성을 유지하는 데 중요한 역할을 한다.
COMMIT: 트랜잭션의 작업 내용을 영구적으로 저장
ROLLBACK: 트랜잭션의 작업을 취소하고 이전 상태로 복구
SAVEPOINT: 트랜잭션 내에 저장점 설정
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;
실무에서 자주 사용되는 조합
실제 개발 환경에서는 여러 명령어를 조합해서 사용하는 경우가 많다. 다음은 자주 사용되는 패턴의 예시이다:
-- 새로운 테이블 생성
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
department VARCHAR(50)
);
-- 데이터 삽입
INSERT INTO employees (id, name, department)
VALUES (1, 'Kim', 'IT'), (2, 'Lee', 'HR');
-- 데이터 조회 및 수정
SELECT * FROM employees WHERE department = 'IT';
UPDATE employees SET department = 'Development' WHERE department = 'IT';
마치며
SQL은 데이터베이스를 다루는 데 있어 가장 기본이 되는 도구이다. 위의 네 가지 유형의 명령어들을 잘 이해하고 있다면, 대부분의 데이터베이스 작업을 수행할 수 있다. 특히 처음에는 DML 명령어들을 중점적으로 학습하시는 것이 추천된다.
Django의 Class-Based Views(CBV)는 객체 지향적인 방식으로 뷰를 구현할 수 있게 해주며, 코드의 재사용성과 확장성을 크게 향상시켜주는 강력한 기능이다. CBV의 내부 동작 방식과 주요 메서드들의 실행 흐름을 자세히 살펴보려고 한다.
CBV의 기본 구조
사용자가 웹 브라우저에서 요청을 보내면, Django는 URL 패턴을 통해 해당 요청을 적절한 CBV로 라우팅한다. Django CBV는 View 클래스의 as_view() 클래스 메서드를 통해 URL 패턴에 연결되며, 이는 실제로 뷰 함수로 변환된다. 관련해서는 아래 포스팅을 참고하면 된다.
뷰 함수가 실행되면, CBV 내부에서는 일련의 메서드들이 순차적으로 실행되면서 요청을 처리한다.
초기 설정 단계: setup()
모든 요청은 먼저 setup() 메서드를 통과한다. setup()은 가장 먼저 실행되며, 뷰 인스턴스에 필요한 기본 속성들을 설정한다. 여기서 request 객체와 URL로부터 전달받은 인자들이 인스턴스 변수로 저장된다.
def setup(self, request, *args, **kwargs):
"""
모든 요청 처리의 시작점
request 객체와 URL 파라미터들을 클래스 속성으로 설정
"""
self.request = request
self.args = args
self.kwargs = kwargs
Dispatch 단계: dispatch()
그 다음으로 dispatch() 메서드가 호출된다. 이 메서드는 트래픽 관리자와 같은 역할을 하여, HTTP 메서드에 따라 적절한 핸들러 메서드를 호출한다. 예를 들어, GET 요청이 들어오면 get() 메서드를, POST 요청이 들어오면 post() 메서드를 실행한다.
이후 단계들은 View 클래스의 용도에 따른 구현이 매우 다양하다. 먼저 generic view들부터 살펴본다.
Generic CBV
Django의 Generic CBV들은 웹 애플리케이션 개발에서 자주 사용되는 일반적인 패턴들을 추상화하여 제공하며, 일반적인 CRUD 작업을 쉽게 처리할 수 있도록 추가적인 메서드들을 제공한다. 대표적인 generic view들을 살펴보자.
View
최상위 CBV 클래스인 View도 generic view에 해당된다. 모든 View는 이 View 클래스를 상속한다. as_view() 클래스 메서드로 URL 패턴에 연결되며, dispatch() 메서드를 통해 요청을 적절한 핸들러로 라우팅한다.
TemplateView
TemplateView는 주로 정적인 콘텐츠를 보여주는 데 사용되며, 템플릿을 렌더링하는 것이 주요 목적이다.
get_context_data() 메소드를 통해 템플릿에 데이터를 전달할 수 있다.
GET 요청을 자동으로 처리한다(get() 메소드)
RedirectView
RedirectView는 들어오는 요청을 다른 URL로 리다이렉트하는 기능을 수행한다. URL 변경, 레거시 URL 처리, 조건부 리다이렉션 등 다양한 시나리오에서 활용된다.
주요 속성:
url: 리다이렉트할 URL
pattern_name: 리다이렉트할 URL 패턴
query_string: URL 파라미터 유지 여부
ListView
ListView는 모델의 객체 목록을 표시하는데 사용된다. (ex. 블로그 포스트 목록이나 상품 카탈로그 등을 구현)
객체 목록을 표시한다.
get_queryset()이 핵심 메서드이다.
paginate_by 설정으로 페이지네이션이 가능하다.
DetailView
DetailView는 단일 객체의 상세 정보를 표시하는데 사용된다.
단일 객체의 상세 정보를 표시한다.
get_object()가 핵심 메서드이다.
객체가 없을 경우 404 에러로 처리한다.
FormView
FormView는 폼 처리를 위한 generic view로, GET 요청과 POST 요청에 따라 다른 흐름을 가진다.
폼 처리 전용 뷰이다.
form_valid(), form_invalid()가 핵심 메서드이다.
success_url 처리가 중요하다.
CreateView
CreateView는 모델 객체 생성을 담당한다.
자동으로 모델 폼 생성한다.
GET 요청시 빈 폼을 표시하며, POST 요청시 폼 데이터를 처리 및 저장한다.
유효성 검사 통과 시 실행되는 form_valid()로 객체 저장 전 추가 처리를 할 수 있다.
성공 시 success_url로 리다이렉트된다.
get_context_data()로 템플릿에 추가 컨텍스트 데이터 전달이 가능하다.
UpdateView
UpdateView는 모델 객체 수정을 담당한다.
자동으로 객체를 조회하여 폼에 데이터를 채운다.
GET 요청시 기존 데이터가 채워진 폼을 표시하며, POST 요청시 수정된 데이터를 처리 및 저장한다.
pk나 slug로 객체를 식별한다.
CreateView와 거의 동일한 인터페이스를 제공한다.
DeleteView
DeleteView는 모델 객체 삭제를 담당한다.
GET 요청시 삭제 확인 페이지를 표시하며, POST 요청시 실제 객체의 삭제를 수행한다.
success_url로 삭제 후 리다이렉트된다.
pk 또는 slug로 삭제할 객체를 식별한다.
CBV 주요 메서드
CBV에서 중요한 메서드이거나, CBV를 커스터마이징할 때 가장 자주 오버라이드되는 메서드들의 용도를 살펴보자:
dispatch()
모든 요청에 대한 선처리가 필요할 때 사용한다.
class BookView(View):
def dispatch(self, request, *args, **kwargs):
# 모든 요청 처리 전에 실행
if not request.user.is_authenticated:
return redirect('login')
return super().dispatch(request, *args, **kwargs)
get_queryset()
데이터베이스에서 객체를 조회하는 방식을 커스터마이징할 때 사용한다.
class BookListView(ListView):
model = Book
def get_queryset(self):
# 출판된 책만 필터링
return Book.objects.filter(is_published=True)
get_context_data()
템플릿에 전달할 컨텍스트 데이터를 추가하거나 수정할 때 사용한다.
class BookDetailView(DetailView):
model = Book
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 추가 컨텍스트 데이터 전달
context['related_books'] = Book.objects.filter(
category=self.object.category
).exclude(id=self.object.id)[:3]
return context
get_object()
단일 객체를 조회하는 방식을 커스터마이징할 때 사용한다.
class BookDetailView(DetailView):
model = Book
def get_object(self, queryset=None):
obj = super().get_object(queryset)
# 조회수 증가
obj.views += 1
obj.save()
return obj
form_valid()/form_invalid()
form_valid(): 폼 데이터가 유효할 때 실행된다. 이 함수는 post() 함수의 내부에서 호출된다.
form_invalid(): 폼이 유효하지 않을 때 실행된다. 이 함수 역시도 post() 함수의 내부에서 호출된다.
class BookCreateView(CreateView):
model = Book
form_class = BookForm
success_url = reverse_lazy('book-list')
def form_valid(self, form):
# 폼이 유효할 때 실행
form.instance.author = self.request.user
return super().form_valid(form)
def form_invalid(self, form):
# 폼이 유효하지 않을 때 실행
messages.error(self.request, "입력 정보를 확인해주세요.")
return super().form_invalid(form)
get() / post() 등 HTTP 메소드 별 핸들러
HTTP 메서드별 처리 로직을 직접 정의할 때 사용한다.
class BookView(View):
def get(self, request, *args, **kwargs):
# GET 요청 처리
return render(request, 'book_detail.html', {'book': self.get_object()})
def post(self, request, *args, **kwargs):
# POST 요청 처리
book = self.get_object()
book.likes += 1
book.save()
return redirect('book-detail', pk=book.pk)
get_form_class()
동적으로 폼 클래스를 결정할 때 사용한다. 오버라이드 되지 않는다면, 기본적으로는 뷰의 form_class 속성에 할당된 폼 클래스가 반환된다.
class BookCreateView(CreateView):
model = Book
def get_form_class(self):
# 사용자 권한에 따라 다른 폼 반환
if self.request.user.is_staff:
return AdminBookForm
return UserBookForm
get_success_url()
요청 처리를 성공힌 후 리다이렉트할 URL을 동적으로 생성할 때 사용한다.
class BookUpdateView(UpdateView):
model = Book
form_class = BookForm
def get_success_url(self):
# 수정 완료 후 리다이렉트할 URL 동적 생성
messages.success(self.request, "책 정보가 수정되었습니다.")
return reverse('book-detail', kwargs={'pk': self.object.pk})
get_template_names()
조건에 따라 다른 템플릿을 사용해야 할 때 사용한다.
class BookDetailView(DetailView):
model = Book
def get_template_names(self):
# 조건에 따라 다른 템플릿 사용
if self.request.user.is_mobile:
return ['books/mobile/book_detail.html']
return ['books/book_detail.html']
get_initial()
폼의 초기값을 동적으로 설정할 때 사용한다.
class BookCreateView(CreateView):
model = Book
form_class = BookForm
def get_initial(self):
# 폼의 초기값 설정
initial = super().get_initial()
initial['category'] = self.request.GET.get('category')
initial['author'] = self.request.user
return initial
get_form_kwargs()
폼 인스턴스 생성 시 추가 인자를 전달할 때 사용한다.
class BookCreateView(CreateView):
model = Book
form_class = BookForm
def get_form_kwargs(self):
# 폼 인스턴스 생성 시 추가 인자 전달
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
get_form()
get_form_class()으로부터 얻은 폼 클래스의 인스턴스를 생성한다. 인스턴스 생성시, get_form_kwargs()에서 얻은 kwargs 딕셔너리가 사용된다.
get_context_object_name()
템플릿에서 사용할 객체나 리스트의 변수명을 커스터마이징할 때 사용한다.
class BookListView(ListView):
model = Book
def get_context_object_name(self, object_list):
# 템플릿에서 사용할 컨텍스트 변수명 설정
return 'book_list_custom'
사용자 정의 뷰
실제 프로젝트에서는 제네릭 뷰만으로 해결할 수 없는 복잡한 요구사항들이 있다. 이러한 경우 사용자 정의 뷰를 만들어 사용한다.
사용자 정의 뷰의 특징
HTTP 메서드별로 별도의 처리 로직을 구현할 수 있다.
Mixin을 통해 공통 기능을 재사용할 수 있다.
요청과 응답을 세밀하게 제어할 수 있다.
1. 기본적인 사용자 정의 뷰 예시
from django.views import View
from django.shortcuts import render
class SimpleView(View):
def get(self, request, *args, **kwargs):
context = {'message': 'Hello, World!'}
return render(request, 'simple.html', context)
def post(self, request, *args, **kwargs):
data = request.POST.get('data')
# 데이터 처리 로직
return redirect('success-page')
트리의 각 노드 정보가 주어질 때, 각 노드의 부모가 무엇인지 알아내는 프로그램을 작성해야 한다.
트리의 자식 노드 정보를 저장하기 위해 defaultdict(list)를 사용했다. defaultdict는 key가 없는 경우 자동으로 기본값을 지정해주는 딕셔너리이다.
부모 노드의 정보를 저장하기 위해서는 일반적인 list를 사용했다. 루트인 1번 노드부터 시작해서, 자식 노드를 순회하는 DFS를 통해 부모 노드 정보를 해당 list에 저장하도록 했다.
정답 코드
import sys
from collections import defaultdict
sys.setrecursionlimit(10**6)
tree = defaultdict(list)
n = int(next(sys.stdin))
for line in sys.stdin:
a, b = map(int, line.split())
tree[a].append(b) # 키가 없어도 자동으로 빈 리스트를 생성
tree[b].append(a) # 키가 없어도 자동으로 빈 리스트를 생성
parent = [0] * (n + 1)
def dfs(node):
for child in tree[node]:
if parent[child] == 0:
parent[child] = node
dfs(child)
dfs(1)
sys.stdout.write("\n".join(map(str, parent[2:])))
파이썬에서 재귀 호출의 깊이는 기본값인 1,000이고, 이를 초과하면 RecursionError가 발생한다. 해당 조건을 완화하기 위해 sys.setrecursionlimit(10**6)으로 재귀 호출의 깊이값을 변경해줘야 한다.
Django로 웹 서비스를 개발할 때 가장 중요한 결정 중 하나는 사용자 모델을 어떻게 구현할 것인가이다. Django는 세 가지 주요 방식을 제공하는데, 각 방식의 특징과 장단점을 살펴보고, 어떤 상황에서 어떤 방식을 선택해야 하는지 알아본다.
1. 기본 User 모델 사용하기
Django의 django.contrib.auth.models에 정의된 기본 User 모델을 사용하는 방식이다.
특징
username, password, email, first_name, last_name 등 기본적인 필드들이 미리 구현되어 있다.
인증 관련 메서드들이 모두 구현되어 있어 즉시 사용 가능하다.
admin 페이지와의 통합이 즉시 가능하다.
UserManager가 이미 구현되어 있어 별도 처리가 불필요하다.
장점
가장 빠르게 구현할 수 있다.
Django의 기본 인증 시스템과 완벽하게 호환된다.
추가 설정 없이 바로 사용할 수 있다.
단점
커스터마이징이 제한적이다.
기본 필드를 수정하기 어렵다.
username을 필수로 사용해야 한다.
추가 정보 관리: User Profile 패턴
기본 User 모델을 사용하면서 추가 정보를 관리해야 할 때는 OneToOneField를 사용한 Profile 모델(모델명은 자유롭게 명명 가능)을 생성하는 것이 일반적이다. 이를 'User Profile' 패턴이라고 한다.
아래 예시와 같이, User에 대한 추가 정보를 별도의 Profile 모델에 저장할 수 있다. Profile은 새로운 User가 생성되면 자동으로 생성된다.
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
birth_date = models.DateField(null=True, blank=True)
phone_number = models.CharField(max_length=15, blank=True)
address = models.CharField(max_length=255, blank=True)
def __str__(self):
return f'{self.user.username} Profile'
# User 생성 시 자동으로 Profile 생성
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
이 패턴의 장단점은 다음과 같다:
장점
기존 User 모델을 그대로 사용할 수 있다.
추가 정보만 별도 테이블로 관리할 수 있다.
User 모델의 기본 기능을 그대로 활용할 수 있다.
Profile 모델의 수정이 용이하다.
단점
추가 쿼리가 발생할 수 있다.
Profile 데이터 접근 시 user.profile과 같이 추가 참조가 필요하다.
데이터 일관성을 위한 관리가 필요하다.
2. AbstractUser 상속하기
Django의 django.contrib.auth.models에 정의된 AbstractUser를 사용하는 방식이다.
특징
기본 User 모델의 모든 필드와 메서드를 상속받는다.
기존 필드를 수정하고 새로운 필드를 추가할 수 있다.
기본 UserManager를 상속받아 사용하되, 필요한 경우 확장 가능하다.
장점
기본 User 모델의 모든 기능을 유지하면서 확장할 수 있다.
admin 페이지와의 통합이 유지된다.
기존 인증 시스템을 그대로 사용할 수 있다.
단점
username이 여전히 필수 필드이다.
데이터베이스 구조가 기본 User 모델과 비슷하게 유지된다.
인증 로직을 완전히 커스터마이징하기는 어렵다.
프로젝트 시작 시점에 설정해야 한다.
구현 예시
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
def create_user(self, username, email=None, password=None, **extra_fields):
# 커스텀 로직 추가
return super().create_user(username, email, password, **extra_fields)
birth_date = models.DateField(null=True, blank=True)
phone_number = models.CharField(max_length=15, blank=True)
3. AbstractBaseUser 상속하기
가장 유연한 커스터마이징이 가능한 방식이다.
특징
가장 기본적인 인증 기능만 제공한다.
모든 필드와 메서드를 직접 정의해야 한다.
BaseUserManager도 함께 구현해야 한다.
create_user()와 create_superuser() 메서드를 반드시 구현 필요하다.
비밀번호 해싱, 이메일 정규화 등을 직접 처리해야 한다.
사용자 생성 로직을 완전히 제어 가능하다.
장점
완전한 커스터마이징이 가능하다.
원하는 필드를 자유롭게 정의할 수 있다.
username 대신 email 등 다른 필드를 인증 필드로 사용할 수 있다.
단점
많은 세부 구현이 필요하다.
인증 관련 모든 메서드를 직접 구현해야 한다.
구현 예시
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('이메일은 필수입니다')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractBaseUser):
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
어떤 방식을 선택해야 할까?
각 방식의 선택 기준을 정리하면 다음과 같다:
1. 기본 User 모델
빠른 개발이 필요할 때
기본 인증 시스템으로 충분할 때
커스터마이징이 거의 필요 없을 때
추가 정보는 Profile 모델로 관리 가능할 때
2. AbstractUser
기존 User 모델에 필드를 추가하고 싶을 때
기본 인증 시스템을 유지하면서 확장이 필요할 때
username 기반 인증이 적합할 때
3. AbstractBaseUser
완전히 다른 인증 체계가 필요할 때
email 기반 인증 등 특별한 인증 방식이 필요할 때
기존 User 모델의 필드가 대부분 불필요할 때
결론
Django에서 사용자 모델을 구현할 때는 프로젝트의 요구사항과 미래의 확장을 고려하여 신중하게 선택해야 한다. 특히 이 결정은 프로젝트 초기에 이루어지며, 나중에 변경하기가 매우 어렵다는 점을 염두에 두어야 한다.
간단한 프로젝트라면 기본 User 모델에 Profile 모델을 추가하는 것으로 충분할 수 있다. 하지만 복잡한 사용자 관리가 필요하거나, 특별한 인증 요구사항이 있다면 AbstractUser나 AbstractBaseUser의 사용을 고려해봐야 한다.
특별한 요구사항이 없다면 AbstractUser의 사용이 추천된다. 대부분의 커스터마이징 요구사항을 충족하면서도 Django의 기본 기능들을 그대로 활용할 수 있기 때문이다.
입력으로 주어지는 각 문자열들에 대해, 소괄호 또는 대괄호의 열고 닫는 쌍이 갖추어져 있는지(="균형잡혀 있는지") 묻는 문제이다.
문제는 stack을 해결하면 풀린다.
문자열의 각 문자를 순회하면서,
여는 괄호일 때는 push하고
닫는 괄호일 때는 stack 내부에서 top의 위치에 매칭되는 여는 괄호가 이미 있는지 확인 -> 없으면 균형잡혀 있지 않은 것이다.
순회가 끝난 뒤, stack에 여는 괄호가 남아 있다면 이것도 균형잡혀 있지 않은 것이다.
관건은 구현이다.
먼저 아래 방법을 썼다.
p라는 문자열은 닫는 괄호에 대해 매칭되는 여는 괄호를 찾기 위한 저장소다. 이를 위해 여는 괄호 2개와 닫는 괄호 2개를 이어붙였다. 그래서 p 문자열 상에서 index() 계산을 사용해서 매칭되는 괄호를 찾는다.
정답 코드 1
import sys
def is_balanced(s):
p = "([)]"
stack = []
for c in s:
if c in p[0:2]:
stack.append(c)
elif c in p[2:]:
if not stack or (stack[-1] != p[p.index(c) - 2]):
return False
stack.pop()
return not stack
sys.stdout.write(
"\n".join(
("yes" if is_balanced(s) else "no") for s in sys.stdin if s.rstrip() != "."
)
)
문자열 p의 길이가 4밖에 안되기 때문에, p.index()가 linear search긴 해도 사실상 O(1)으로 동작할 것으로 생각했다.
그렇지만, 실제로는 O(4)라고 볼 수 있다.
만약 입력 문장의 갯수가 매우 많아지면, 이 미세한 차이가 누적되서 성능에 영향을 끼칠 수 있다.
매칭되는 여는 괄호를 한번에 찾을 수 있는 방법을 사용하면 성능을 좀 더 최적화할 수 있다.
이를 위해, 해싱 기반으로 동작하는 dict를 사용했다.
정답 코드 2 - dict를 사용한 해싱 탐색
import sys
def is_balanced(s):
stack = []
pairs = {")": "(", "]": "["}
for c in s:
if c in "([":
stack.append(c)
elif c in ")]":
if not stack or stack[-1] != pairs[c]:
return False
stack.pop()
return not stack
sys.stdout.write(
"\n".join(
("yes" if is_balanced(s) else "no") for s in sys.stdin if s.rstrip() != "."
)
)
Django를 사용시 as_view() 메서드를 자주 마주치게 된다. URL 설정에서 클래스 기반 뷰(Class-based View)를 연결할 때 항상 이 메서드를 호출한다. 이 메서드가 어떤 일을 하는지, 그리고 왜 필요한지 알아본다.
as_view()의 필요성
Django의 URL 라우팅 시스템은 기본적으로 함수를 기대한다. 하지만 코드를 더 체계적으로 구조화하기 위해, 클래스 기반 뷰를 사용한다. 바로 여기서 as_view()가 등장한다. as_view()는 클래스 기반 뷰를 URL 라우터가 이해할 수 있는 함수로 변환해주는 다리 역할을 한다.
dispatch 함수를 보면, request에서 HTTP 메서드를 알아낸 다음, 뷰가 해당 HTTP 메서드에 대해 구현하는 핸들러 함수(get()이나 post() 등)를 호출하는 것을 확인 가능하다.
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
실제 사용 예시
# views.py
from django.views import View
from django.http import HttpResponse
class BlogPostView(View):
def get(self, request, *args, **kwargs):
return HttpResponse("블로그 포스트를 보여줍니다")
def post(self, request, *args, **kwargs):
return HttpResponse("새 포스트를 생성했습니다")
# urls.py
from django.urls import path
from .views import BlogPostView
urlpatterns = [
path('post/', BlogPostView.as_view(), name='blog-post'),
]