Django 회원가입, 로그인 구현하기!!!

2022. 1. 25. 19:27강의 정리/Django Form

반응형

출처 : https://www.inflearn.com/course/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%A5%EA%B3%A0-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4/dashboard

 

장고(Django)를 배우기 시작한 입문자이시거나, 또는 배우고 싶은 생각이 있으신 분은 위 출처의 강의를 적극 추천드립니다!!!

 


 

로그인 처리

  • Django에서 로그인 처리는 accounts를 주로 이용하자
  • Django github에서 기본 login 코드를 지원하고 있다.
    • django/django/contrib/auth/

https://github.com/django/django/blob/main/django/contrib/auth/urls.py

 

GitHub - django/django: The Web framework for perfectionists with deadlines.

The Web framework for perfectionists with deadlines. - GitHub - django/django: The Web framework for perfectionists with deadlines.

github.com

 

 

 

코드

#accounts/urls.py

from django.contrib.auth import views
from django.urls import path

urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),

    path('password_change/', views.PasswordChangeView.as_view(), name='password_change'),
    path('password_change/done/', views.PasswordChangeDoneView.as_view(), name='password_change_done'),

    path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
    path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

 

 

#accounts/templates/accounts/login_form.html

{% extends "accounts/layout.html" %}
{% load bootstrap4 %}

{% block content %}
<form action="" method="post">
    {% csrf_token %}
    {% bootstrap_form form %}
    {% buttons %}
        <button class="btn btn-primary">로그인</button>
    {% endbuttons %}
</form>
{% endblock content %}

 

 

 

 

 

자세히 알아보기

LoginView.as_view(template_name='accounts/login.html')
  • as_view의 인자는 해당 클래스나 뷰 함수의 멤버함수 인스턴스를 변경해준다.

 

 


 

 


 

  • 나는 Login 성공시 다른 주소로 이동시키고 싶다면 ?next=이동할주소 를 이용하면 된다.

 

 


 

  • 프로젝트 전반적으로 layout을 통일되게 사용하고 싶다면 프로젝트 폴더(static 폴더가 있는)에 만드는 것이 좋다.

 

 

 

 

 


 

사용자 프로필 페이지 및 프로필 수정

 

 

 

코드

#accounts/models.py

from django.conf import settings
from django.db import models

# Create your models here.

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    address = models.CharField(max_length=100, blank=True)
    zipcode = models.CharField(max_length=6, blank=True) # validators=[])를 통해서 숫자만 쓰일 수 있게 유효성 검사를 할 수 있다.

 

 

#accounts/urls.py

from django.contrib.auth.views import LoginView
from django.urls import path
from django.conf import settings
from django.conf.urls import static
from . import views


urlpatterns = [
    path('login/', LoginView.as_view(template_name='accounts/login_form.html'), name='login'),
    
    path('profile/', views.profile , name='profile'),
    path('profile/edit/', views.profile_edit , name='profile_edit'),

]

 

 

#accounts/views.py

from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.views.generic import TemplateView, UpdateView
from .forms import ProfileForm
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Profile 
from django.shortcuts import get_object_or_404, render, redirect

# Create your views here.


@login_required
def profile(request):
    return render(request, 'accounts/profile.html')


@login_required
def profile_edit(request):
    try:
        profile = request.user.profile # profile 변수에 현재 user의 profile model값 넘김
    
    except Profile.DoesNotExist: # Profile이 존재하지않다면
        profile = None 

    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES, instance=profile) # 
        if form.is_valid():
            form.save()
			# redirect는 view 함수 내에서 특정 url로 이동하고자 할 때 사용
            return redirect('profile')
    else:
        form = ProfileForm(instance=profile)

    return render(request, 'accounts/profile_form.html', {
        'form' : form,
    })

 

 

#accounts/forms.py

1from dataclasses import field
from django import forms
from .models import Profile


class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = [
            'address', 'zipcode'
        ]

 

 

#accounts/templates/accounts/profile.html

{% extends "accounts/layout.html" %}

{% block content %}
    <h2>User: {{ user }}</h2>

    {% if user.profile %}
        <table>
            <li>{{ user.profile.address }}</li>
            <li>{{ user.profile.images.url }}</li>
        </table>
    {% endif %}

    <a href="{% url 'profile_edit' %}" class="btn btn-primary">
        프로필 수정
    </a>
 
{% endblock content %}

 

 

#accounts/templates/accounts/profile_form.html

{% extends "accounts/layout.html" %}
{% load bootstrap4 %}

{% block content %}
<form action="" method="post">
    {% csrf_token %}
    {% bootstrap_form form %}
    {% buttons %}
        <button class="btn btn-primary">프로필 수정</button>
    {% endbuttons %}
</form>
{% endblock content %}

 

 

 

 

 

 

자세히 알아보기

        form = ProfileForm(request.POST, request.FILES, instance=profile)
  • views.py의 위 코드에서 인자들의 의미
    • request.POST : Form-data에 존재하는 text 형식의 객체를 불러올 수 있다.
    • requset.FILES : Form-data에 존재하는 File 형식의 객체를 불러올 수 있다.]

 

        if form.is_valid():
            form.save()
            return redirect('profile')
  • views.py의 위 코드의 의미
    • redirect는 view 함수내에서 특정 url로 이동하고자 할 때 사용하고, redirect('profile')은 문자열 그대로 profile 문자열 주소로 이동한다.
    • https://devdongbaek.tistory.com/77

 

 


 

<h2>User: {{ user }}</h2>
  • profile.html의 user는 settings.py의 'context_processors'의 auth를 통해 받는 user 모델 객체이다.
    • 로그인 시 : User 모델 인스턴스(객체)
    • 로그아웃 시 : AnonymousUser

 


 


 

 

 

 

 

 


템플릿 레이아웃에 링크 추가하기

 

 

코드

#inflearn/templates/inflearn/layout.html

{% load bootstrap4 static %}

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title> {% block title %}{% endblock title %} </title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.2-dist/css/bootstrap.css' %}" />
    <script src="{% static 'jquery.csrf.js' %}"></script>
    <script src="{% static 'jquery-3.6.0.min.js' %}" ></script>
    <script src="{% static 'bootstrap-3.3.2-dist/js/bootstrap.js' %}"></script>
    <style>
        body{
            padding-top: 5rem;
            padding-bottom: 2rem;
        }
        .nav-item{
            padding-left: 5rem;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
        <div class="collapse navbar-collapse" id="navbarsExampleDefault">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="{% url 'instagram:post_list' %}">Home<span class="sr-only"></span></a>
                </li>
                {% if not user.is_authenticated %}
                    <span class="nav-item">
                        <a class="nav-link" href="#">회원가입</a>
                    </span>
                    <span class="nav-item">
                        <a class="nav-link" href="{% url 'login' %}">로그인</a>
                    </span>
                {% else %}
                    <span class="nav-item">
                        <a class="nav-link" href="{% url 'profile' %}">프로필</a>
                    </span>
                    <span class="nav-item">
                        <a class="nav-link" href="#">로그아웃</a>
                    </span>
                {% endif %}
            </ul>                 
        </div>
      </nav>
      
      <div class="container">
          <div class="row">
              <div class="col-sm-12">
                    {% bootstrap_messages %}                                  
                    {% if messages %}  
                        <div class="messages">
                            {% for message in messages %}
                                <div class="alert alert-{{ message.tags }}">
                                    {{ message.message }}                       
                                </div>
                            {% endfor %}
                        </div>    
                    {% endif %}
                   
                    {% block content %}
                    {% endblock content %}
                  
                  <hr/>
                  &copy; Dongbaek
              </div>
          </div>
      </div>
</body>
</html>

 

 

 

 

자세히 알아보기

                {% if not user.is_authenticated %}
                    <span class="nav-item">
                        <a class="nav-link" href="#">회원가입</a>
                    </span>
                    <span class="nav-item">
                        <a class="nav-link" href="{% url 'login' %}">로그인</a>
                    </span>
  • layout.html에서 위의 코드의 의미
    • 유저가 로그인 되어있지 않다면 상단에 회원가입로그인만 노출시켜라

 

                {% else %}
                    <span class="nav-item">
                        <a class="nav-link" href="{% url 'profile' %}">프로필</a>
                    </span>
                    <span class="nav-item">
                        <a class="nav-link" href="#">로그아웃</a>
                    </span>
                {% endif %}
  • layout.html에서 위의 코드의 의미
    • 유저가 로그인 되어있다면 상단에 프로필로그아웃만 노출시켜라

 

 

 


 

퀴즈를 맞춰야 로그인 되게하기

 

 

코드

#accounts/urls.py

from http.client import ImproperConnectionState
from django.contrib.auth.views import LoginView
from django.contrib.auth.forms import AuthenticationForm
from django.urls import path
from django.conf import settings
from django.conf.urls import static

from dongbaek.accounts.forms import LoginForm
from . import views


urlpatterns = [
    path('login/', LoginView.as_view(
        form_class=LoginForm,
        template_name='accounts/login_form.html'
        ), name='login'),
    path('logout/', views.logout , name='logout'),


    path('profile/', views.profile , name='profile'),
    path('profile/edit/', views.profile_edit , name='profile_edit'),

    path('signup/', views.signup , name='signup'),

]

 

#accounts/forms.py

from dataclasses import field
from django import forms
from .models import Profile
from django.contrib.auth.forms import AuthenticationForm



class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = [
            'address', 'zipcode'
        ]

    
class LoginForm(AuthenticationForm): # AuthenticationForm은 인증을 도와주는 Form
    answer = forms.IntegerField(help_text='3 + 3 = ?') # help text는 form 아래 작은 도움말

    def clean_answer(self):
        answer = self.cleaned_data.get('answer') # 입력받은 값을 answer 변수에 넣어줌
        if answer != 6:
            raise forms.ValidationError('틀렸습니다!')
        return answer

 

 

 

자세히 알아보기

urlpatterns = [
    path('login/', LoginView.as_view(
        form_class=AuthenticationForm,
        template_name='accounts/login_form.html'
        ), name='login'),
        ]
  • urls.py에서 위 form_class의 의미
    • 실제 내역들이 적용이 되는 Form

 


 

회원가입

 

 

GitHub - django/django: The Web framework for perfectionists with deadlines.

The Web framework for perfectionists with deadlines. - GitHub - django/django: The Web framework for perfectionists with deadlines.

github.com

 

 

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

 

 

코드

#accounts/urls.py

urlpatterns = [

    path('signup/', views.signup , name='signup'),

]

 

 

#accounts/views.py

from django.contrib.auth import get_user_model, login
from django.contrib.auth.forms import UserCreationForm


User = get_user_model() #django.contrib.auth.models.User를 가리킨다


class SignupView(CreateView):
    model = User
    form_class = UserCreationForm
    success_url = settings.LOGIN_URL
    template_name = 'accounts/signup_form.html'


signup = CreateView.as_view()
  • 회원 가입 후 settings.LOGIN_URL로 redirect한다.

 

#accounts/signup_form.html

{% extends "accounts/layout.html" %}
{% load bootstrap4 %}

{% block content %}
<form action="" method="post">
    {% csrf_token %}
    {% bootstrap_form form %}
    {% buttons %}
        <button class="btn btn-primary">회원가입</button>
    {% endbuttons %}
</form>
{% endblock content %}

 

 

 

 

자세히 알아보기


 

  • Form을 이용한 각 템플릿들은 거의 유사하다. =signup_form.hmtl, profile_form.html etc...

 

  • 장고 내부의 User로 할 수 있는 게 제약이 많다. 따라서 내가 User를 models.py에 만들어준다. 그리고 settings.py에서 AUTH_USER_MODEL 설정을 해준다. 그 후에 get_user_model()을 이용해서 클래스로 사용해준다. 

회원가입 하자마자 로그인하기

    def form_valid(self, form):
        """If the form is valid, save the associated model."""
        self.object = form.save()
        return super().form_valid(form)
  • form이 유효성 검사에 통과하였다면 form을 model에 저장한다.

 

 

 

코드

#accounts/views.py

from django.contrib.auth import get_user_model, login as auth_login

User = get_user_model()

class SignupView(CreateView):
    model = User
    form_class = UserCreationForm
    success_url = settings.LOGIN_REDIRECT_URL
    template_name = 'accounts/signup_form.html'

    def form_valid(self, form):
        response = super().form_valid(form) # 부모를 호출한다.
        user = self.object # self.object는 이 뷰가 표시하는 객체를 의미한다.
        auth_login(self.request, user)
        return response

signup = SignupView.as_view()

 

 

자세히 알아보기

  • auth_login() 함수는 이름과는 달리 실제로는 인증 과정 마무리 단계를 담당합니다. 로그인 양식을 토대로 이용자 정보를 가져와서 HTTP Request(request) 정보와 함께 사용해 서버 세션 정보를 만듭니다. 세션 정보를 만들지 않으면 로그인 정보는 유지되지 않아서 다른 페이지에 방문할 때마다 매번 로그인을 해야 합니다.
  • auth_login은 인자로 request와 user를 받는다. 

 

 

 


로그아웃

class LogoutView(SuccessURLAllowedHostsMixin, TemplateView):
    """
    Log out the user and display the 'You are logged out' message.
    """
    next_page = None
    redirect_field_name = REDIRECT_FIELD_NAME
    template_name = 'registration/logged_out.html'
    extra_context = None

    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        auth_logout(request)
        next_page = self.get_next_page()
        if next_page:
            # Redirect to this page until the session has been cleared.
            return HttpResponseRedirect(next_page)
        return super().dispatch(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        """Logout may be done via POST."""
        return self.get(request, *args, **kwargs)

    def get_next_page(self):
        if self.next_page is not None:
            next_page = resolve_url(self.next_page)
        elif settings.LOGOUT_REDIRECT_URL:
            next_page = resolve_url(settings.LOGOUT_REDIRECT_URL)
        else:
            next_page = self.next_page

        if (self.redirect_field_name in self.request.POST or
                self.redirect_field_name in self.request.GET):
            next_page = 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=next_page,
                allowed_hosts=self.get_success_url_allowed_hosts(),
                require_https=self.request.is_secure(),
            )
            # Security check -- Ensure the user-originating redirection URL is
            # safe.
            if not url_is_safe:
                next_page = self.request.path
        return next_page

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        current_site = get_current_site(self.request)
        context.update({
            'site': current_site,
            'site_name': current_site.name,
            'title': _('Logged out'),
            'subtitle': None,
            **(self.extra_context or {})
        })
        return context


def logout_then_login(request, login_url=None):
    """
    Log out the user if they are logged in. Then redirect to the login page.
    """
    login_url = resolve_url(login_url or settings.LOGIN_URL)
    return LogoutView.as_view(next_page=login_url)(request)


def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
    """
    Redirect the user to the login page, passing the given 'next' page.
    """
    resolved_url = resolve_url(login_url or settings.LOGIN_URL)

    login_url_parts = list(urlparse(resolved_url))
    if redirect_field_name:
        querystring = QueryDict(login_url_parts[4], mutable=True)
        querystring[redirect_field_name] = next
        login_url_parts[4] = querystring.urlencode(safe='/')

    return HttpResponseRedirect(urlunparse(login_url_parts))

 

 

 

코드

#settings.py

LOGOUT_REDIRECT_URL = reverse_lazy('login')
  • reverse가 아닌 reverse_lazy를 사용하는 이유는 urls.py가 메모리에 올라왔지만 views.py가 메모리에 올라와 있지 않으면 reverse호출 시 에러가 발생하기 때문이다.

 

 

#accounts/urls.py

from django.contrib.auth.views import LogoutView

urlpatterns = [

    path('logout/', LogoutView.as_view() , name='logout'),

]

 

 

#inflearn/templates/inflearn/layout.html

                {% else %}
                    <span class="nav-item">
                        <a class="nav-link" href="{% url 'profile' %}">프로필</a>
                    </span>
                    <span class="nav-item">
                        <a class="nav-link" href="{% url 'logout' %}?next={{ request.get_full_path }}">로그아웃</a>
                    </span>
                {% endif %}
  • next를 인자로 주어서 로그아웃 후 이동할 주소를 설정한다.
  • next 값이 없다면 global_settings.LOGOUT_REDIRECT_URL에서 설정한 값에 따라 이동한다.

 

 

 

자세히 알아보기

<a class="nav-link" href="{% url 'logout' %}?next={{ request.get_full_path }}">로그아웃</a>
  • layout.html에서 위 코드 의미
    • 예를 들어서 "instagram/14/"에서 로그아웃을 하면 로그아웃 후 다시 "instagram/14/"주소로 돌아온다.
    • 마찬가지로 "accounts/profile/"에서 로그인을 눌러 로그인 화면에서 로그인을 마치면 다시 "accounts/profile/"로 돌아온다.
    • 마지막으로 한가지 예시만 더 든다면, 쇼핑몰에서 원하는 상품을 보다가 구매를 하고 싶어서 상품창에서 로그인을 눌러서 로그인을 마치면 다시 전에 보던 상품창으로 돌아가게 한다.

 

 


 

도움 받은 문서

반응형

'강의 정리 > Django Form' 카테고리의 다른 글

Django 빌트인 CBV를 통한 Form 처리  (0) 2022.01.24
Django Form 삭제 구현!  (0) 2022.01.24
Django Messages Framework  (0) 2022.01.24
Django Form Validation  (0) 2022.01.19
Django ModelForm이란?  (0) 2022.01.19