🧩

011 회원가입 | 로그인 | 로그아웃

 
config/urls.py를 수정할 것입니다. 수정하기 전에 urls.py의 특성을 간단히 짚고 가겠습니다.
notion imagenotion image
notion imagenotion image
 
파일명 : config/urls.py
urlpatterns = [ path('admin/', admin.site.urls), ]
 
이 부분에서 URL 형식에 따른 site 연결이 이루어졌습니다. https://*.run.goorm.io/admin URL을 통해 admin 페이지로 접속했었습니다. 만약에 여기서 코드를 아래와 같이 수정하면 어떻게 될까요?
 
파일명 : config/urls.py
urlpatterns = [ path('adi/', admin.site.urls), ]
 
https://*.run.goorm.io/adi 로 admin 사이트에 접속할 수 있게됩니다. 이처럼 urls.py 에서 사용자가 접속하는 URL에 따라서 설정을 달리할 수 있습니다.
 

1. config/urls.py

먼저, 필요한 기능을 import 하는 코드를 추가합니다.
 
파일명 : config/urls.py
from django.urls import include, path from django.conf import settings from django.conf.urls.static import static
 
그러고 나서, urlpatterns에 path들을 추가합니다.
 
파일명 : config/urls.py
from django.contrib import admin from django.urls import include, path from django.conf import settings urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accounts.urls')), ]
 
이렇게 설정하면 accounts 앱에서 독립적으로 URL을 운영할 수 있습니다. settings.py에서 설정한 static 파일 관련 값들을 가져옵니다.
 
파일명 : config/urls.py
from django.contrib import admin from django.urls import include, path from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accounts.urls')), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 

2. accounts/urls.py

아까 accounts 앱에서 URL을 운영하도록 설정했으니 accounts 앱의 urls.py를 열겠습니다. 없으면 urls.py 파일을 만들면 됩니다.
 
파일명 : accounts/urls.py
from django.urls import path from .views import * app_name = 'accounts' urlpatterns = [ path('signup/', signup, name='signup'), path('login/', login_check, name='login'), path('logout/', logout, name='logout'), ]
 
이렇게 작성하게 되면 accounts/signup/, accounts/login/, accounts/logout/ URL로 연결이 되고, signup, login_check, logout 기능들은 views.py 에서 정의할 것입니다.
 

3. accounts/views.py

views.py는 사용자가 URL을 통해 접근했을 때 구체적으로 어떤 일을 수행하는지 정하는 곳입니다. accounts/views.py 으로 가서 편집합니다.
 
먼저, 기능들을 import해서 가져옵니다.
 
파일명 : accounts/views.py
from django.contrib.auth import authenticate, login from django.contrib.auth import logout as django_logout from django.contrib.auth.forms import AuthenticationForm from django.shortcuts import redirect, render
 
  • authenticate : 인증 기능
  • loign : 로그인 기능
  • redirect : 로그인을 했을 시 어떤 페이지로 보낼지에 관한 기능
  • render : template을 랜더링하는 기능
  • logout : 로그아웃 기능
 
form을 정의해서 사용하기 때문에 따로 파일을 만들어서 불러오도록 하겠습니다.
 
파일명 : accounts/views.py
from django.contrib.auth import authenticate, login from django.contrib.auth import logout as django_logout from django.contrib.auth.forms import AuthenticationForm from django.shortcuts import redirect, render from .forms import SignupForm
 
잠깐 SignupForm의 형식 틀만 만들고 가겠습니다. accounts/forms.py 생성해주시고 클래스만 만들고 가겠습니다.
 
파일명 : accounts/forms.py
from django import forms from django.contrib.auth.forms import UserCreationForm class SignupForm(UserCreationForm): pass
 
accounts/views.py로 돌아옵니다. 뷰를 작성하도록 하겠습니다. 뷰는 함수로 만드냐, 클래스로 만드냐에 따라서 함수형 뷰, 클래스형 뷰로 나뉩니다. 이번에는 함수형 뷰로 만들 것입니다.
 

1. 회원가입 함수

파일명 : accounts/views.py
... def signup(request): if request.method == 'POST': form = SignupForm(request.POST, request.FILES) if form.is_valid(): user = form.save() return redirect('accounts:login') else: form = SignupForm() return render(request, 'accounts/signup.html', { 'form': form, })
 
처음에 요청이 POST 방식인지 확인합니다. POST 일 경우 forms.py에 정의한 SignupForm을 가져오고, form의 값이 제대로 채워졌을 경우 저장을 하게 됩니다. 저장을 하고 나서 로그인 페이지로 이동시킵니다.
만약, POST가 아닐 경우 회원가입 페이지를 다시 랜더링하게 됩니다.
 

2. 로그인 함수

파일명 : accounts/views.py
... def login_check(request): if request.method == "POST": form = AuthenticationForm(request, request.POST) if form.is_valid(): user = form.get_user() login(request, user) return redirect else: form = AuthenticationForm() return render(request, 'accounts/login.html', {"form":form})
 
POST 요청으로부터 받은 계정과 비밀번호를 가지고 유저인지 확인합니다. 유저라면 로그인을 하고, 루트 URL(/)로 이동하게 됩니다.
유저가 아니라면 로그인 페이지를 재렌더링 합니다. 이때에는 form에 에러 메시지가 담겨 있기 때문에 에러메시지와 함께 렌더링 됩니다.
만약, POST 요청이 아니라면 로그인 페이지를 렌더링합니다.

3. 로그아웃 함수

파일명 : accounts/views.py
... def logout(request): django_logout(request) return redirect("/")
 
로그아웃은 검증이 따로 필요하지 않아서 간단합니다. 로그아웃 처리를 하고, 루트 URL(/)로 이동시킵니다.
 
완성된 코드는 아래와 같습니다.
 
파일명 : accounts/views.py
from django.contrib.auth import authenticate, login from django.contrib.auth import logout as django_logout from django.contrib.auth.forms import AuthenticationForm from django.shortcuts import redirect, render from .forms import SignupForm def signup(request): if request.method == 'POST': form = SignupForm(request.POST, request.FILES) if form.is_valid(): user = form.save() return redirect('accounts:login') else: form = SignupForm() return render(request, 'accounts/signup.html', { 'form': form, }) def login_check(request): if request.method == "POST": form = AuthenticationForm(request, request.POST) if form.is_valid(): user = form.get_user() login(request, user) return redirect("/") else: form = AuthenticationForm() return render(request, 'accounts/login.html', {"form":form}) def logout(request): django_logout(request) return redirect("/")
 

4. forms

우선 Django에 기본적으로 존재하는 User 기능에 관한 것들을 추가로 불러옵니다.
 
파일명 : accounts/forms.py
from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm from .models import Profile from django.contrib.auth.models import User
 
SignupForm의 필드들을 정의합니다.
 
파일명 : accounts/forms.py
... class SignupForm(UserCreationForm): username = forms.CharField(label='사용자명', widget=forms.TextInput(attrs={ 'pattern': '[a-zA-Z0-9]+', 'title': '특수문자, 공백 입력불가', })) nickname = forms.CharField(label='닉네임') picture = forms.ImageField(label='프로필 사진', required=False)
username(사용자명)이 될 수 있는 조건을 설정하고 설명을 붙입니다. nickname 필드를 추가하고, 프로필 사진을 선택 사항(required=False)으로 추가할 수 있도록 정합니다.
 
Meta 클래스를 추가합니다. MetaSignupForm의 내부 클래스가 됩니다. fields 변수에 회원 가입 폼에서 보여줄 필드들을 설정합니다.
 
파일명 : accounts/forms.py
... class SignupForm(UserCreationForm): username = forms.CharField(label='사용자명', widget=forms.TextInput(attrs={ 'pattern': '[a-zA-Z0-9]+', 'title': '특수문자, 공백 입력불가', })) nickname = forms.CharField(label='닉네임') picture = forms.ImageField(label='프로필 사진', required=False) class Meta(UserCreationForm.Meta): fields = UserCreationForm.Meta.fields + ('email',)
 
Meta 클래스를 이용하면 정렬 옵션, 데이터베이스 테이블 이름 등의 모델 단위의 옵션을 정할 수 있습니다. 조금 더 다양한 옵션은 장고의 공식 문서에 상세하게 나와있습니다. 참고로 Meta 클래스는 Django 프레임워크에서 제공하는 옵션 값으로 기본 python 문법에는 존재하지 않습니다. Meta 클래스는 모델 단위의 옵션을 주시고 싶으실 때 사용하는 것이라고 생각하시면 됩니다.
사용자가 회원가입을 위해 입력한 값들이 적절하고 올바른지 판단하는 유효성 검사 기능을 만듭니다. 입력한 닉네임이 이미 존재하는 닉네임인지 확인하는 메소드입니다.
 
파일명 : accounts/forms.py
... def clean_nickname(self): nickname = self.cleaned_data.get('nickname') if Profile.objects.filter(nickname=nickname).exists(): raise forms.ValidationError('이미 존재하는 닉네임 입니다.') return nickname
 
입력한 이메일이 이미 사용 중인 이메일인지 확인합니다.
 
파일명 : accounts/forms.py
... def clean_email(self): email = self.cleaned_data.get('email') User = get_user_model() if User.objects.filter(email=email).exists(): raise forms.ValidationError('사용중인 이메일 입니다.') return email
 
프로필 사진을 확인하고 프로필 사진이 없으면 None 값을 할당하는 기능입니다.
 
파일명 : accounts/forms.py
... def clean_picture(self): picture = self.cleaned_data.get('picture') if not picture: picture = None return picture
 
저장하는 기능입니다. user 모델과 입력된 값들을 바탕으로 프로필을 만듭니다.
 
파일명 : accounts/forms.py
... def save(self): user = super().save() Profile.objects.create( user=user, nickname=self.cleaned_data['nickname'], picture=self.cleaned_data['picture'], ) return user
 
완성된 코드는 아래와 같습니다.
 
파일명 : accounts/forms.py
from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from .models import Profile class SignupForm(UserCreationForm): username = forms.CharField(label='사용자명', widget=forms.TextInput(attrs={ 'pattern': '[a-zA-Z0-9]+', 'title': '특수문자, 공백 입력불가', })) nickname = forms.CharField(label='닉네임') picture = forms.ImageField(label='프로필 사진', required=False) class Meta(UserCreationForm.Meta): fields = UserCreationForm.Meta.fields + ('email',) def clean_nickname(self): nickname = self.cleaned_data.get('nickname') if Profile.objects.filter(nickname=nickname).exists(): raise forms.ValidationError('이미 존재하는 닉네임 입니다.') return nickname def clean_email(self): email = self.cleaned_data.get('email') User = get_user_model() if User.objects.filter(email=email).exists(): raise forms.ValidationError('사용중인 이메일 입니다.') return email def clean_picture(self): picture = self.cleaned_data.get('picture') if not picture: picture = None return picture def save(self): user = super().save() Profile.objects.create( user=user, nickname=self.cleaned_data['nickname'], picture=self.cleaned_data['picture'], ) return user
 

5. Template

이제 signup.html 템플릿을 작성합니다. 먼저 accounts 폴더에 templates 라는 폴더를 만듭니다.
notion imagenotion image
notion imagenotion image
 
만든 templates 폴더 아래 accounts 폴더를 만듭니다.
notion imagenotion image
notion imagenotion image
 
 
만들어진 accounts 폴더 아래에 signup.html을 생성합니다.
notion imagenotion image
notion imagenotion image
 
동일한 위치에 layout.html 을 생성합니다.
notion imagenotion image
notion imagenotion image
 
layout.html에 들어갈 내용은 다음과 같습니다. extends 템플릿 태그를 사용합니다.
이와 같이 작성하면 config/templates/layout.html 내용을 가져와서 사용할 수 있습니다.
 
파일명 : accounts/templates/accounts/layout.html
{% extends "layout.html" %}
 
signup.html 내용에는 아래와 같이 작성합니다. static 파일들을 사용하기 위해서는 {% load static %} 태그를 추가해야 합니다. head 블록 영역을 만들고, login.css를 추가로 연결합니다. 내용이 들어갈 content 블록 영역을 설정합니다.
 
파일명 : accounts/templates/accounts/signup.html
{% extends "accounts/layout.html" %} {% load static %} {% block head %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} {% endblock %}
 
layout.html은 단계적인 구조로 사용하게 됩니다. 그리고 block content 영역에 HTML 내용을 채울 것입니다.
 
HTML은 인스타그램 클론 프론트엔드 파일을 이용할 것입니다. 아래의 링크를 확인하면, 아래와 같이 호랑이 캐릭터가 있습니다.
 
캐릭터를 클릭하게 되면 소스를 다운로드할 수 있습니다.
notion imagenotion image
 
index.html을 열도록 하겠습니다.
notion imagenotion image
 
 
지금 로그인 기능과 관련된 template을 작성하고 있기 때문에 로그인 페이지로 갑니다.
notion imagenotion image
 
개발자 도구를 열어서 내용을 삽입하게 될 부분인 div#main_container 영역을 선택합니다.
notion imagenotion image
 
그리고 나서 Copy > Copy element 항목을 선택하면 해당 부분의 HTML 만 복사할 수 있습니다.
notion imagenotion image
 
복사한 코드를 signup.html 의 block content 영역에 붙여 넣습니다.
 
파일명 : accounts/templates/accounts/signup.html
{% extends "accounts/layout.html" %} {% load static %} {% block head %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} <div id="main_container"> <div class="form_container"> <div class="form"> <h1 class="sprite_insta_big_logo title"></h1> <form action="#"> <p class="login_user_name"> <label for="user_name">사용자명:</label> <input type="text" id="user_name"> </p> <p class="login_user_password"> <label for="user_password">비밀번호:</label> <input type="text" id="user_password"> </p> <input type="submit" id="submit_btn" value="로그인" class="submit_btn"> </form> </div> <div class="bottom_box"> <div> <span>아이디가 없으신가요?</span><a href="#">회원가입</a> </div> </div> </div> </div> {% endblock %}
 
여기서 HTML 코드 몇 가지를 수정합니다. h1 태그, form 태그는 내부 내용을 지우고 속성들을 수정합니다.
 
파일명 : accounts/templates/accounts/signup.html
{% extends "accounts/layout.html" %} {% load static %} {% block head %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} <div id="main_container"> <div class="form_container"> <div class="form"> <form action="" method="post" enctype="multipart/form-data"> </form> </div> <div class="bottom_box"> <div> <span>아이디가 없으신가요?</span><a href="#">회원가입</a> </div> </div> </div> </div> {% endblock %}
 
form 태그 안에 {% csrf_token %} 라고 작성합니다. 이것은 Django에서 제공하는 기능입니다. form 데이터를 보낼 때마다 무작위 문자열을 생성해서 보안을 강화하는 기능을 합니다.
 
파일명 : accounts/templates/accounts/signup.html
... <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} </form> ...
 
{{ form.as_p }} 을 추가합니다. 이것은 form을 p 태그로 랜더링 한다는 의미입니다.
 
파일명 : accounts/templates/accounts/signup.html
... <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} </form> ...
 
submit button을 추가합니다.
 
파일명 : accounts/templates/accounts/signup.html
... <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <input class="submit_btn" type="submit" value="가입"> </form> ...
 
마지막으로 span 태그 내용을 수정합니다. 원래 로그인 페이지에서 가져왔기 때문에 회원 가입 페이지처럼 내용이 바뀌어야 합니다.
 
완성된 코드는 아래와 같습니다. a 태그의 링크는 나중에 다른 파일이 완성되는 대로 연결하겠습니다.
 
파일명 : accounts/templates/accounts/signup.html
{% extends "accounts/layout.html" %} {% load static %} {% block head %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} <div id="main_container"> <div class="form_container"> <div class="form"> <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <input class="submit_btn" type="submit" value="가입"> </form> </div> <div class="bottom_box"> <div> <span>아이디가 있다면? </span><a href="{% url 'accounts:login' %}">로그인</a> </div> </div> </div> </div> {% endblock %}
 

6. login.html

다음으로 login.html을 작성하겠습니다. 파일이 없다면 만들어줍니다.
notion imagenotion image
 
notion imagenotion image
 
signup.html 작성할 때와 처음 부분이 유사합니다. 아래와 같이 기본적인 부분을 작성합니다.
 
파일명 : accounts/templates/accounts/login.html
{% extends "accounts/layout.html" %} {% load static %} {% block head %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock%} {% block content %} {% endblock %}
 
프론트엔드 소스를 참고하겠습니다. 프론트엔드 소스에서 login.html 파일을 엽니다. 검사 기능을 통해서 항목 틀을 볼 것입니다. 브라우저는 크롬을 기준으로 하겠습니다.
notion imagenotion image
 
항목을 선택하면 개발자도구가 열림과 동시에 마우스 클릭했던 부분에 해당하는 영역이 선택됩니다. content 블록 안에는 div#main_container 부분의 태그들을 붙여 넣습니다.
 
파일명 : accounts/templates/accounts/login.html
{% extends "accounts/layout.html" %} {% load static %} {% block head %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock%} {% block content %} <div id="main_container"> <div class="form_container"> <div class="form"> <h1 class="sprite_insta_big_logo title"></h1> <form action="#"> <p class="login_user_name"> <label for="user_name">사용자명:</label> <input type="text" id="user_name"> </p> <p class="login_user_password"> <label for="user_password">비밀번호:</label> <input type="text" id="user_password"> </p> <input type="submit" id="submit_btn" value="로그인" class="submit_btn"> </form> </div> <div class="bottom_box"> <div> <span>아이디가 없으신가요?</span><a href="#">회원가입</a> </div> </div> </div> </div> {% endblock %}
 
이번에는 form 태그를 수정합니다.
 
파일명 : accounts/templates/accounts/login.html
... <form method="POST" action="{% url 'accounts:login' %}"> {% csrf_token %} {{ form.as_p }} <input class="submit_btn" type="submit" value="로그인"> </form> ...
 
div.bottom_box 에 있는 span 태그에 a 태그를 추가해서 링크를 연결합니다.
 
파일명 : accounts/templates/accounts/login.html
... <div class="bottom_box"> <div> <span>아이디가 없으신가요? </span><a href="{% url 'accounts:signup' %}">회원가입</a> </div> </div> ...
 
완성된 코드는 아래와 같습니다.
 
파일명 : accounts/templates/accounts/login.html
{% extends "accounts/layout.html" %} {% load static %} {% block head %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock%} {% block content %} <div id="main_container"> <div class="form_container"> <div class="form"> <h1 class="sprite_insta_big_logo title"></h1> <form method="POST" action="{% url 'accounts:login' %}"> {% csrf_token %} {{ form.as_p }} <input class="submit_btn" type="submit" value="로그인"> </form> </div> <div class="bottom_box"> <div> <span>아이디가 없으신가요? </span><a href="{% url 'accounts:signup' %}">회원가입</a> </div> </div> </div> </div> {% endblock %}
 

7. 로그인 기능 확인

이제 작성한 것들이 잘 작동하는지 서버를 작동하여 확인해보겠습니다.
(venv)root@goorm:/workspace/instaclone/instaclone# python manage.py runserver 0:80
 
구름IDE에서 편집 창을 분리하는 방법을 사용해보겠습니다.
notion imagenotion image
 
화면 우측 상단의 창 나누기 버튼이 있습니다. 한 번 클릭하면 2개의 창으로 나뉘고, 클릭할 때 마다 창의 개수가 1개씩 증가합니다. 창의 개수를 줄이고 싶으시면, ALT + C 키를 통해 줄일 수 있습니다. 단축키가 아니어도 화면 상단에이라는 항목에서 기능들을 사용할 수 있습니다.
 
이와 같이 터미널 창과 파일 편집 창을 동시에 볼 수 있습니다.
notion imagenotion image
 
서버가 작동이 되셨으면, https://*.run.goorm.io 로 접속합니다.
notion imagenotion image
 
첫 페이지에서 404 페이지가 나타나는데, 아직 루트 URL(/)에 무언가 설정하지 않았기 때문에 404 페이지가 나타나는 것은 정상입니다.
 
지금 확인해야 할 것은 https://*.run.goorm.io/accounts/signup 입니다.
notion imagenotion image
 
위와 같이 회원가입 페이지가 나타납니다. 기능이 작동하는지 확인하겠습니다. 빈칸을 모두 채워서 회원 가입이 되는지 보겠습니다. 가입 버튼을 누르면 로그인 페이지로 오게 됩니다. 방금 생성한 계정을 입력하여 로그인 합니다.
notion imagenotion image
 
로그인 버튼을 누르면, 이와 같은 페이지가 나타납니다. 로그인 후에는 루트 URL(/)로 보내기로 했었습니다. 루트 URL에 해당하는 페이지가 없기 때문에 정상입니다.
notion imagenotion image
 
admin 페이지를 가보면, 인증된 상태라고 나옵니다. 당연히 admin 권한은 없어서 admin 페이지로 접속은 할 수 없습니다.
notion imagenotion image
 
이렇게 회원 가입과 로그인 기능이 잘 작동한다는 것을 확인했습니다.