3 분 소요

django 회원가입과 로그인/로그아웃 구현


장고 auth에서 제공하는 기능들로 회원가입과 로그인/로그아웃을 구현해보자.


회원가입


장고의 모든 기능이 그러하듯 회원가입도 FBV/CBV 두 가지 방법을 제시한다.


본 글에서는 회원가입은 두 기능을 적절히 섞어서 구현해보겠다.


클래스 기반 form 생성


우선 회원가입을 위한 POST 요청을 받기 위해서 form 을 만들어보자.


장고에서는 회원가입 폼을 위해 django.contrib.auth.forms에서 UserCreationForm 클래스를 제공한다.

해당 클래스를 상속받아 기능을 확장하는 식으로 진행해보자.


우선 UserCreationForm 의 기본 코드는 다음과 같다.


class UserCreationForm(forms.ModelForm):
    """
    A form that creates a user, with no privileges, from the given username and
    password.
    """
    error_messages = {
        'password_mismatch': _('The two password fields didn’t match.'),
    }
    password1 = forms.CharField(
        label=_("Password"),
        strip=False,
        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
        help_text=password_validation.password_validators_help_text_html(),
    )
    password2 = forms.CharField(
        label=_("Password confirmation"),
        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
        strip=False,
        help_text=_("Enter the same password as before, for verification."),
    )

    class Meta:
        model = User
        fields = ("username",)
        field_classes = {'username': UsernameField}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self._meta.model.USERNAME_FIELD in self.fields:
            self.fields[self._meta.model.USERNAME_FIELD].widget.attrs['autofocus'] = True

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise ValidationError(
                self.error_messages['password_mismatch'],
                code='password_mismatch',
            )
        return password2

    def _post_clean(self):
        super()._post_clean()
        # Validate the password after self.instance is updated with form data
        # by super().
        password = self.cleaned_data.get('password2')
        if password:
            try:
                password_validation.validate_password(password, self.instance)
            except ValidationError as error:
                self.add_error('password2', error)

    def save(self, commit=True):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user


기본 form 필드로는 비밀번호, 비밀번호 확인, 아이디 이렇게만 존재한다.

그리고 유효성 검사와 저장에대한 함수가 선언되어있다.


해당 클래스를 상속받아 나만의 회원가입 form 을 만들 수 있다.


class SignupForm(UserCreationForm):
    class Meta:
        model= get_user_model()
        fields = [
            'username',
            'email',
            'first_name',
            'last_name',
            'nickname',
        ]
    def clean_nickname(self):
        nickname= self.cleaned_data['nickname']
        if get_user_model().objects.filter(nickname=nickname).exists():
            raise forms.ValidationError("사용중인 닉네임 입니다.")
        else :
            return nickname


Meta 클래스에서 모델과 필드 위젯등을 지정해줄 수있다.

위젯은 별도로 설정해주지 않는다면 모델 필드에 맞게 생성된다.


나는 닉네임에 대한 중복 검사 항목을 추가했다.

해당 항목을 통해 닉네임 모델에 Unique = True 설정 없이도 데이터의 유일성을 유지할 수있다.


함수 기반 회원가입 view 생성


제너릭 뷰의 CreateView 를 상속받아 CBV 방식으로 회원 가입을 구현할 수 있지만, 나는 함수 기반으로 구현해봤다.


from django.contrib.auth import login as Sign_login
from django.shortcuts import render, redirect

def signup(request):
    if request.method == "POST":
        form = SignupForm(request.POST)
        if form.is_valid():
            signed_user = form.save()
            Sign_login(request, signed_user)
            return redirect('index')
    else :
        form = SignupForm()
    return render(request, 'accounts/signup_form.html',{
            'form': form
        })


http 요청의 방식에 따라 POST 이면 지정해둔 모델 form 을 이용하여 유효성 검사후 저장한다.

이후에 auth의 login 기능을 이용하여 회원가입 이후 로그인 세션을 유지시켜 준다.

그리고 redirect로 회원가입 후 이동할 페이지를 설정한다.

만약 POST 요청이 아니면 빈 폼을 만들어서 회원가입 폼을 작성할 수 있는 템플릿으로 render해준다.


url 맵핑과 form 에 대한 부분은 회원가입이라고 특별할 게 없어 생략했습니다.


로그인


로그인 기능 또한 auth 앱에서 제공해주는 기능들을 이용하면 어렵게 세션을 만들고 유지시키는 과정을 모두 생략해도 된다.


로그인의 경우엔 form을 작성할 필요도 없다.

auth에 내장되어 있는 LoginView 를 이용하면 url 맵핑과 로그인 템플릿만 만들어주면 모든 준비가 끝난다.


클래스 기반 로그인 뷰 생성


from django.contrib.auth.views import LoginView

login = LoginView.as_view(template_name='accounts/login_form.html')


이게 전부이다.


놀랍지 아니한가.


물론 기본 기능만을 이용한 것이며 as_view에 몇몇 인수로 자세한 설정도 가능하다.

그러나 기본 로그인만을 원한다면 로그인 폼을 작성할 템플릿만 명시해주면 된다.


settings.py 설정


아래 코드는 auth앱의 LoginView 클래스 코드이다.


class LoginView(SuccessURLAllowedHostsMixin, FormView):
    """
    Display the login form and handle the login action.
    """
    form_class = AuthenticationForm
    authentication_form = None
    next_page = None
    redirect_field_name = REDIRECT_FIELD_NAME
    template_name = 'registration/login.html'
    redirect_authenticated_user = False
    extra_context = None

    @method_decorator(sensitive_post_parameters())
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        if self.redirect_authenticated_user and self.request.user.is_authenticated:
            redirect_to = self.get_success_url()
            if redirect_to == self.request.path:
                raise ValueError(
                    "Redirection loop for authenticated user detected. Check that "
                    "your LOGIN_REDIRECT_URL doesn't point to a login page."
                )
            return HttpResponseRedirect(redirect_to)
        return super().dispatch(request, *args, **kwargs)

    def get_success_url(self):
        return self.get_redirect_url() or self.get_default_redirect_url()

    def get_redirect_url(self):
        """Return the user-originating redirect URL if it's safe."""
        redirect_to = self.request.POST.get(
            self.redirect_field_name,
            self.request.GET.get(self.redirect_field_name, '')
        )
        url_is_safe = url_has_allowed_host_and_scheme(
            url=redirect_to,
            allowed_hosts=self.get_success_url_allowed_hosts(),
            require_https=self.request.is_secure(),
        )
        return redirect_to if url_is_safe else ''

    def get_default_redirect_url(self):
        """Return the default redirect URL."""
        return resolve_url(self.next_page or settings.LOGIN_REDIRECT_URL)

    def get_form_class(self):
        return self.authentication_form or self.form_class

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['request'] = self.request
        return kwargs

    def form_valid(self, form):
        """Security check complete. Log the user in."""
        auth_login(self.request, form.get_user())
        return HttpResponseRedirect(self.get_success_url())

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        current_site = get_current_site(self.request)
        context.update({
            self.redirect_field_name: self.get_redirect_url(),
            'site': current_site,
            'site_name': current_site.name,
            **(self.extra_context or {})
        })
        return context


 def get_default_redirect_url(self):
        """Return the default redirect URL."""
        return resolve_url(self.next_page or settings.LOGIN_REDIRECT_URL)


중간에 보면 위와 같은 함수가 정의되어 있다.

resolve_url 에서 참조하는 settings.LOGIN_REDIRECT_URL 덕분에 로그인 뷰를 사용하였을 때 해당 설정의 default인 ‘accounts/profile’ URL 로 이동하게 된다.

뭐 구현해놨다면 상관 없지만 일반적인 경우라면 404 에러를 마주칠 수 밖에 없다.


이 문제는 프로젝트의 settings.py 에서 해당 설정 부분을 추가해주면 된다..


LOGIN_REDIRECT_URL ='/'


나의 경우엔 기본 index 페이지로 이동하도록 설정했다.


로그아웃


로그아웃은 로그인보다도 간단하다.


form도 template도 필요 없다.


url 맵핑과 매우 간단하 함수 기반 view 만으로도 구현 할 수 있다.


함수 기반 ..? 로그아웃 구현


from django.contrib.auth import logout as auth_logout

def logout(request):
    auth_logout(request)
    return redirect('index')


logout 에 별칭을 붙여준 이유는 함수명과 충돌이 남을 방지하기 위함이다.


댓글남기기