2022. 9. 26. 10:07ㆍHow to become a real programmer/Back-End
피드 생성하는 것 까지는 완료했다.
이번엔 로그인 구현을 할 차례이다. 각 사용자마다 자신의 계정에 대한 피드 생성 권한을 주어야 하기 때문이다.
우선은 로그인을 위해서는 사용자 모델이 있어야 한다. 사실 우리는 먼저 웹 사이트 구현부터 했지만 장고에서는 프로젝트를 만들 때 원래 모델 먼저 만들어야 한다. 그 이유는, 개발자가 사용자 모델을 커스텀 하여 따로 만들고자 한다면 마이그레이션 이전에 해당 모델을 만들어 두고, 마이그레이션해야 적용이 되기 때문이다. 장고에서 기본적으로 만들어놓은 사용자 모델을 마이그레이션 해버리면 그 후로 커스텀 모델을 적용할 수가 없다. 디폴트로 사용되는 유저모델은 하나여야 하기 때문이다. 즉, 만들려면 db를 날리고 새로 만들어야 한다.
장고에서 기본으로 제공되는 db를 보면 auth_user이라는 db 테이블이 있는데, 사용자를 관리하는 기본적인 세팅이 되어 있는 것이다. 그렇다고 절대적으로 이 테이블을 사용해야 하는 것은 아니다. 만들고자 하는 다른 사용자 모델이 있다면 그것을 만들어서 사용해도 상관없다.
그래도 장고에서 기본으로 제공하는 사용자 모델을 사용하면 장점이 있는데, 패스워드 암호화 같은 함수들을 장고에서 미리 정의 된 것을 갖다 쓸 수도 있고, 로그인 세션 처리를 하는 것을 장고에서 다 마련해두었기 때문이다. 그래서 만약 우리가 원하는 칼럼이 없다면, 이 auth_user을 상속받아서 만들면 쉽다.
우선 이전에 Python - Django를 이용한 인스타그램 클론 - 2에서 user 앱을 만들었었다. 해당 폴더에 들어가 models.py 부분을 만들어주면 사용자 모델을 설정할 수 있는 것이다.
from django.contrib.auth.base_user import AbstractBaseUser
from django.db import models
# Create your models here.
class User(AbstractBaseUser):
"""
유저 프로필 사진
유저 닉네임 -> 화면에 표기되는 이름
유저 이름 -> 실제 사용자 이름
유저 이메일 주소 -> 회원 가입시 사용하는 아이디
유저 비밀번호 -> 장고에서 제공하는 디폴트 사용
"""
profile_image = models.TextField()
nickname = models.CharField(max_length=24)
name = models.CharField(max_length=24)
email = models.EmailField()
"""
만약에 테이블 명을 안정하면 user(앱이름)_User(클래스명) 이런 식으로 생성될 것이다
Meta 클래스를 통해 db_table의 이름을 설정할 수 있다
"""
class Meta:
db_table = "User"
AbstractBaseUser을 상속해온 User클래스는 유저 모델이다. 그래서 클래스 안에 저렇게 프로필 사진, 닉네임, 이름, 이메일 주소 까지 우선 만들어 주었고, db_table 이름을 User로 설정해주었다.
장고 커스텀 유저 모델 만드는 것은 https://dev-yakuza.posstree.com/ko/django/custom-user-model/ 블로그를 참고하면 될 것 같다.
장고(django)의 커스텀 유저 모델(Custom User Model)
장고(django) 프로젝트에서 사용되는 유저 모델(User Model)을 입맛에 맞게(Customization) 수정하여 사용해 봅시다.
dev-yakuza.posstree.com
UserManager은 무엇일까?
UserManager은 실제 사용자를 생성할 때 기능한다. UserManager은 User을 생성하는 클래스인 BaseUserManager을 상속함으로서 이러한 역할을 한다.
우리가 모델로 만든 AbstractBaseUser 클래스는 모델을 잡아 생성하는 클래스라 생성하는 클래스라 생각하면 된다.
UserManager 클래스는 2가지 함수가 있다.
1. create_user() : User 생성 함수
2. create_superuser() : 관리자 User 생성 함수
이 그것이다.
Django 커스텀 유저 모델(Custom User Model)
Django Custom Model
hckcksrl.medium.com
그런데 우리가 구현하는 부분에 있어서는 UserManager가 필수적이지 않기에, 그냥 User클래스로만 한다.
우리가 만든 models.py를 마이그레이션 하기 위해서는 기존에 있던 db를 삭제해야한다. db.sqlite3를 삭제하면 된다. 그런데 그렇게 되면 우리가 만든 content_feed가 사라지기 때문에 그 안에 있던 데이터들은 얼마 안되니 복사해서 엑셀같은데 붙여넣기 해서 백업해주자.
그리고 나서 db.sqlite3를 삭제해주면 된다. 삭제가 안된다면 데이터베이스 창에서 빨간 네모박스(중지버튼)을 누르고, Do Refactor 눌러주면 된다.
이렇게 db를 날렸으니 이제 마이그레이션 해주자.
마이그레이션 명령어인
python manage.py makemigrations
을 해주면 된다. 우리는 python3.,,,버전이므로 python3 manage.py makemigrations해주면 되겠다(하,, 저 python3 때문에 여기서 1시간을 날릴 줄이야)
그리고 python3 manage.py migrate 해주면 db.sqlite3가 생성될 것이다.
그리고 Database창에 새로고침 눌러주면,
User 테이블이 생겼다.
그리고 settings.py에 어떤 유저 모델을 쓸지 지정해주면 된다.
# 커스텀 유저 모델 사용
AUTH_USER_MODEL = 'user.User'
settings.py 가장 아래에 추가해주었다. 그리고 저장 후, 다시 마이그레이트 해줘서 db생성 한다. 이때 바로 하면 에러가 뜨는데 USERNAME이 무엇인지 설정하라고 뜰 것이다.
profile_image = models.TextField()
nickname = models.CharField(max_length=24, unique=True)
name = models.CharField(max_length=24)
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email'
user/models.py의 User 클래스에서 nickname과 email에 unique key라는 점을 알려주고, USERNAME_FIELD가 email임을 넣어주면 된다.
그리고 다시 마이그레이션을 해주자(db삭제 후)
우리가 만든 user테이블의 모습이다. models에 만들어둔 column 처럼 잘 만들어져 있다.
아까 백업해둔 content_feed 내용을 다시 content_feed 에 넣어주자.
이러고 실행하면 기존과 동일한 피드가 불러와진다.
이렇게, 유저 모델을 만들었으니. 로그인 폼을 만들어보자. 이전에 template - user 폴더를 만들었으니 해당 폴더에 join.html을 하나 만들어준다.
Bootstrap을 쓸 거라 밑에 스타터 탬플릿을 복붙하면된다.
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
urls.py에 연결해줄텐데, 그 전에 해당 코드를 잘 보자.
"""Minstagram URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from .views import Sub
from content.views import Main, UploadFeed
urlpatterns = [
path('admin/', admin.site.urls),
path('main/', Main.as_view()),
path('content/upload', UploadFeed.as_view())
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# 미디어 내 파일 조회를 위한 코드
우리는 여기서 한 가지 특이한 점을 알 수 있는데 main/ 경로로 가면 나오는 Main 클래스는 content/views에 있다. 또한 UploadFeed도 마찬가지로 content/views에 있다. 그런데 urls.py는 Minstagram에 있다는 건 좀 언벨런스 하다. 장고는 앱 단위로 나눌 수 있으므로 urls.py를 나눠주자.
우선 content에 urls.py를 생성해주고 아래 내용을 붙여준다.
from django.urls import path
from .views import Main, UploadFeed
urlpatterns = [
path('upload', UploadFeed.as_view())
]
그리고 minstagram/urls.py 에서 기존 UploadFeed 클래스를 쓰던 부분을 아래와 같이 수정해준다.(복붙해도 됨)
"""Minstagram URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from .views import Sub
from content.views import Main, UploadFeed
urlpatterns = [
path('admin/', admin.site.urls),
path('main/', Main.as_view()),
#path('content/upload', UploadFeed.as_view())
path('content/', include('content.urls')),
path('user/', include('user.urls'))
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# 미디어 내 파일 조회를 위한 코드
이렇게 분할하게 사용하게 되면 나중에 서비스가 커지고 url을 구성하는 경로가 많아질 때 나누어서 관리하는 이점이 있다.
그리고 user 앱도 urls와 연결시켜야 하므로
user - <urls.py>
from django.urls import path
from .views import Join
urlpatterns=[
path('join', Join.as_view())
]
위와 같이 해서 user/join 경로로 접속 시 Join클래스가 실행되게 해주고
user - <views.py>
from django.shortcuts import render
from rest_framework.views import APIView
# Create your views here.
class Join(APIView):
def get(self, request):
return render(request, "user/join.html")
views에서 Join클래스를 만들어 template내 user/join.html이 렌더되게 해주면 된다, 그리고
path('', include('user.urls'))
Minstagram - urls.py의 path로 user의 urls를 쓰겠다고 추가해주면 된다. 잘 실행이 되나 확인해보겠다.
잘 연결이 된 것을 확인할 수 있다.
minstagram - urls에서 path의 앞 부분 인자로 전달해주는 것과 해당 앱 urls에서 path를 가리킬 때 앞 부분인자가 합쳐져 저렇게 경로를 만드는 것이다.
이제 로그인 페이지를 만들어야 하는데, 그 과정은 html이 대부분이라 소스코드를 아래 따로 두었다.
template - user - <join.html>
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body style="background: #FAFAFA">
<div style="display:flex; flex-direction:column; align-items: center; width:100%">
<div style="padding:20px; text-align: center; border: solid 1px rgba(219, 219, 219, 1); width:300px; height:400px; margin-top:30px; background:white;">
<div style="text-align: center;">
<img style="padding-bottom: 10px; width:150px"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo-2x.png/1b47f9d0e595.png">
</div>
<div style="padding-bottom: 20px; font-weight: bold; color:gray; ">친구들의 사진과 동영상을 보려면 가입하세요.</div>
<div>
<div class="form-floating mb-3">
<input type="email" class="form-control" style="font-size:14px; height:24px;" id="floatingInput"
placeholder="name@example.com">
<label style="font-size:14px; padding:4px 10px" for="floatingInput">이메일 주소</label>
</div>
</div>
<div>
<div class="form-floating mb-3">
<input type="text" class="form-control" style="font-size:14px; height:24px;" id="floatingInput"
placeholder="성명">
<label style="font-size:14px; padding:4px 10px" for="floatingInput">성명</label>
</div>
</div>
<div>
<div class="form-floating mb-3">
<input type="text" class="form-control" style="font-size:14px; height:24px;" id="floatingInput"
placeholder="닉네임">
<label style="font-size:14px; padding:4px 10px" for="floatingInput">닉네임</label>
</div>
</div>
<div>
<div class="form-floating">
<input type="password" class="form-control" style="font-size:14px; height:24px;" id="floatingPassword"
placeholder="Password">
<label style="font-size:14px; padding:4px 10px;" for="floatingPassword">비밀번호</label>
</div>
</div>
<button class="btn btn-primary" style="margin-top:10px; width:100%">가입</button>
</div>
<div style="padding:20px; text-align: center; border: solid 1px rgba(219, 219, 219, 1); width:300px; height:70px; margin-top:30px; background:white;">
<div>계정이 있으신가요? <a href="login">로그인</a></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous">
</script>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</body>
</html>
template - user - <login.html>
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body style="background: #FAFAFA">
<div style="display:flex; flex-direction:column; align-items: center; width:100%">
<div style="padding:20px; text-align: center; border: solid 1px rgba(219, 219, 219, 1); width:300px; height:300px; margin-top:30px; background:white;">
<div style="text-align: center;">
<img style="padding-bottom: 10px; width:150px"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo-2x.png/1b47f9d0e595.png">
</div>
<div style="padding-bottom: 20px; font-weight: bold; color:gray; ">친구들의 사진과 동영상을 보려면 가입하세요.</div>
<div>
<div class="form-floating mb-3">
<input type="email" class="form-control" style="font-size:14px; height:24px;" id="floatingInput"
placeholder="name@example.com">
<label style="font-size:14px; padding:4px 10px" for="floatingInput">이메일 주소</label>
</div>
</div>
<div>
<div class="form-floating">
<input type="password" class="form-control" style="font-size:14px; height:24px;" id="floatingPassword"
placeholder="Password">
<label style="font-size:14px; padding:4px 10px;" for="floatingPassword">비밀번호</label>
</div>
</div>
<button class="btn btn-primary" style="margin-top:10px; width:100%">로그인</button>
</div>
<div style="padding:20px; text-align: center; border: solid 1px rgba(219, 219, 219, 1); width:300px; height:70px; margin-top:30px; background:white;">
<div>계정이 없으신가요? <a href="join">가입하기</a></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous">
</script>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</body>
</html>
이렇게 하고, login 창을 하나 만들어 줬기에 user-views.py에
from django.shortcuts import render
from rest_framework.views import APIView
# Create your views here.
class Join(APIView):
def get(self, request):
return render(request, "user/join.html")
class Login(APIView):
def get(self, request):
return render(request, "user/login.html")
위 코드를 추가해주고,
user-urls.py에
from django.urls import path
from .views import Join, Login
urlpatterns=[
path('join', Join.as_view()),
path('login', Login.as_view())
]
위와 같이 경로를 추가해주면 아래와 같은 창이 될 것이다.
'How to become a real programmer > Back-End' 카테고리의 다른 글
Python - Django를 이용한 인스타그램 클론 - 7 (0) | 2022.11.08 |
---|---|
Python - Django를 이용한 인스타그램 클론 - 6 (0) | 2022.10.02 |
Python - Django를 이용한 인스타그램 클론 - 4 (2) | 2022.09.21 |
Python - Django를 이용한 인스타그램 클론 - 3 (2) | 2022.09.20 |
Python - Django를 이용한 인스타그램 클론 - 2 (0) | 2022.09.17 |