views.py
에 30개 이상의 뷰를 작성한다고 가정할 때, 해당 뷰의 코드를 views.py
에 모두 작성하게 되면 코드의 가독성이 떨어져 개발자가 코드를 보기 힘들어지는 문제가 발생한다. 그래서 도메인별로 코드를 분리해서 작성하는 것이 좋다.views/main
: 메인 화면을 보여주는 뷰를 저장하는 디렉터리
views/users
: 사용자 기능과 관련된 뷰를 저장하는 디렉터리
templates/main
: 메인 화면을 나타내는 템플릿
templates/users
: 사용자 기능과 관련된 화면을 나타내는 템플릿
from django import forms
class RegisterForm(forms.Form):
email = forms.EmailField(label="이메일", error_messages={'invalid' : '이메일 형식이 올바르지 않습니다.'})
password = forms.CharField(label="비밀번호", min_length=6, max_length=20, widget=forms.PasswordInput)
password_confirm = forms.CharField(label="비밀번호 확인", min_length=6, max_length=20, widget=forms.PasswordInput)
nickname = forms.CharField(label="닉네임", min_length=2, max_length=10)
profile_image = forms.ImageField(label="프로필 사진", required=False)
def clean(self):
# RegisterForm의 부모 클래스 → forms.Form의 clean메소드를 그대로 사용하기 위해 super() 클래스 사용
cleaned_data = super(RegisterForm, self).clean()
password = cleaned_data.get('password')
password_confirm = cleaned_data.get('password_confirm')
if password and password_confirm:
if password != password_confirm:
raise forms.ValidationError({'password_confirm' : '2개의 비밀번호가 일치하지 않습니다.'})
return cleaned_data
# def clean(self):
# """
# Hook for doing any extra form-wide cleaning after Field.clean() has been
# called on every field. Any ValidationError raised by this method will
# not be associated with a particular field; it will have a special-case
# association with the field named '__all__'.
# """
# return self.cleaned_data
from django.shortcuts import HttpResponseRedirect, redirect
from django.urls import reverse, reverse_lazy
from django.contrib.auth.models import User
from django.contrib import auth
from ..forms import RegisterForm, LoginForm
from ..models import UserProfile
from django.views.generic import FormView
from django.views import View
from django.contrib import messages
class RegisterView(FormView):
template_name = 'users/register.html'
form_class = RegisterForm
success_url = '/'
def form_valid(self, form):
email = form.cleaned_data['email']
password = form.cleaned_data['password']
nickname = form.cleaned_data['nickname']
profile_image = form.cleaned_data['profile_image']
# unique constraint failed → 테이블 제약 조건(중복 X)
user = User.objects.create_user(email, email, password)
UserProfile.objects.create(user=user, nickname=nickname, profile_image=profile_image)
return super().form_valid(form)
# def form_valid(self, form):
# """If the form is valid, redirect to the supplied URL."""
# return HttpResponseRedirect(self.get_success_url())
cleaned_data
부분을 작성하지않고 넘어간다면 문제가 발생할 가능성이 높다.clean
코드를 보면 주석으로 처리된 부분과 같이 동작을 한다는 것을 알 수 있는데 clean
메소드는 cleaned_data
를 리턴하는 역할을 한다. 따라서 부모 클래스 원형의 기능을 그대로 사용하고 싶다면 super()
키워드를 활용 후 RegisterForm을 넣어 그대로 사용하면 된다.form_valid
메소드의 코드 원형을 보면 get_success_url
이 리턴이 되고 get_success_url
메소드의 코드 원형을 보면 유효한 폼의 프로세스 이후 리다이렉트되는 URL로 리턴시켜준다. 따라서 역시 부모 클래스 원형의 기능을 그대로 사용하고 싶다면 super()
키워드를 활용해서 작성하면 된다.from django import forms
class LoginForm(forms.Form):
email = forms.EmailField(label="이메일", error_messages={'invalid' : '이메일 형식이 올바르지 않습니다.'})
password = forms.CharField(label="비밀번호", min_length=6, max_length=20, widget=forms.PasswordInput)
from django.shortcuts import HttpResponseRedirect, redirect
from django.urls import reverse, reverse_lazy
from django.contrib.auth.models import User
from django.contrib import auth
from ..forms import RegisterForm, LoginForm
from ..models import UserProfile
from django.views.generic import FormView
from django.views import View
from django.contrib import messages
class LoginView(FormView):
template_name = 'users/login.html'
success_url = '/'
form_class = LoginForm
def form_valid(self, form):
email = form.cleaned_data['email']
password = form.cleaned_data['password']
user = auth.authenticate(username=email, password=password)
if user is not None:
auth.login(self.request, user)
return super().form_valid(form)
else:
messages.warning(self.request, "계정 혹은 비밀번호를 확인하세요!")
return redirect(reverse_lazy('login'))
redirect
와 path
를 직접 입력하여 URL을 지정하는 방식을 사용하여 구현한 것을 볼 수 있다.redirect
를 path
를 이용한 방법이라면 reverse
그리고 reverse_lazy
는 name
을 이용한 방법이라고 할 수 있다.viewname
은 URL 패턴 이름이거나 호출 가능한 view 객체일 수 있다. reverse
메소드는 주어진 뷰에 대한 URL을 생성하는데 사용된다. reverse
는 호출되는 즉시 평가되기 때문에 아직 로드되지 않을 수 있는 URL 구성(Django의 URL 구성에 의존하는 클래스 기반 뷰의 속성을 정의할 때)에는 사용해서는 안 된다.reverse
를 사용하면 아래와 같은 순환 참조 에러를 보게 될 것이다.raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) from e
django.core.exceptions.ImproperlyConfigured: The included URLconf 'table_bookings.urls' does not appear to have any patterns in it.
If you see the 'urlpatterns' variable with valid patterns in the file then the issue is probably caused by a circular import.
urls.py
와 views.py
를 보게 되면 예를 들어, RegisterView
를 보면 success_url
이 reverse('index')
가 되는데 이 때, URL의 모든 파일을 읽은 것이 아닌 상태로 계속 왔다갔다하면서 무한히 참조를 반복하게 되는 것이다.from news import views
path("archive/", views.archive, name="news-archive")
# using the named URL
reverse("news-archive")
반면에 reverse_lazy
는 reverse
와 동일한 작업을 수행하지만 지연되어 실행되기 때문에 더 느리다. 즉 명시적으로 액세스할 때까지 URL을 생성하지 않는다.
이는 클래스 기반 뷰에서 처리 성공 시 지정할 URL을 정하는 success_url
과 같이 URLConf가 완전히 로드되지 않은 상태에서 URL을 사용해야할 때 유용하다.
reverse_lazy
는 일반적으로 CBV에서 success_url
을 정의할 때 사용된다. success_url
속성은 클래스 수준에서 값이 필요하지만 이 시점에서 모든 URL 패턴이 로드되지 않을 수 있다. reverse_lazy
를 사용하면 모든 패턴이 로드되고 실제로 URL이 필요할 때까지 URL을 연기한다.
반면에 reverse
는 FBV에서 login, logout과 같이 메소드 내부에서 사용된다. 이러한 경우 메소드가 호출될때쯤이면 모든 URL 구성이 로드될 것이므로 reverse
방식을 사용하는 것이 안전하다고한다.