관리자 페이지를 구현하기 앞서 관리자 페이지는 관리자만 접근할 수 있어야 함으로 다음과 같이 모델을 수정하자
is_staff 라는 테이블을 추가해 이 값이 True냐 False냐로 관리자인지 일반 유저인지 판별할 것이다.
다음으로 지금까진 앱을 직접 실행해 만든 코드들이 정상 작동하는지 확인했지만, 테스트 코드를 작성해 통과한다면, 코드를 리팩토링하는 방법으로 개발을 진행할 것이다. 이러한 과정을 통해 테스트를 통과하기 위한 최소한의 코드를 생성해 표준에 맞도록 리팩토링 해주는 개발을 테스트 주도 개발이라고 한다.
테스트 코드를 작성하기 위한 폴더와 파일을 생성해준다.
다음과 같이 코드를 짜주면 되는데, 먼저 다음과 같은 코드를 짜주기 전에 pip install BeautifulSoup4 명령을 입력해 라이브러리를 추가해준다. 맨 윗줄에 보이는 import는 테스트 프레임워크를 import 해준 것이다. 나머지는 테스트에 있어 우리가 실제 코드를 작업하는 것과 환경이 같아야 하기 때문에 import 해준 것이다.
다음으로 테스트를 위한 클래스를 하나 만들어준다.
unittest에서 TestCase를 상속받아 작성한 것이다. 그 다음으로 테스트를 하기 위해서 setUp()과 tearDown() 이 두 가지가 존재하는데 이는 각각 테스트 준비 와 테스트가 끝났을때 호출되는 것이다.
다음과 같이 클래스 안에 코드를 짜주면 되는데 우선 위에서 설명한 두개의 함수가 보이고, 그안에 내용을 살펴보면 setUP에선 테스트를 위한 db를 설정해줬다고 생각하면 된다. 안에 있는 코드들을 보면 지난 실습에서 사용된 내용들이 많기에 어느정도 무슨 내용인지, 어떤 느낌인지 유추할 수 있을 것이다. 다음으로 tearDown에선 테스트가 끝나고, 테스트에서 사용한 데이터베이스 내용을 삭제해주는 것을 확인할 수 있다.
첫번째로 우리는 구현 해놓은 User 모델이 잘 작동하는 지다. User 모델에 맞춰 내용을 입력해주고 잘 저장되었는지 테스트 해주는 것이다. 위의 코드들은 대부분 이해가 갈 것이고, 마지막 줄(self.assertEqual(User.query.count(), 2)이 생소하게 느껴질 수도 있다. 이 코드의 의미는 User에 맞춰 구현한 위 두개의 데이터가 잘 있는지 확인하기 위함으로 데이터베이스에 존재하는 유저가 2명인지를 물어주는 코드이다.
이건 회원가입 요청을 보내보는 것이다. 정확히는 위의 방법은 데이터베이스에 바로 보내는 거면, 이것은 웹처럼 POST형식으로 보내는 것이라고 이해하면 좋을 거 같다. 위의 코드가 정상 작동한다면 터미널에서
가 마지막에 나타날 것이다. 만약 그렇지 않고 에러가 난다면 자신이 짠 코드를 다시 한번 확인해보자. 필자 같은 경우는 db가 어딘가에서 사용중이다 라고 나타나기에
모든 함수에 다음과 같은 문장을 추가해 확실하게 닫아주었다. 또 위의 os.remove에서 지워지지 않았기에 경로 수정, 명령어 변경으로 다음과 같이 수정해 원하는 결과를 얻었다.
다음과 같이 코드를 짜줘서 이번엔 네비게이션바가 잘 작동하는지 확인 해줄 것이다. 위에서 부터 설명을 차근차근 해보자면 nav태그를 찾아, navbar안에 로그인/로그아웃, 회원가입이 각각 있는지 없는지 확인해주고, 로그인이 돼야 바에 로그인이 로그아웃으로 바뀌는지 알 수 있기에 회원가입과 로그인을 진행해주고, 바에 가서 로그인 됐을때의 상태가 제대로 나오는지 확인을 해주는 것이다.
이제 본격적으로 관리자 페이지를 만들 것이다. pip install flask-admin 명령을 터미널에 입력해 설치해준다.
설치가 완료되면 다음과 같이 Admin을 import 해준다
그리고 다음과 같이 코드를 추가해주면 된다. 마지막부분에서 어드민 페이지를 만들어 준것이다.
하지만 아직 내용을 입력하지 않았기 때문에 /admin에 들어가보면 텅 빈 화면이 맞이해줄 것이다.
이제부터 기능들을 구현해보겠다. 먼저 우리가 만든 User모델을 어드민에 추가할 것인데, 이를 admin.add_view(ModelView(User, db.session)) 으로 바로 __init__.py에 추가하게 되면, 순환 참조 에러가 발생한다.
이를 해결하기 위해 우리는 models.py에서 우리가 만든 User 클래스를 반환해주는 함수를 정의할 것이다.
다음과 같이 해주면 된다. 실로 간단하다!
그리고 다음과 같이 __init__.py에 import를 추가로 해주고,
코드를 다음과 같이 수정해주면 된다. 추가된 구절을 보면 어드민 페이지에 user모델을 리턴해주는 함수를 불러와 user모델을 어드민 페이지에 등록한 것이다.
그다음 auth.py에 들어와 다음과 같이 import 해준다. 그리고 auth.py에서 User 부분을 get_user_model()로 모두 치환해준다. tests.py도 동일한 작업을 해주고 테스트를 실행 해보면 OK가 뜰 것이다. 그리고 실행시켜 admin 주소로 들어가보면
다음과 같이 admin 페이지에서 User을 관리할 수 있다.
다음으로 카테고리, 게시물 구현을 해보겠다. 먼저 구현하려면 무엇을 해야 하는가! 그렇다 바로 모델을 작성해야 된다.
그 전에 카테고리를 구현하려면 어떻게 해야 될까 생각해보자. 게시물과 카테고리의 관계, 한 카테고리 안에 여러 게시물이 존재할 수 있다. 하지만 한 게시물에 여러 카테고리가 들어갈 순 없는 노릇이다. 그렇기에 카테고리와 게시물은 일 대 다 관계이고, 유저와 게시글 역시 한 유저가 쓴 여러 게시글은 존재할 수 있지만 한 게시글을 여러 유저가 쓸 수 없다. 그렇기에 이 관계 또한 일 대 다 관계이다. 지금부터 이 일 대 다 관계를 ORM을 활용해 다뤄볼 것이다.
다음과 같이 새로운 모델을 추가해줬는데 대부분 알 것 같아 모를 수 있는 부분만 찝고 넘어가자면 db.ForeignKey는 외래키로 다른 테이블의 id를 참조하는 것이다. user는 역참조다. 여기서 역참조란 예시를 들어 설명하면 게시글과 유저 모델이 존재하면 게시글을 작성한 유저가 있을 것이다. 이 유저를 찾기 위해 사용하는 것이라고 생각하면 될 거 같다. author_id에 어떤 유저가 글을 작성했는지, category_id에 어떤 카테고리에 속한지를 담고, ondelete='CASCADE'는 저자 모델이 삭제 될 경우 그 저자의 글이 모두 삭제되도록 하는 것이고, nullable=False는 게시글에 무조건 저자를 담기게 하는 것이다.
이렇게 게시글 모델을 만들었으면 다음으론 카테고리 모델을 만들 것이다.
다음과 같이 만들어주면 된다. 다음으로 서버에 데이터를 보내야 하기 때문에 POST 메소드를 사용하는 HTML Form 을 작성할 것이다. 아래의 코드를 복사해 html 파일을 생성해 붙여넣기 해주면 된다. 코드는 한번 읽어보는 것도 괜찮을 거 같다.
{% extends 'base.html' %}
{% block title %}Create a Post{% endblock %}
{% block header %}
<header class="masthead"
style="background-image: url('{{ url_for('static', filename='assets/img/create-bg.jpeg') }}'); height: 130px;">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="site-heading">
<h1>Create a Post</h1>
<h2>Post whatever you want!</h2>
</div>
</div>
</div>
</div>
</header>
{% endblock %}
{% block content %}
<!-- Main Content-->
<main class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-12">
<div class="my-5">
<form id="createForm" method="POST">
{# {{ form.csrf_token }}#}
<div style="margin: 20px;">
<input class="form-control" id="title" type="text" placeholder="Post Title"
name="title" style="font-size: 40px;"/>
</div>
<div style="margin: 20px;">
<textarea class="form-control" id="content" type="text" placeholder="Content"
name="content" style="height: 500px;"></textarea>
</div>
<div style="margin: 20px;" id="category">
<select class="form-control" name="category" required>
<option value="" disabled selected>select a category</option>
{# <option value="python">python</option>#}
{# <option value="java">java</option>#}
{# <option value="rust">rust</option>#}
{# <option value="typescript">typescript</option>#}
</select>
</div>
<br/>
<div style="text-align: center">
<button class="btn btn-primary text-uppercase" id="submitButton" type="submit">
Create
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
{% endblock %}
그리고 주소를 만들어줘서 html과 연결시켜서 확인 해주면
다음과 같은 화면을 얻을 수 있다. 화면을 볼 수 있도록 코드를 짜는 건 이쯤되면 다 할 수 있다고 생각하기 때문에 추가로 설명을 하지는 않겠다. 그럼 이제 카테고리와 게시물을 테스트 코드를 짜서 정상작동하는 지 확인해보겠다.
아까 했던 것처럼 db 설정을 먼저 해주고 본격적인 테스트를 시작하겠다.
테스트를 위해 다음과 같이 코드를 짜주면 되는데 양이 많아서 놀랄 수도 있지만 위에서부터 천천히 살펴보면 많이 어렵지 않을 것이다.(아마도?) 흐름을 살펴보면 카테고리를 추가해서 추가가 잘 됐는지 확인하고, 카테고리를 두번 더 추가해주고 잘 되는지 확인 해준다. 그 이후 회원가입과 로그인을 진행하고, 게시글을 써보고 카테고리도 제대로 되는지 확인해준 것이다. 작성하다보면 의문이 드는 부분이 있을 것이다. 아직 category-list 라는 주소를 처리해준 적이 없기 때문이다. 그렇기 때문에 해당 코드를 돌리게 되면 처음으로 아래와 같은 에러가 맞이해줄 것이다.
에러를 해결해줄 생각에 벌써부터 침이 고이는데, 먼저 views 파일에 가 다음과 같이 코드를 수정해준다.
내용을 살펴보면 모든 카테고리를 윗줄에서 가져와 담아준 것이다. 이제 이것을 아래에서 컨텍스트로 넘겨주면 템플릿에서 {{ categories }} 와 같은 코드를 사용할 수 있다.
{% extends 'base.html' %}
{% block title %}All Categories{% endblock %}
{% block header %}
<!-- Page Header-->
<header class="masthead"
style="background-image: url({{ url_for('static', filename='assets/img/categories-bg.jpeg') }})">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="site-heading">
<h1>All Categories</h1>
<span class="subheading">Blog categories...</span>
</div>
</div>
</div>
</div>
</header>
{% endblock %}
{% block content %}
<!-- Main Content-->
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-10">
{% for category in categories %}
<!-- Category item-->
<div class="post-preview">
<a href="post_detail.html">
<h2 class="post-title">{{ category.name }}</h2>
</a>
</div>
<!-- Divider-->
<hr class="my-4"/>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
categories_list.html 코드이다. 한번 살펴보는 게 좋을 거 같다.
이럼 에러 해결이 끝난 건가? 생각이 들것이다. 맞다. 저 에러는 html파일을 작성하고, views파일을 조금 수정해주면 해결이 된다.
하지만 새로운 에러가 당신을 기다리고 있다. 의도된 응답상태 코드는 302였지만 200으로 접근되어 발생한 것이다.
다음과 같은 데코레이터를 추가하고 돌려보면 위의 문제가 해결될 것이다. 이러면 모든 에러가 해결 됐을까?
콧방구라도 끼듯 이놈은 당신에게 새로운 에러를 선사해줄 것이다. 파이썬이라는 카테고리를 못찾겠다는 건데 다음과 같이 코드를 수정해주면 된다.
위와 내용은 똑같기에 코드에 대한 설명은 스킵하겠다.
위의 코드처럼 수정을 해주면 되는데 이도 직관적이라 이해하기 쉬워 설명은 스킵하겠다. 이러면 위의 에러가 해결 되지만
마지막으로 또 이런 에러가 나를 반겨준다.. 행복하다. 게시글이 없어서 발생하는 오류기에 게시물을 작성해보자.
게시글을 작성해서 POST형식으로 보내 서버에서 받아줄 거기 때문에 그거에 맞는 폼을 작성한다.
이제는 정말 익숙한 코드라 생각이 들기에 그냥 post 요청이 들어오면 서버에 form에 있는 데이터를 전송해주는 코드를 추가해주고, 주소를 받는 부분에서 methods를 추가해 POST요청을 받을 수 있게 해줬다 생각하면 되겠다.
다음과 같이 작동할 수 있도록 문장을 추가해주면 된다.
다음과 같이 tests.py에 추가해준다. 그리고 테스트 해보면 될 것 같다.
이제 포스트 화면을 구현해보자. id로 포스트들을 구별할 것이기 때문에, 다음과 같이 코드를 짜준다.
{% extends 'base.html' %} {% block title %}{{ post.title }}{% endblock %} {%
block header %}
<!-- Page Header-->
<header
class="masthead"
style="background-image: url('assets/img/post-bg.jpg')"
>
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="post-heading">
<div id="title-wrapper"><h1>{{ post.title }}</h1></div>
<span class="meta">
<div id="author-wrapper">
<p>Posted by : <a href="#!">{{ post.author_id }}</p>
</div>
<p>created_at : {{ post.created_at }}</p>
</span>
</div>
</div>
</div>
</div>
</header>
{% endblock %} {% block content %}
<!-- Post Content-->
<article class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">{{ post.contene }}</div>
</div>
</div>
</article>
{% endblock %}
post_detail.html 파일을 다음과 같이 수정해준다.
그리고 어드민 페이지에서 이를 확인할 수 있도록 추가해준다.
그 다음 admin페이지에 들어가 카테고리 하나를 생성해준다.
위의 주소로 들어가면 우리가 어드민 페이지에서 만들어준 포스트를 확인할 수 있다.
관리자 권한을 가진 계정으로 로그인한 상태로 create post 사이트에 접속하게 되면, 다음과 같이 글을 작성할 수 있는 페이지로 이동하게 되고, 만약 우리가 관리자 권한이 없는 계정으로 이 주소로 접속하려고 하면 접속이 되지 않는 것을 확인할 수 있을 것이다. 또한, 로그아웃 상태로 접근하면 로그인 페이지로 이동될 것이다.
그리고 저 페이지에서 글을 작성한다면 다음과 같이 post/2 주소로 접속했을 때 작성한 글이 보일 것이다.
또한 카테고리도 어드민 페이지에서 추가해줬기 때문에 categories-list 주소로 접속하면 추가된 카테고리를 확인할 수 있을 것이다.
categories_list.html 로 가 해당부분을 다음과 같이 수정해주고, views.py 파일을 다음과 같이 수정해주면 된다.
post-list에서 id를 받아 주소로 이동되는 건데, 여기까지만 하면 클릭한 카테고리에 해당하는 글들이 보이진 않을 것이다. 그렇기 때문에 아래와 같이 추가적으로 코드작업을 해준다.
post_list.html 파일이 없기 때문에 다음과 같은 코드를 post_list.html 이란 파일을 만들어서 코드 작업을 해주면 된다.
{% extends 'base.html' %}
{% block title %}Post List{% endblock %}
{% block header %}
<!-- Page Header-->
<header class="masthead" style="background-image: url('{{ url_for('static', filename='assets/img/home-bg.jpg') }}')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="site-heading">
<h1>(카테고리명) Posts.</h1>
<span class="subheading">() 개의 포스트가 있습니다.}</span>
</div>
</div>
</div>
</div>
</header>
{% endblock %}
{% block content %}
<!-- Main Content-->
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-10">
<!-- Post preview-->
<div class="post-preview">
<a href="post_detail.html">
<h6 class="post-title">Man must explore, and this is exploration at its greatest</h6>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 24, 2022
</p>
</div>
<!-- Divider-->
<hr class="my-4"/>
<!-- Post preview-->
<div class="post-preview">
<a href="post_detail.html"><h6 class="post-title">I believe every human has a finite number of
heartbeats. I don't intend to waste any of mine.</h6></a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 18, 2022
</p>
</div>
<!-- Divider-->
<hr class="my-4"/>
<!-- Post preview-->
<div class="post-preview">
<a href="post_detail.html">
<h6 class="post-title">Science has not yet mastered prophecy</h6>
<h3 class="post-subtitle">We predict too much for the next year and yet far too little for the
next ten.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on August 24, 2022
</p>
</div>
<!-- Divider-->
<hr class="my-4"/>
<!-- Post preview-->
<div class="post-preview">
<a href="post_detail.html">
<h6 class="post-title">Failure is not an option</h6>
<h3 class="post-subtitle">Many say exploration is part of our destiny, but it’s actually our
duty to future generations.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on July 8, 2022
</p>
</div>
<!-- Divider-->
<hr class="my-4"/>
<!-- Pager-->
<div class="d-flex justify-content-end mb-4"><a class="btn btn-primary text-uppercase" href="#!">Older
Posts →</a></div>
</div>
</div>
</div>
{% endblock %}
그리고 다음과 같이 html 파일을 수정해주면 아래와 같은 화면을 얻을 수 있을 것이다.
제목? 부분이라고 할 수 있는 부분이 이제 제대로 나오니 2개의 포스트가 있다면 아래 2개의 포스트도 보여야 되지 않겠는가? 그렇기에 html 파일을 조금만 더 수정해줄 것이다.
파일을 다음과 같이 수정해주면 아래와 같은 화면을 얻을 수 있을 것이다!
그리고 글쓰기 권한은 확실하게 스테프여야 되기 때문에 다음과 같이 코드를 수정해줍니다.
다음으로 게시물 수정과 삭제를 처리해보겠다.
먼저 수정을 하기 위한 버튼을 만들어 주기 위해 다음과 같이 코드를 수정한다.
그리고 라우트 함수의 내용을 다음과 같이 수정해준다.
그리고 create-post.html 에서 내용을 복사해와 다음과 같이 코드를 수정해 post_edit_form.html를 작성해주면 된다.
그리고 마저 views파일에서 라우트 함수 코드를 짜주면 된다.
그리고 글쓴이만 수정할 수 있게 다음과 같이 코드를 수정해준다. 그리고 categories-list 주소로 이동 -> 카테고리 클릭 후 게시물 이동 -> edit 버튼을 눌러 게시글을 수정해주면 다음과 같이 제목과 내용을 바꿀 수 있다.
다음으로 어드민 페이지 접근 권한을 설정할 것이다. 어드민 페이지에 아무나 접근이 가능하다면 곤란하지 않겠는가
다음과 같은 코드 작업을 통해서 is_staff가 True일때 즉 관리자일 때만 어드민 페이지에 접근할 수 있도록 코드작업을 해준 것이고, 만약 권한이 없는 사람 혹은 로그인 되지 않은 사용자가 접근하면 403 에러가 당신을 반겨줄 수 있게 코드를 작업해준 것이다.
이렇게 관리자만 접근하게 해줬다면 우리는 다음 문제를 생각해봐야 된다. 폼에서 회원가입을 진행하게 되면 우리가 짜준 코드에 의해 비밀번호가 해싱되지만 만약 어드민 페이지에서 계정을 생성해준다면 비밀번호 해싱이 이뤄지지 않을 것이다. 우리가 로그인을 진행할 때 해싱된 비밀번호를 풀게 되어있지만 어드민 페이지에서 만든 계정은 해싱이 되어 있지 않기때문에 이를 로그인하려고 하면 되지 않는 문제점을 겪을 것이다. 이를 해결해보자.
import email, click
from sqlalchemy.exc import IntegrityError
from flask import Flask, abort
from .views import views
from .auth import auth
from .models import DB_NAME, db, get_user_model, get_post_model, get_category_model
from flask_sqlalchemy import Model, SQLAlchemy
from os import path
from flask_login import LoginManager, current_user
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from pprint import pprint
from werkzeug.security import generate_password_hash, check_password_hash
from wtforms import PasswordField, StringField
from wtforms.validators import InputRequired
from flask.cli import with_appcontext
def create_app():
app=Flask(__name__)
app.config["SECRET_KEY"]="IFP"
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['FLASK_ADMIN_SWATCH'] = 'Darkly'
admin = Admin(app, name='blog',
template_mode='bootstrap3')
class MyUserView(ModelView):
def is_accessible(self):
if current_user.is_authenticated and current_user.is_staff == True:
return True
else:
return abort(403)
class CustomPasswordField(StringField):
def populate_obj(self, obj, name):
setattr(obj, name, generate_password_hash(self.data))
form_extra_fields = {'password': CustomPasswordField('Password', validators=[InputRequired()])}
form_excluded_columns = {'post', 'created_at'}
class MyPostView(ModelView):
def is_accessible(self):
if current_user.is_authenticated and current_user.is_staff == True:
return True
else:
return abort(403)
form_excluded_columns = {'created_at', 'comments'}
class MyCategoryView(ModelView):
def is_accessible(self):
if current_user.is_authenticated and current_user.is_staff == True:
return True
else:
return abort(403)
form_excluded_columns = {'category'}
class MyCommentView(ModelView):
def is_accessible(self):
if current_user.is_authenticated and current_user.is_staff == True:
return True
else:
return abort(403)
admin.add_view(MyUserView(get_user_model(), db.session))
admin.add_view(MyPostView(get_post_model(), db.session))
admin.add_view(MyCategoryView(get_category_model(), db.session))
# admin.add_view(MyCommentView(get_comment_model(), db.session))
db.init_app(app)
app.register_blueprint(views, url_prefix="/")
app.register_blueprint(auth, url_prefix="/auth")
create_database(app)
login_manager = LoginManager()
login_manager.login_view = "auth.login"
login_manager.init_app(app)
@login_manager.user_loader
def load_user_by_id(id):
return get_user_model().query.get(int(id))
@click.command(name="create_superuser")
@with_appcontext
def create_superuser():
username = input("Enter username : ")
email = input("Enter email : ")
password = input("Enter password : ")
is_staff = True
try:
superuser = get_user_model()(
username = username,
email = email,
password =generate_password_hash(password),
is_staff = is_staff
)
db.session.add(superuser)
db.session.commit()
except IntegrityError:
print('\033[31m' + "Error : username or email already exists.")
print(f"User created! : {email}")
app.cli.add_command(create_superuser)
return app
def create_database(app):
if not path.exists("blog/"+DB_NAME):
db.create_all(app=app)
__init__.py를 다음과 수정함으로서 비밀번호 해싱 문제를 해결할 수 있다.
'파이썬 > 파이썬 플라스크' 카테고리의 다른 글
ep 8. Flask-RESTful 로 Car CRUD api 구축하기 (0) | 2022.08.29 |
---|---|
ep 7. 간단한 HTTP API 구축해보기 (0) | 2022.08.24 |
ep 4. 로그인 처리 (0) | 2022.07.26 |
ep3. 블로그 웹 애플리케이션 개발(0) - 프로젝트 생성, 패키지 설치, 기본 작업 (0) | 2022.07.17 |
2-5. sqlite3 (0) | 2022.07.10 |