기본적인 화면 및 State 구성을 했으니 실제 로그인/로그아웃을 구현해보겠습니다.
로그인에 사용할 사용자 정보를 관리하기위해서 pynecone이 기본으로 제공하는 SQLite 파일 DB를 사용합니다.
pynecone docs에 보면 pc.session 을 통해서 SQLAlchemy syntax의 query로 Database에 데이터를 등록, 변경, 삭제, 조회 할수 있습니다. (https://pynecone.io/docs/database/queries)
"with pc.session" 으로 데이터처리를 하고 해당 block이 끝나면 자동으로 session도 닫게 됩니다.
이제 구현방법입니다.
- 먼저, State(pc.State) 에서 전 App에서 사용가능한 사용자 정보(이름, 패스워드, 권한)를 포함하는 User Class 생성하고 Base State에 로그인 한 사용자, 권한, 상태를 저장하는 vars를 생성
- demo_auth.py의 login에 사용자이름, 패스워드를 입력받는 로그인 Page 생성
- 사용자 등록하는 signup Page 생성 및 사용자 등록
- DB Browser for SQLite를 이용해서 직접 pynecondb에 붙어서 기본 사용자를확인
프로젝트 명(폴더) : demo_board
# | 파일 경로 | 설명 | 구분 |
1 | pcconfig.py | pc 구동 설정 파일 | |
2 | demo_board > demo_board.py | "/signup" 사용자 등록 Page 추가 "/servers" Page 추가 |
수정 |
3 | demo_board > demo_state.py | 나중 추가 확장성(페이지추가)를 위해 Base State(pc.State) | 수정 |
4 | demo_board > demo_auth.py | "/login" 페이지 변경 및 "/signup" 페이지 추가 | 수정 |
5 | demo_board > demo_servers.py | 로그인후 오픈할 페이지 | 추가 |
6 | demo_board > demo_helpers.py |
https://pynecone.io/docs/database/tables
demo_state.py 수정
import pynecone as pc
#table=True 인수는 Pynecone에게 이 클래스에 대해 데이터베이스에 테이블을 생성하도록 지시
class User(pc.Model, table=True):
"""A table of Users."""
username: str # 사용자명
password: str # 패스워드
userrole: str # 사용자 권한
class State(pc.State):
"""The base state for the app."""
print("──────────────────── [ State(pc.State) ] ────────────────────")
username: str
userrole: str
logged_in: bool = False # 로그인/로그아웃시 상태를 저장
demo_auth.py 수정
Log in / Sign up 을위해서 추가 변경되는 부분이 많습니다.
import pynecone as pc
from .demo_state import State , User # Base State, User Class import
from .demo_helpers import navbar
# 로그인 호면에 대한 스타일 시트 적용
styles = {
"login_page": {
"padding_top": "10em",
"text_align": "top",
"position": "relative",
"background_image": "bg.svg",
"background_size": "100% auto",
"width": "100%",
"height": "100vh",
},
"login_input": {
"shadow": "lg",
"padding": "1em",
"border_radius": "lg",
"background": "white",
},
}
class AuthState(State):
"""The Substate of base state for the login page."""
print("──────────────────── [ AuthState(State) ] ────────────────────")
app_name: str = "Log In"
password: str # 로그인에서 입력받은 Password
new_username: str # 신규 사용자 Name
new_userrole: str # 신규 사용자 Role
new_password: str # 신규 사용자 Password
confirm_password: str # 신규 사용자 Confirm Password
message: str # 사용자 등록시 발생하는 Error 등 메시지 표시
message_color: str = "red" # Message의 기본색은 red, Sign up 성공인경우 navy
def signup(self):
"""Sign up a user."""
print("signup values : [{}], [{}], [{}]".format(self.new_username, self.new_password, self.new_userrole))
with pc.session() as session:
#self.message_color = "red"
# 입력 값 Validation 체크
if self.new_userrole == "" or self.new_userrole == None:
self.message = "Select user role."
return
if self.new_username == "" or self.new_username == None:
self.message = "Input user name."
return
if self.new_password == "" or self.new_password == None:
self.message = "Input passwd"
return
if self.new_password != self.confirm_password:
self.message = "Passwords do not match."
return
if session.exec(User.select.where(User.username == self.new_username)).first():
self.message = "Username already exists."
return
user = User(username=self.new_username, password=self.new_password, userrole=self.new_userrole)
session.add(user)
session.commit()
self.logged_in = False
self.message_color = "navy"
self.message = "User siginup completed, please log in"
return pc.redirect("/login")
def login(self):
"""Log in a user."""
if self.username == "" or self.password=="":
self.message = "Input username and password."
return
with pc.session() as session:
user = session.exec(
User.select.where(User.username == self.username)
).first()
if user and user.password == self.password:
self.logged_in = True
self.userrole = user.userrole
return pc.redirect("/servers")
else:
self.message = "Invalid username or password."
return
# Signup 화면에서 선택한 Role 정보를 AuthState에 저장
def role_change(self,value):
print("role_change value : {}".format(value))
self.new_userrole = value
def clear_message(self):
self.message = ""
self.message_color = "red"
def signup():
return pc.box(
pc.vstack(
navbar(State, AuthState.app_name),
pc.center(
pc.vstack(
pc.heading("Sign Up", font_size="1.5em"),
pc.select(
["User", "Admin"],
placeholder="Select a Role.",
on_change=lambda value: AuthState.role_change(value),
border_style="solid",
border_width="1px",
border_color="#43464B",
),
pc.input(
on_blur=AuthState.set_new_username, placeholder="Username", width="100%",
on_click=AuthState.clear_message,
),
pc.input(
on_blur=AuthState.set_new_password,
placeholder="Password",
type_="password",
width="100%",
on_click=AuthState.clear_message,
),
pc.input(
on_blur=AuthState.set_confirm_password,
placeholder="Confirm Password",
type_="password",
width="100%",
on_click=AuthState.clear_message,
),
pc.button("Sign Up", on_click=AuthState.signup, width="100%"),
pc.text(AuthState.message, color=AuthState.message_color),
),
style=styles["login_input"],
),
),
style=styles["login_page"],
)
def login():
return pc.box(
pc.vstack(
navbar(State, AuthState.app_name),
pc.center(
pc.vstack(
pc.heading("Log In", font_size="1.5em"),
pc.input(
on_blur=State.set_username, placeholder="Username", width="100%",
on_click=AuthState.clear_message,
),
pc.input(
on_change=AuthState.set_password,
placeholder="Password",
type_="password",
width="100%",
on_click=AuthState.clear_message,
),
# Username, Password를 입력받으면 AuthState의 login 함수 호출하는 event 작동
pc.button("Login", on_click=AuthState.login, width="100%"),
# 사용자 등록하기위한 Signup
pc.link(
pc.button("Sign Up", width="100%"), href="/signup", width="100%"
),
pc.text(AuthState.message, color=AuthState.message_color),
),
style=styles["login_input"], # 로그인 Page에 맞는 추가 스타일
),
),
style=styles["login_page"],
)
demo_servers.py
"/servers" 페이지는 pc.cond 컴포넌트로 Base State의 logged_in var가 True 일때만 열리게 구성합니다.
import pynecone as pc
from .demo_state import State # Substate of Base State
from .demo_helpers import navbar
class ServerState(State):
"""The Substate of base state for the server page."""
print("──────────────────── [ ServerState(State) ] ────────────────────")
app_name: str = "Servers" #1 Navigation Bar에 표시될 Page 명
def servers():
return pc.box(
pc.vstack(
navbar(State, ServerState.app_name), #2 맨위 상단에 navbar를 추가
pc.cond(
State.logged_in,
pc.box(
pc.vstack(
pc.heading("서버 모니터링 페이지", font_size="2em"),
),
width="100%",
border_width="0px",
),
pc.link(
pc.button("먼저 로그인 하세요"),
href="/login",
color="rgb(107,99,246)",
button=True,
)
),
padding_top="5em",
width="100%",
),
)
로그인 없이 접근할경우
demo_board.py
"""Welcome to Pynecone! This file outlines the steps to create a basic app."""
from pcconfig import config
import pynecone as pc
from .demo_state import State
from .demo_auth import login, signup # Page 추가
from .demo_servers import servers
def index():
return pc.center(
pc.vstack(
pc.heading("Pynecone Demo Board!", font_size="2em"),
pc.link(
"로그인",
href="/login",
border="0.1em solid",
padding="0.5em",
border_radius="0.5em",
_hover={
"color": "rgb(107,99,246)",
},
),
spacing="1.5em",
font_size="2em",
),
padding_top="10%",
)
# Add state and page to the app.
app = pc.App(state=State)
app.add_page(index)
app.add_page(login)
app.add_page(signup) # Page 추가
app.add_page(servers) # Page 추가
app.compile()
서버 재시작 (ctrl + c & pc run)
서버 재구동후 DB Browser for SQLite 로 pynecone.db 파일을 열어보면 user 테이블이 생성된것을 확인
DB Browser for SQLite 를 닫고 사용자를 추가 (Sign up)
pynecone.db에 저장된 사용자 확인
(문서 만들기 시작할때는 낮이었는데 이제 밤이라 야간모드로 전환되었네요)
로그인
로그인 성공 후 /servers Page로 이동
'python > pynecone' 카테고리의 다른 글
6.pynecone Demo List[Dict] typing table, DataTable 추가 (0) | 2023.02.18 |
---|---|
5.pynecone Demo Navigation에 Menu 추가 (0) | 2023.02.18 |
2.pynecone Demo Navigation Bar 구현 (0) | 2023.02.18 |
1.pynecone Demo 프로젝트 생성 (0) | 2023.02.18 |
0.pynecone docker 컨테이너로 실행하기 (0) | 2023.02.17 |