728x90

주소가 맞는지 검증하여 결과를 엑셀로 반환 저장하는 코드가 필요해서 사용한 코드이다.

 

# pip install selenium  # Selenium 설치
# pip install openpyxl  # Excel 다루기
# pip install webdriver-manager  # Webdriver Manager for Python is installed
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
 
import time
import pandas as pd
 
# 크롬 드라이버 생성
options = Options()
#options.add_experimental_option("detach", True) # 브라우저 창 떳다기 사라지기(False), 계속 유지(True)
options.add_argument("headless"# 창 숨기는 옵션
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
# driver.maximize_window() # 브라우저 창 최대로 하는 옵션인데 필요없을 거 같다.
 
aaa = []
bbb = []
 
= pd.read_excel(r'Juso_ErrData.xlsx')
= pd.DataFrame(a)
= b['검증주소']
 
= 0
= c.count()
 
# 사이트 접속하기
url = 'https://www.juso.go.kr/support/AddressMainSearch.do?searchKeyword='
 
 
while n < g:
    keyword = str(c[n])
 
    print(keyword)
 
    driver.get(url + keyword)  # url 페이지로 이동
    time.sleep(2)  # 로딩 대기
 
    try:
        roadAddress = driver.find_element(By.XPATH, value='//*[@id="list1"]/div[1]/span[2]').text
        jiAddress = driver.find_element(By.XPATH, value='//*[@id="list1"]/div[2]/span[2]').text
    except:
        pass
        roadAddress = ''
        jiAddress = ''
 
    print(roadAddress)
    aaa.append(roadAddress)
    bbb.append(jiAddress)
    n += 1
b['도로명주소'= aaa
b['지번주소'= bbb
b.to_excel('보정주소.xlsx', index=False)
 

 

 

위 코드 파일과 샘플 엑셀 파일

RoadExcel.py
0.00MB

 

Juso_ErrData.xlsx
0.01MB

 

블로그 이미지

Link2Me

,
728x90

윈도우즈 환경에서 잘 동작하던 코드가 리눅스(CentOS 7) 환경에서 테스트하니까 동작이 안된다.

아래와 같이 설정하면 제대로 동작되는 걸 확인할 수 있다.

 

1. Google Chrome 설치

yum -y install https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm

 

2. Chrome Driver 설치

먼저 google-chrome --version 을 실행하여 현재 버전을 확인한다.

 

wget https://storage.googleapis.com/chrome-for-testing-public/123.0.6312.58/linux64/chromedriver-linux64.zip
로 파일을 다운로드 한다.

 

압축을 풀고 chromedriver 파일을 아래와 같이 옮긴다. (이게 중요하더라)

mv chromedriver /usr/bin/

 

3. 이제 코드 상에서 동작되도록 구현된 코드를 살펴보자.

chromedriver 를 /usr/bin 으로 옮겨서

driver = webdriver.Chrome(options=options) 만으로 코드가 잘 동작된다.

구글링해보면 아래와 같은 설정으로 동작이 되는 것처럼 설명되어 있지만...

driver = webdriver.Chrome(
    executable_path='크롬드라이버 설치경로', options=options
)

로 테스트 한 것은 동작이 안되었다.

 

pip install selenium 을 하면 4.18.1 버전이 설치된다. (pip list 로 확인)

# pip install selenium  
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
 
import time
 
def jusoGet(keyword):
    # 크롬 드라이버 생성
    options = Options()
    options.add_argument("headless"# 창 숨기는 옵션
    options.add_argument("--no-sandbox")
    driver = webdriver.Chrome(options=options)
    #driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
 
    # 사이트 접속하기
    url = 'https://www.juso.go.kr/support/AddressMainSearch.do?searchKeyword='
    driver.get(url+keyword) # url 페이지로 이동
    time.sleep(2# 로딩 대기
 
    try:
        h = driver.find_element(By.XPATH, value='//*[@id="list1"]/div[2]/span[2]').text
        print(h)
    except:
        pass
 
 
if __name__ == "__main__":
    keyword = '서초구청'
    jusoGet(keyword)

 

 

Python3.9 버전이 설치된 환경 변수 설정 방법

1. Python 환경변수 3.9 설정 (alias)
vi /etc/profile
 
alias python3='/usr/local/bin/python3.9'
alias python='/usr/local/bin/python3.9'
alias pip='/usr/local/bin/pip3.9'
 
source /etc/profile
 
2. 설치 확인
python
python3 -V

 

 

Python 3.9 버전과 3.11 버전 모두 정상 동작함을 확인했다.

 

 

블로그 이미지

Link2Me

,
728x90

오랫만에 크롤링을 해보려고 하니까 selenium 드라이브 설치없이 auto 로 설정하는 옵션이 전혀 동작하지 않는다.

버전업이 중단되어서 동작이 안되는가 보다.

방식이 새롭게 변경되었다는 걸 검색하고 테스트 해본 결과 확인했다.

 

아래 코드를 CentOS 7 에서 실행해보니 안된다. Windows10 환경에서는 잘 된다.

CentOS 7 환경에서 성공한 사항은 다음 게시글에 기록해둔다.

# pip install -U selenium  # Selenium is upgraded to v4.0.0
# pip install webdriver-manager  # Webdriver Manager for Python is installed
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
 
import time
 
def jusoGet(keyword):
    # 크롬 드라이버 생성
    options = Options()
    #options.add_experimental_option("detach", False) # 브라우저 창 떳다기 사라지기(False), 계속 유지(True)
    options.add_argument("headless"# 창 숨기는 옵션
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
    # driver.maximize_window() # 브라우저 창 최대로 하는 옵션인데 필요없을 거 같다.
 
    # 페이지 로딩이 완료될 때 까지 기다리는 코드
    driver.implicitly_wait(1)
 
    # 사이트 접속하기
    url = 'https://www.juso.go.kr/support/AddressMainSearch.do?searchKeyword='
    driver.get(url+keyword) # url 페이지로 이동
    time.sleep(2# 로딩 대기
 
    try:
        h = driver.find_element(By.XPATH, value='//*[@id="list1"]/div[2]/span[2]').text
        print(h)
    except:
        pass
 
 
if __name__ == "__main__":
    keyword = '서초구청'
    jusoGet(keyword)

 

 

 

크롬 브라저에 맞는 driver 설치방법 → 불필요

드라이버 설치하고 해보면 아래와 같은 경고 문구가 나온다.

DeprecationWarning: executable_path has been deprecated, please pass in a Service object

 

아래 내용은 불필요한 사항이지만 나중에 보면서 이런 적도 있었구나 하는 셈치고 적어둔다.

chrome://settings/help 를 크롬 브라우저에서 실행하여 현재 버전을 찾아야 한다.

 

 

https://chromedriver.chromium.org/downloads/version-selection

 

ChromeDriver - WebDriver for Chrome - Version Selection

Version selection is the process of matching a Chrome binary of a given version to a compatible ChromeDriver binary. For versions 115 and newer Starting with M115 the ChromeDriver release process is integrated with that of Chrome. The latest Chrome + Chrom

chromedriver.chromium.org

사이트에 접속하면 최신버전과 맞지 않는다.

 

더 최신버전을 위에서 찾아들어가야 한다.

https://googlechromelabs.github.io/chrome-for-testing/

 

Chrome for Testing availability

chrome-headless-shellmac-arm64https://storage.googleapis.com/chrome-for-testing-public/123.0.6312.58/mac-arm64/chrome-headless-shell-mac-arm64.zip200

googlechromelabs.github.io

 

 

블로그 이미지

Link2Me

,
728x90

네이버 증권에서 일별 시세 정보를 가져오는 코드이다.

아래 코드로는 100% 만족된 결과를 도출하지 못한다.

IF 조건문을 사용해서 원하는 결과를 도출하는 건 개발자의 몫이다.

또한 DB에 저장되는 순서를 날짜가 적은 데이터를 먼저 저장하기 위한 방법으로 수정하는 것이 좋다.

 
import requests
from fake_useragent import UserAgent
# BeautifulSoup은 HTML 과 XML 파일로부터 데이터를 수집하는 라이브러리
from bs4 import BeautifulSoup
# BeautifulSoup 에서는 Xpath 사용 불가능
import pymysql
import re
 
def financialSiseDayData(code,page):
    
    ua = UserAgent()
    headers = {'User-agent': ua.ie}
    url = f'https://finance.naver.com/item/sise_day.naver?code={code}&page={page}'
    # print(url)
 
    res = requests.get(url, headers=headers)
    #
    if res.status_code == 200:
        html = res.text
 
        # HTML 페이지 파싱 BeautifulSoup(HTML데이터, 파싱방법)
        soup = BeautifulSoup(html, 'html.parser')
 
        # find() : 가장 먼저 검색되는 태그 반환
        body = soup.select_one("table")
        # print(body)
 
        items = body.find_all('tr', {'onmouseover':'mouseOver(this)'})
        # print(items)
 
        conn = pymysql.connect(host="localhost", user="", password="", db="studydb", charset="utf8")
        curs = conn.cursor()
 
        for item in items:
            information = item.text
            info = information.split('\n')
            # print(info)
            # print(info[2], "type: ", type(info[2]))
            # print(code, info[1],info[2],info[8],info[9],info[10],info[11],info[5].strip())
            closeM = re.sub(r'[^0-9]''', info[2])
            openM = re.sub(r'[^0-9]''', info[8])
            highM = re.sub(r'[^0-9]''', info[9])
            lowM = re.sub(r'[^0-9]''', info[10])
            volume = re.sub(r'[^0-9]''', info[11])
 
            # 기존 DB에 있는 데이터인 경우에는 중복 저장하지 말고 무시하라.
            sql = "insert ignore into stock_day (code,date,close,open, high, low, volume) values (%s, %s, %s, %s, %s, %s, %s)"
            val = (str(code), info[1], closeM, openM, highM, lowM, volume)
            curs.execute(sql,val)
            conn.commit()
        conn.close()
 
    else:
        print(res.status_code)
 
 
if __name__ == '__main__':
    code = 140670
    for i in range(14): # 1개월 데이터만 추출
        financialSiseDayData(code,str(i))
 
    print('완료되었습니다.')

 

DB에 저장하기 위한 SQL 코드

CREATE TABLE stock_day (
  idx int(11NOT NULL,
  code varchar(8NOT NULL COMMENT '종목코드',
  date char(10NOT NULL COMMENT '날짜',
  close double NOT NULL DEFAULT 0 COMMENT '종가',
  open double NOT NULL DEFAULT 0 COMMENT '시가',
  high double NOT NULL DEFAULT 0 COMMENT '고가',
 low double NOT NULL DEFAULT 0 COMMENT '저가',
  volume double NOT NULL DEFAULT 0 COMMENT '거래량',
  display int(2NOT NULL DEFAULT 1,
  reg_date timestamp NOT NULL DEFAULT current_timestamp()
ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
ALTER TABLE stock_day
  ADD PRIMARY KEY (idx),
  ADD UNIQUE KEY code_date (code,date);
 
ALTER TABLE stock_day
  MODIFY idx int(11NOT NULL AUTO_INCREMENT;
COMMIT;

코드와 날짜를 UNIQUE KEY 로 설정하였다.

 

 

 

블로그 이미지

Link2Me

,
728x90

초보수준으로 약간 배우고 오랫만에 Python 을 사용하니까 생각나는게 하나도 없어서 테스트하면서 적어둔다.

 

가장 먼저 해야 할일은 크롬 브라우저에서 개발자 모드(F12) 상태에서 원하는 정보를 클릭하는 것이다.

 

 

//*[@id="tab_con1"]/div[1]/table/tbody/tr[3]/td/em

XPath 복사 결과는 위와 같다.

 

selector 복사를 하면....

#tab_con1 > div.first > table > tbody > tr:nth-child(3) > td > em

와 같은 결과를 반환해준다.

이 결과를 그대로 사용하면 원하는 결과가 나오지 않기 때문에 태그 구조를 파악하면서 결과를 얻어내야 한다.

 

원하는 정보를 가져오기 위한 준비를 마치고 Code 상에서 확인하는 작업을 한다.

텍스트 형태의 데이터에서 원하는 HTML 태크를 추출하는 쉽게 도와주는 라이브러리가 BeautifulSoup 이다.

 

먼저 table 태그를 출력해보고 원하는 자료가 몇번째 tr 에 있는지 확인하고 원하는 값을 추출한다.

데이터의 형태가 추출하기 쉽게 되어 있지 않는 경우도 있으니 split('\n') 를 사용하여 배열 구조를 파악해서

원하는 결과를 출력해야 하는 시가총액 정보도 있다.

import requests
# BeautifulSoup은 HTML 과 XML 파일로부터 데이터를 수집하는 라이브러리
from bs4 import BeautifulSoup
# BeautifulSoup 에서는 Xpath 사용 불가능
 
def financialData(code):
    url = 'https://finance.naver.com/item/sise.naver?code='+ str(code)
    res = requests.get(url)
 
    if res.status_code == 200:
        html = res.text
 
        # HTML 페이지 파싱 BeautifulSoup(HTML데이터, 파싱방법)
        soup = BeautifulSoup(html, 'html.parser')
 
        # parents : 선택한 요소 위로 올라가면서 탐색하는 도구
        # next_siblings : 동일 레벨에 있는 태그들을 가져온다.
 
        # //*[@id="tab_con1"]/div[1]/table/tbody/tr[3]
 
        # find() : 가장 먼저 검색되는 태그 반환
        body = soup.select_one("#tab_con1 > div.first > table")
        print(body.prettify()) # 보기 좋게 출력
 
        # 시가총액 가져오기
        market_sum_init= soup.select('#tab_con1 > div.first > table > tr')[0].find('td').text.split('\n')
        print(market_sum_init)
        market_sum = market_sum_init[4].strip() + market_sum_init[7].strip()
        print(market_sum)
 
        # 상장 주식수 가져오기
        stock_num = soup.select('#tab_con1 > div.first > table > tr')[2].find('td').text
        print(stock_num)
 
    else:
        print(res.status_code)
 
if __name__ == '__main__':
   financialData(140670)

 

거래량 가져오기

find는 가장 가까운 태그 하나를 찾는다.

find(태그,Class명)

find(태그, id='address')

 

개발자모드에서 select 값을 찾아 보니 아래와 같다.

이걸 활용하여 soup.select('#chart_area > div.rate_info > table > tr')[0] 를 한 다음에 print 출력으로 구조를 파악한다.

td 태그 중에서 3번째 태크가 원하는 값이 있다는 걸 확인할 수 있다.

.select('td')[2].find('span','blind').text 으로 최종 원하는 결과를 얻을 수 있다.

# #chart_area > div.rate_info > table > tbody > tr:nth-child(1) > td:nth-child(3) > em
stock_rateinfo = soup.select('#chart_area > div.rate_info > table > tr')[0].select('td')[2].find('span','blind').text
print(stock_rateinfo)

 

블로그 이미지

Link2Me

,
728x90

장고 배포를 위한 환경 구성을 할 때마다 실패를 해서 삽질을 엄청했다.

https://computingforgeeks.com/deploy-python-3-django-application-on-centos-with-apache-and-mod-wsgi/

위 사이트에 적힌 순서에 따라 해도 안되더니 마지막 2줄이 해결책이더라.

 

Windows 10 Pycharm 툴을 이용해서 Python 3.9.5 or Python 3.10.2 환경에서 개발 테스트 한 것을 CentOS 7 으로 옮겨서 실행하다보니 동일 환경을 구성하기 어렵다.

그래서 pip install -r requirements.txt 를 하면 에러가 발생한다.

이런 점을 감안해서 작성한 스크립트이다.

 

1. 선행 작업사항

##########################################################################
## CentOS 7 Package Update
 
yum -y update
 
##########################################################################
# 방화벽 설정
yum -y install firewalld
 
# 방화벽 데몬 시작
systemctl start firewalld
 
# 서버 부팅 시 firewalld 데몬 자동 시작 설정
systemctl enable firewalld
 
firewall-cmd --permanent --add-service=http 
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-service=mysql
firewall-cmd --permanent --zone=public --add-port=3306/tcp
 
firewall-cmd --permanent --zone=public --add-port=8000/tcp
firewall-cmd --permanent --zone=public --add-port=8080/tcp
firewall-cmd --reload
firewall-cmd --list-all
 
#########################################################################
# Databse 접근을 위한 SELinux 설정 변경
# SELinux 상태 확인
sestatus
 
#SELinux httpd flag 확인 : 네트워크를 통해 Database에 연결할 수 있는 옵션이 꺼져 있음
getsebool -| grep httpd
 
# Database 접근을 위한 SELinx 설정 변경
setsebool -P httpd_can_network_connect_db 1
 
# SELinux 비활성화 하기
vi /etc/sysconfig/selinux
SELINUX=disabled
:wq 로 저장하고 나온다.
 
# 재부팅해야 SELinux 명령어 수정한 사항이 적용된다.
reboot 
 
# 임시 비활성화 방법
setenforce 0
 

 

2. Python 설치 과정

 

#########################################################################
# 파이썬(Python) 설치 과정
#########################################################################
# python 2.7.X 버전이 설치된 것을 확인한다.
rpm -qa | grep mariadb
# 설치된 것이 없다면 아래 한꺼번에 지우기는 실행하지 않아도 된다.
 
## 설치된 것 한꺼번에 지우기
rpm -qa | grep mariadb > list
 
# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
sudo yum -y remove $(awk '{print $1}' <list)
 
# Python 모듈을 빌드하려면 개발 도구가 필요
yum -y update
yum -y groupinstall 'Development Tools'
yum -y install yum-utils
yum -y install zip unzip wget mc git net-tools
 
# 아파치와 mode_wsgi 를 설치하기 위해 아래 명령어를 실행한다.
yum -y install epel-release
yum -y makecache
yum -y install httpd mod_wsgi
yum -y install mariadb-devel 
 
# PHP 관련으로 httpd 를 설치하지 말아야 한다.
# 먼저 설치하면 mariadb-devel 이 PHP와 관련되어 설치되기 때문에 
# pip install mysqlclient 명령어 실행시 에러가 발생하더라.
 
 
# 아래 명령어는 필요시 설치해 주면 될 거 같아서 주석처리했다.
#yum -y install gcc openssl-devel bzip2-devel libffi-devel xz-devel sqlite-devel tk-devel
#yum -y install zlib-devel bzip2-devel gdbm-devel 
 
 
# Enable Software Collections
sudo yum -y install centos-release-scl
sudo yum -y install rh-python36 python36-devel httpd-devel rh-python36-mod_wsgi
scl enable rh-python36 bash
 
#sudo yum -y install rh-python38 python38-devel httpd-devel rh-python38-mod_wsgi
#scl enable rh-python38 bash
 
python --version
python -V
 
# yum 설치 문제가 발생하지 않도록 아래와 같이 수정한다.
vi /usr/bin/yum
# 첫줄에 python 을 python2.7 로 변경하고 저장(:wq)한다.
 
vi /usr/libexec/urlgrabber-ext-down
# 첫줄에 python 을 python2.7 로 변경하고 저장(:wq)한다.
 
# 현재 Alias 확인
ls -/bin/python*
 
pip -V
pip3 -V
 
###### SQLite3 DB 설정 ###################################
# Python 에서 SQLite3 를 찾는데, 기본 설치 버전이 3.7.17 이다.
# 장고 설치시에 더 높은 버전을 요구하므로, 아래와 같이 3.10.2 버전을 설치해준다.
 
# https://kojipkgs.fedoraproject.org//packages/sqlite/ 접속하여 확인한다.
# 원하는 파일을 찾아서 링크를 복사하고 wget 으로 붙여넣기 한다.
 
cd /root
mkdir sqlite3
cd sqlite3
wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.10.2/1.fc22/x86_64/sqlite-3.10.2-1.fc22.x86_64.rpm
wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.10.2/1.fc22/x86_64/sqlite-devel-3.10.2-1.fc22.x86_64.rpm
 
sudo yum -y install sqlite-3.10.2-1.fc22.x86_64.rpm sqlite-devel-3.10.2-1.fc22.x86_64.rpm
 
# 파이썬에서 설치된 SQLite3 버전 확인 방법
python -"import sqlite3; print(sqlite3.sqlite_version)"
 
#########################################################
##### 파이썬 가상환경 설정 ####################################
#########################################################
# Step : Install virtual environment
cd /var/www
python -m pip install virtualenv
 
# 가상환경 이름을 venv 로 설정했는데 다른 명칭으로 변경해도 된다.
mkdir -/var/www/logs
 
# venv 가상환경 생성
cd /var/www
virtualenv venv
 
# venv 가상환경 실행
source /var/www/venv/bin/activate
 
pip install --upgrade pip setuptools
pip install django
pip install djangorestframework
 
# MySQL 이나 MaraiDB 와 연동 목적
pip install mysqlclient
 
# 파이썬 디버깅 도구 설치
pip install pylint
pip install twisted
 
# venv 가상환경 종료
deactivate
 
 

 

3. MaraiDB 10.5 설치

 

 
################################
##### MariaDB 10.5 버전 설치 #####
################################
 
vi /etc/yum.repos.d/MariaDB.repo
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
 
:wq 로 저장하고 나온다.
 
sudo yum makecache fast
yum -y install mariadb-server mariadb-client
 
# mariadb 부팅 시 자동 시작 설정
systemctl enable mariadb
 
# mariadb 시작
systemctl start mariadb
 
# mariadb 상태 확인
service mariadb status
 
# Maria DB 보안 설정하기
mysql_secure_installation
 
# root 비밀번호 설정 등 상세 과정은 생략한다. 거의 Y만 누르면 끝난다.
 
 
# UTF-8 로 통신하기 위한 서버/클라이언트 설정
vi /etc/my.cnf.d/server.cnf
[mysqld]
collation-server = utf8_general_ci
init-connect='SET NAMES utf8'
character-set-server = utf8
 
vi /etc/my.cnf.d/mysql-clients.cnf
[mysql]
default-character-set=utf8
[mysqldump]
default-character-set=utf8
 
# MariaDB 재시작
service mariadb restart
 
# MariaDB 접속하여 적용된 사항 확인
mysql -u root -p
status
show variables like 'c%';
 
#########################################
####### 실제 적용 예제 ######
#########################################
// DB 생성
create database pythondb default character set utf8;
 
use mysql;
create user pythonfox@localhost identified by 'PythonWonder#$';
grant all privileges on pythondb.* to pythonfox@localhost;
flush privileges;
quit
 
### 테이블 백업하기 ####
mysqldump -uroot ---databases pythondb > pythondb.sql
 
### 테이블 구조만 백업하기 ####
mysqldump -uroot ---no-data --databases pythondb > pythondb.sql
 

 

4. 장고 프로젝트 생성 예시

 

 
################################################
# 장고 프로젝트 생성 및 Web 서버 구동 테스트
################################################
# venv 가상환경 실행
source /var/www/venv/bin/activate
 
# 프로젝트 생성 예시
cd /var/www/html/
django-admin startproject config .
 
python manage.py startapp accounts
python manage.py startapp blog
python manage.py startapp bookmark
 
# bootstrap4 라이브러리 설치
pip install django-bootstrap4
 
# Client IP address 수집을 위한 라이브러리
pip install django-ipware
 
# RSA/AES 암호화 라이브러리
pip install pycryptodome
pip install pycryptodomex
 
# mariadb db 연동 라이브러리
pip install mariadb
 
# 이제 Windows/MAC 환경에서 구현했던 코드를 리눅스 각 APP에 Override로 한다.
 
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py collectstatic
 
 
# 8000번 포트로 Web 서버 구동(개발자 모드)
python manage.py runserver 0.0.0.0:8000
 
# 정상 동작하는 걸 확인하고 나서
deactivate
 
sudo chown -R apache:apache /var/www/html/
 
 
# Django 배포(운영)를 위한 설정
vi /etc/httpd/conf.d/django.conf
 
<VirtualHost *:80>
        ServerName localhost
        DocumentRoot /var/www/html/
 
        Alias /static /var/www/html/static
        <Directory "/var/www/html/static">
                Options FollowSymLinks
                Order allow,deny
                Allow from all
                Require all granted
        </Directory>
 
        Alias /media /var/www/html/media
        <Directory "/var/www/html/media">
                Options FollowSymLinks
                Order allow,deny
                Allow from all
                Require all granted
        </Directory>
        ErrorLog /var/www/logs/error.log
        CustomLog /var/www/logs/access.log combined
 
        WSGIPassAuthorization On
        WSGIDaemonProcess wowdjango python-path=/var/www/html:/var/www/venv/lib/python3.6/site-packages
        WSGIProcessGroup wowdjango
        WSGIScriptAlias / /var/www/html/config/wsgi.py
 
        <Directory /var/www/html/config>
                <Files wsgi.py>
                        Require all granted
                </Files>
        </Directory>
</VirtualHost>
 
 
# 아래 두줄을 실행하기 전에는 에러가 발생하면서 동작되지 않더라.
sudo cp /opt/rh/httpd24/root/usr/lib64/httpd/modules/mod_rh-python36-wsgi.so /lib64/httpd/modules
sudo cp /opt/rh/httpd24/root/etc/httpd/conf.modules.d/10-rh-python36-wsgi.conf /etc/httpd/conf.modules.d
 
systemctl restart httpd
 
# 서버를 재부팅하거나 Web 서버를 중지시킨 경우에 가상환경 재실행 명령어
source /var/www/venv/bin/activate
 
cd /var/www/html/
python manage.py runserver 0.0.0.0:8000
 
# 가상환경 종료
deactivate
 
# Web 브라우저에서 접속시 문제가 없는지 테스트 해본다.
 

 

 

파일 위치 찾기

 

개발 서버 구동

 

 

환경설정 파일

python_conf.zip
0.10MB

 

 

mod_wsgi 설치하는 또다른 방법이다.

sudo yum -y install httpd httpd-devel
#mod_wsgi 는 python pip 로 설치할 것이므로 yum 설치는 생략한다.
 
# venv 가상환경 실행
source /var/www/venv/bin/activate
 
# mod_wsgi 설치
pip install mod_wsgi
 
# mod_wsgi 설치 확인
mod_wsgi-express start-server
 
#wsgi_module 등록하기
mod_wsgi-express install-module
 
cd /etc/httpd/conf.modules.d/
vi 10-wsgi.conf
LoadModule wsgi_module "/usr/lib64/httpd/modules/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so"
# :wq(저장)하고 빠져나온다.
 
# 정상 동작하는 걸 확인하고 나서
deactivate
 
# Web 브라우저에서 잘 동작되는 걸 확인할 수 있다.

 

 

 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

SQLite 높은 버전을 소스설치하고 나서 Python 에서 sqlite3 버전을 체크하면 계속 3.7.17 버전으로 표시되는 경우에 해결방법이다.

 

cd /root
mkdir sqlite3
cd sqlite3
wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.10.2/1.fc22/x86_64/sqlite-3.10.2-1.fc22.x86_64.rpm
wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.10.2/1.fc22/x86_64/sqlite-devel-3.10.2-1.fc22.x86_64.rpm
 
sudo yum -y install sqlite-3.10.2-1.fc22.x86_64.rpm sqlite-devel-3.10.2-1.fc22.x86_64.rpm
 
python -"import sqlite3; print(sqlite3.sqlite_version)"
 

 

이것 저것 테스트하다보니 원하지 않는 결과로 당황하기도 하면서 다시 한번 적어둔다.

 

아래와 같이 소스 설치하는 방법도 나오는데 이런거 전혀 필요없더라.

cd /root
wget https://www.sqlite.org/2020/sqlite-autoconf-3320100.tar.gz 
tar xvfz sqlite-autoconf-3320100.tar.gz
cd sqlite-autoconf-3320100 
./configure
make && make install
 
mv /usr/bin/sqlite3 /usr/bin/sqlite3_old
cp /usr/local/bin/sqlite3 /usr/bin/sqlite3
 

 

블로그 이미지

Link2Me

,
728x90

Python 3 를 yum 으로 설치하면 3.6.X 버전이 설치된다.

가상환경에서 tensorflow 를 설치했더니 3.10.X 버전을 요구하는 것 같다.

그래서 3.10.2 버전을 소스 설치했더니 SSL 에러가 발생하더라. 삽질 끝에 해결이 되었다.

 

###############################################################
# 파이썬(Python 3.10) 설치 과정
###############################################################
 
# Python 모듈을 빌드하려면 개발 도구가 필요
yum -y install epel-release
yum -y groupinstall 'Development Tools'
yum -y install yum-utils
yum -y install mariadb-devel 
yum -y install zlib zlib-devel libffi-devel bzip2-devel
yum -y install gcc gcc-c++ openssl openssl-devel
yum -y install zip unzip wget mc git net-tools
 
# openssl 경로 맞춰주기
vi /root/.bashrc
export PATH="/usr/local/ssl/bin:${PATH}"
 
mkdir -/home/httpd/python/
cd /home/httpd/python/
wget https://www.python.org/ftp/python/3.10.2/Python-3.10.2.tgz
sudo tar xvf Python-3.10.2.tgz
 
 
cd /home/httpd/python/Python-3.10.2/Modules
# 파이썬 인스톨 전에 SSL 설정 꼭 해야 한다.
vi Setup
 
# /ssl 로 찾아 주석처리된 것은 그대로 두고 아래 코드를 추가한다.
SSL=/usr/local/ssl
_ssl _ssl.c \
    -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
    -L$(SSL)/lib -lssl -lcrypto
 
#:wq 로 저장하고 빠져나온다.
 
cd ..
./configure --enable-optimizations
sudo make altinstall
 
 
python3.10 --version
 
pip3.10 --version
pip3.10 list
 
vi /root/.bashrc
alias python3="/usr/local/bin/python3.10"
alias pip="/usr/local/bin/pip3.10"
 
source /root/.bashrc
 
 
# 현재 Alias 확인
ls -/bin/python*
 
# ln -s 원본파일이름 심볼릭링크이름
ln -/bin/pip3.6 /bin/pip
# 심볼릭 링크(ln) 삭제
rm /bin/pip
 
ln -/usr/local/bin/pip3.10 /bin/pip
 
 
##### 파이썬 가상환경 설정 ####################################
# pip 업그레이드
/usr/local/bin/python3.10 -m pip install --upgrade pip
 
cd /home/httpd/python/
python3 -m pip install virtualenv
python3 -m pip install --upgrade pip
 
# 가상환경 이름을 django 로 설정했는데 다른 명칭으로 변경해도 된다.
virtualenv dlearning
# 실행하면 dlearning 폴더가 자동으로 생성된다.
 
# dlearning 가상환경 실행
source /home/httpd/python/dlearning/bin/activate
 
cd dlearning
 
# 파이썬 디버깅 도구 설치
pip install pylint
pip install twisted
 
pip install numpy pandas jupyter
pip install tensorflow-cpu
# GPU 가 있는 버전은 pip install tensorflow 로 한다.
 
 
# 가상환경 종료
deactivate

 

위에 설명된 SSL 부분의 실제 내용을 캡쳐한 그림이다.

 

yum 설치가 안될 경우 아래와 같이 코드를 수정한다.

# 아래와 같이 수정하지 않으면 yum 설치가 되지 않는다.
vi /usr/bin/yum
# 첫줄에 python 을 python2.7 로 변경하고 저장(:wq)한다.
 
vi /usr/libexec/urlgrabber-ext-down
# 첫줄에 python 을 python2.7 로 변경하고 저장(:wq)한다.

 

참고자료

https://brightwhiz.com/how-to-install-python-3-10-on-centos-7-centos-8-linux-systems/

 

How To Install Python 3.10 on CentOS 7 | CentOS 8 Linux Systems

With this tutorial you can follow along to install Python 3.10 on CentOS 7 or CentOS 8 Linux systems to have the latest version

brightwhiz.com

위 사이트만으로는 해결이 안될 수 있으니 꼭 SSL 설치 부분을 고려해줘야 한다.

 

블로그 이미지

Link2Me

,
728x90

class 에서 db 를 호출하고 처리하는 코드를 구현하기 위한 샘플을 작성했다.

 

class Config(object):
  DATABASE_CONFIG = {
          'server''localhost',
          'user''root',
          'password''autoset',
          'dbname''pythondb',
          }
 
 
from config import Config
import mariadb
import sys
 
class DbConnection:
    def __init__(self):
        self._conn=mariadb.connect(
            user=Config.DATABASE_CONFIG['user'],
            password=Config.DATABASE_CONFIG['password'],
            host=Config.DATABASE_CONFIG['server'],
            port=3306,
            database=Config.DATABASE_CONFIG['dbname']
        )
        self._cursor = self._conn.cursor()
 
    def __enter__(self):
        return self
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
 
    @property
    def connection(self):
        return self._conn
 
    @property
    def cursor(self):
        return self._cursor
 
    def commit(self):
        self.connection.commit()
 
    def close(self, commit=True):
        if commit:
            self.commit()
        self.connection.close()
 
    def execute(self, sql, params=None):
        self.cursor.execute(sql, params or ())
 
    def fetchall(self):
        return self.cursor.fetchall()
 
    def fetchone(self):
        return self.cursor.fetchone()
 
    def query(self, sql, params=None):
        self.cursor.execute(sql, params or ())
        return self.fetchall()
 
    def rows(self):
        return self.cursor.rowcount

 

 

 
# pip install pymysql or pip install mariadb
 
from dbconn import DbConnection
 
db = DbConnection()
 
class LoginClass:
    def WebLogin(self,userid, password):
        pass
 
    def sampleQuery(self,sql):
        rows = db.query(sql)
        print(rows)
        print(db.rows())
 
    def sampleQueryResult(self,sql):
        rs = db.query(sql)
        # print(db.rows())
        return rs
 

 

테스트를 위해서 위와 같은 샘플 함수를 만들어서 테스트했다.

DB와 다른 기능을 혼용하여 함수를 구현하기 위한 목적이다.

from loginclass import LoginClass
 
if __name__ == '__main__':
 
    params = tuple([1,3,4])
    sql = 'select * from errorcode where codeID IN {}'.format(params)
 
    auth = LoginClass()
    auth.sampleQuery(sql)
 
    rs = auth.sampleQueryResult('select * from errorcode')
    print(rs)
 

 

 

'파이썬 > Python 활용' 카테고리의 다른 글

[Python] Naver API 주소 좌표변환 예제  (0) 2022.03.16
[파이썬] 엑셀 파일 읽고 쓰기  (0) 2021.07.02
블로그 이미지

Link2Me

,
728x90

Naver API 를 이용하여 주소를 위치좌표로 변환하는 예제 코드이다.

 

https://link2me.tistory.com/1832 를 참조하고, geocoding API 를 신청하고 나서 인증정보를 눌러보면 아래와 같은 key 정보를 알 수 있다.

 

 

Client ID, Client Secret 키를 복사해서 아래 코드에 붙여넣기 한다.

# python.exe -m pip install --upgrade pip
# pip install requests
# pip install openpyxl
class
 NaverAPI:
    def geocoding(self, addr):
        import requests
 
        client_id = ""
        client_secret = ""
 
        url = f"https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode?query={addr}"
        headers = {'X-NCP-APIGW-API-KEY-ID': client_id,
                   'X-NCP-APIGW-API-KEY': client_secret
                   }
 
        r = requests.get(url, headers=headers)
 
        if r.status_code == 200:
            data = r.json()
            lat = data['addresses'][0]['y']  # 위도
            lng = data['addresses'][0]['x']  # 경도
            stAddress = data['addresses'][0]['roadAddress'# 도로명 주소
            list = [lat,lng,stAddress]
            return list
 

 

class 를 만든 후 아래와 같이 인스턴스를 생성하여 사용하면 된다.

엑셀 처리를 해도 되고 DB와 연동하여 다량의 데이터를 처리해도 된다.

from NaverAPI import NaverAPI
 
if __name__ == '__main__':
    addr = '서울시 서초구 서초대로50길 8'
    naverapi = NaverAPI()
    arr = naverapi.geocoding(addr)
 
    print(arr)
    print(arr[0])
    print(arr[1])
    print(arr[2])
 

 

 

'파이썬 > Python 활용' 카테고리의 다른 글

[Python] mariadb connection in a class  (0) 2022.03.16
[파이썬] 엑셀 파일 읽고 쓰기  (0) 2021.07.02
블로그 이미지

Link2Me

,
728x90

Django REST Framework는 라우터, 인증/권한, 데이터 규격화 (시리얼라이저), 필터/페이지네이션, 캐시, 쓰로틀, 렌더러, 테스트 등의 기능을 제공하며, 대체로 django 에서 제공하는 기능을 감싼 wrapper 형태로 되어있다.

 

https://www.django-rest-framework.org/

 

Home - Django REST framework

 

www.django-rest-framework.org

Home 에서 예시로 나온 코드를 복사하여 붙여넣기하면 기본적인 장고 REST 프레임웍이 동작되는 걸 확인할 수 있다.

 

다음 단계로 python manage.py startapp api 를 하고 나서

아래 그림처럼 파일을 각각 분리하여 serializers.py, views.py, urls.py 로 분리하여 넣는다.

 

프로젝트 수준의 urls.py

from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
    # path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
 

 

api/serializers.py

Serializer 는 queryset 과 model instance 를 쉽게 JSON 또는 XML 의 데이터 형태로 렌더링 할 수 있게 해준다.

 
# Serializers define the API representation.
from django.contrib.auth.models import User
from rest_framework import serializers
 
 
class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url''username''email''is_staff']
 

 

api/views.py

# ViewSets define the view behavior.
from django.contrib.auth.models import User
from rest_framework import viewsets
 
from api.serializers import UserSerializer
 
 
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
 

 

api/urls.py

# Routers provide an easy way of automatically determining the URL conf.
from django.urls import path, include
from rest_framework import routers
 
from api.views import UserViewSet
 
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
 
urlpatterns = [
    path('', include(router.urls)),
]

 

 

 

 

블로그 이미지

Link2Me

,
728x90

장고(Django) 사용자 인증 테스트 하면서 제대로 이해 못해서 삽질하다 기능 구현에 성공했다.

RSA 암호화/복호화 처리하는 코드는 배제하고 URL Redirection 처리 구현 목적으로 테스트했다.

장고에서 기본 제공하는 Custom User Model은 https://docs.djangoproject.com/en/dev/topics/auth/customizing/#a-full-example 을 참고하면 된다.

 

로그인이 안되어 있으면 로그인 화면으로 이동하도록 하는 옵션을 설정한다.

login_url 과 redirect_field_name 을 추가 해준다.

#--- ListView
class PostLV(LoginRequiredMixin,ListView):
    model = Post
    template_name = 'blog/post_all.html'
    context_object_name = 'posts'
    paginate_by = 2
    login_url = '/login/'
    redirect_field_name = 'redirect_to'
 

Mixin 클래스 : 자신의 인스턴스를 만드는 용도보다는 다른 클래스에게 부가 기능을 제공하기 위한 용도로 사용되는 클래스를 의미한다.

 

forms.py

class UserLoginForm(forms.Form):
    username = forms.CharField(
        max_length=32, label='userID',
        widget=forms.TextInput(
            attrs={
                "placeholder""Username",
                "class""form-control"
            }
        ))
    password = forms.CharField(
        label='비밀번호',
        widget=forms.PasswordInput(
            attrs={
                "placeholder""Password",
                "class""form-control"
            }
        ))
 
    def clean(self):
        if self.is_valid():
            username = self.cleaned_data.get('username')
            password = self.cleaned_data.get('password')
            if not authenticate(username=username, password=password):
                raise forms.ValidationError("입력 정보를 확인하세요.")
 

 

 

https://swarf00.github.io/2018/12/10/login.html 를 자세히 읽어보지 않고

대충 복사/붙여넣기 했더니 원하는데로 동작되지 않는다.

# accounts/views.py
from django.conf import settings
from django.contrib.auth import authenticate, login, logout, REDIRECT_FIELD_NAME
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django.contrib.sites.shortcuts import get_current_site
from is_safe_url import is_safe_url
from django.urls import reverse_lazy, reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView, FormView
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import render, redirect, resolve_url
from .forms import SignUpForm, LoginForm, UserLoginForm
from .RSACipher import *
from .models import User
# from django.contrib.auth.models import User
 
class UserLoginView(SuccessURLAllowedHostsMixin,FormView):
    template_name = 'accounts/userlogin.html'
    form_class = UserLoginForm
    redirect_field_name = REDIRECT_FIELD_NAME
    redirect_authenticated_user = False
    authentication_form = None
    extra_context = None
    # success_url = reverse_lazy("home")
 
    @method_decorator(sensitive_post_parameters())
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        if self.redirect_authenticated_user and self.request.user.is_authenticated:
            redirect_to = self.get_success_url()
            if redirect_to == self.request.path:
                raise ValueError(
                    "Redirection loop for authenticated user detected. Check that "
                    "your LOGIN_REDIRECT_URL doesn't point to a login page."
                )
            return HttpResponseRedirect(redirect_to)
        return super().dispatch(request, *args, **kwargs)
 
    def get_success_url(self):
        url = self.get_redirect_url()
        print("get_success_url : {}".format(url))
        return url or resolve_url(settings.LOGIN_REDIRECT_URL)
 
    def get_redirect_url(self):
        redirect_to = self.request.POST.get(
            self.redirect_field_name, # 1. 폼의 필드 중 next 이름을 가진 필드 값
            self.request.GET.get(self.redirect_field_name, '')
            # 2. query parameter 중 next 이름을 가진 값
        )
        url_is_safe = is_safe_url(
            url=redirect_to,
            allowed_hosts=self.get_success_url_allowed_hosts(),
            require_https=self.request.is_secure(),
        )
        return redirect_to if url_is_safe else ''
 
    def form_valid(self, form):
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password')
        user = authenticate(self.request, username=username, password=password)
        if user is not None and user.is_active:
            login(self.request,user)
        return super().form_valid(form)
 
 

 

 

로그인하면 Home 화면으로 무조건 이동하는 것은 아래와 같이 구현하면 된다.

# accounts/views.py
from django.contrib.auth import authenticate, login, logout, REDIRECT_FIELD_NAME
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django.urls import reverse_lazy, reverse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView, FormView
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect, resolve_url
from .forms import SignUpForm, LoginForm, UserLoginForm
from .RSACipher import *
from .models import User
# from django.contrib.auth.models import User
 
 
class UserLoginView(SuccessURLAllowedHostsMixin,FormView):
    template_name = 'accounts/userlogin.html'
    form_class = UserLoginForm
    success_url = reverse_lazy("home")
 
    def form_valid(self, form):
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password')
        user = authenticate(self.request, username=username, password=password)
        if user is not None and user.is_active:
            login(self.request,user)
        return super().form_valid(form)
 

 

게시글 List를 클릭할 때 인증되어 있지 않으면 인증 URL 로 이동하고, 인증 완료후 원래 게시글 List 화면으로 이동하는 기능을 구현하려고 엄청 삽질을 좀 했다.

세션 처리를 하면 금방 해결될 사항이었는데 말이다.

# accounts/views.py
from django.conf import settings
from django.contrib.auth import authenticate, login, logout, REDIRECT_FIELD_NAME
from django.urls import reverse_lazy, reverse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView, FormView
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect, resolve_url
from .forms import SignUpForm, LoginForm, UserLoginForm
from .models import User
# from django.contrib.auth.models import User
 
 
class UserLoginView(FormView):
    template_name = 'accounts/userlogin.html'
    form_class = UserLoginForm
    # success_url = reverse_lazy("home")
 
    def form_valid(self, form):
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password')
        user = authenticate(self.request, username=username, password=password)
        if user is not None and user.is_active:
            login(self.request,user)
        return super().form_valid(form)
 
    def get_context_data(self**kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.GET.get("redirect_to"):
            redirect_to = self.request.GET.get("redirect_to")
            self.request.session['redirect_to'= redirect_to
        return context
 
    def get_success_url(self):
        url = self.request.session.get('redirect_to')
        return url or resolve_url(settings.LOGIN_REDIRECT_URL)
 

ㅇ View : 모든 클래스형 뷰의 기본이 되는 최상위 뷰. 모든 클래스형 뷰는 이 View 클래스를 상속받는다.

ㅇ TemplateView : 단순하게 화면에 보여줄 템플릿 파일을 처리하는 정도의 간단한 뷰

ㅇ FormView : 폼을 보여주기 위한 뷰. form_class, template_name, success_url 속성 필요하다.

   만약 검색 결과를 같은 페이지에서 보여주고자 한다면, success_url 속성 지정은 생략한다.

   form_class 는 FormView, CreateView, UpdateView 에서 사용한다.

ㅇ CreateView : 새로운 레코드를 생성해서 테이블에 저장해주는 뷰. FormView의 기능을 포함하고 있다.

    폼을 만들 때 사용할 필드를 fields 속성으로 정의한다.

    fields 는 CreateView, UpdateView 에서 사용한다.

ㅇ get_context_data(**kwargs) :  모든 제네릭 뷰에서 사용하는 메소드이다.

    템플릿에서 사용할 context 데이터를 반환한다.

    뷰에서 템플릿 파일에 넘겨주는 context 데이터를 추가하거나 변경할 수 있다.

ㅇ 모델 폼(ModelForm) : 모델에 정의한 필드를 참조해서 모델 폼을 만드는 역할을 한다.

 

 

블로그 이미지

Link2Me

,
728x90

RSA 공개키, 개인키 생성 방법

# 리눅스 SSH 에서 RSA 공개키, 개인키 생성방법
mkdir -/home/rsa/key/
cd /home/rsa/key/
 
# Private Key 생성
openssl genrsa -out rsa_pri.pem 1024
 
# Public Key 생성
openssl rsa -pubout -in rsa_pri.pem -out rsa_pub.pem
 

 

rsa_key.php

Javascript 에서 RSA 암호화를 하고, PHP에서 복호화 시 아래 주석처리한 $key 부분을 주석처리 안해도 잘 동작된다.

그런데 Python 에서는 동작이 잘 안되더라. Python 에서도 동작되도록 Key 변수를 약간 수정했다.

<?php
function get_publickey() {
    // 경로 : 절대경로로 설정 필요
    $rsakeyfile = '/home/rsa/key/rsa_pub.pem';
    
    $fp = fopen($rsakeyfile,"r");
    $key = "";
    while(!feof($fp)) {
        $key .= fgets($fp,4096);
    } 
    fclose($fp);
 
    $key = preg_replace('/\r\n|\r|\n/','\n',$key);
    //$key = str_replace('-----BEGIN PUBLIC KEY-----','',$key);
    //$key = str_replace('-----END PUBLIC KEY-----','',$key);
    return $key;
 
function get_privatekey() {
    // 경로 : 절대경로로 설정 필요
    $rsakeyfile = '/home/rsa/key/rsa_pri.pem';
    
    $fp = fopen($rsakeyfile,"r");
    $key = "";
    while(!feof($fp)) {
        $key .= fgets($fp,4096);
    } 
    fclose($fp);
 
    $key = preg_replace('/\r\n|\r|\n/','\n',$key);
    //$key = str_replace('-----BEGIN RSA PRIVATE KEY-----','',$key);
    //$key = str_replace('-----END RSA PRIVATE KEY-----','',$key);
    return $key;
 
?>

 

login.html

pubkey는 예시로 일부만 발췌한 것이라 이 코드를 그대로 복사하면 동작되는게 아니다.

위에서 PHP코드로 발췌한 공개키를 복사하여 붙여녛기 해야 동작한다.

{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
 
{% block content %}
    <form method="POST" id="post-form">
        {% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="btn btn-primary">로그인</button>
        <a href="{% url 'home' %}">
            <button type="button" class="btn btn-warning">취소</button>
        </a>
    </form>
    <script type="text/javascript" src="{% static 'js/jsencrypt.min.js' %}"></script>
    <script>
        $(document).on('submit''#post-form'function (e) {
            e.preventDefault();
            var plainpw = $("input[name=password]").val();
            var pubkey = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCQAB\n-----END PUBLIC KEY-----";
            var crypt = new JSEncrypt();
            crypt.setPrivateKey(pubkey);
            var password = crypt.encrypt(plainpw);
 
            $.ajax({
                type: 'POST',
                url: '{% url 'login' %}',
                data: {
                    username: $("input[name=username]").val(),
                    password: password,
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    console.log(json);
                    if (json.data == 1) {
                        window.location.href = '/';
                        alert(json.message);
                    } else if (json.data == 0) {
                        alert(json.message);
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했습니다.');
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
    </script>
{% endblock content %}
 

 

 

Javascript 에서 암호화한 비밀번호를 views.py 파일에서 복호화한다.

# accounts/views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
from .forms import SignUpForm, LoginForm
from .RSACipher import *
 
def login_view(request):
    context = {}
 
    if request.method == "POST":
        form = LoginForm(request.POST)
        if form.is_valid():
            # 유효성 검사, 내부적으로 form.clean()을 실행한다.
            username = form.cleaned_data.get("username")
            enc_password = form.cleaned_data.get("password")
            password = RSACipher().decrypt(enc_password)
 
            try:
                user = User.objects.get(username=username)
                if check_password(password, user.password):
                    access = authenticate(username=username, password=password)
                    login(request, access)
                    context['data'= '1'
                    context['message'= '로그인 되었습니다.'
                else:
                    context['data'= '0'
                    context['message'= '입력 정보를 확인하세요.'
                    # context['message'] = '비밀번호가 다릅니다.'
                return JsonResponse(context)
            except User.DoesNotExist:  # 대문자 User 임에 주의
                # 가입된 회원인지 미가입된 회원인지 알 수 없는 메시지 출력이 중요
                context['data'= '0'
                context['message'= '입력 정보를 확인하세요.'
                return JsonResponse(context)
        else:
            context['form'= form
    else:
        form = LoginForm()
        context['form'= form
 
    return render(request, "accounts/login.html", context)
 
 
def logout_view(request):
    logout(request)
    return redirect("home")
 

 

 

라이브러리 설치

pip install pycryptodomex (for window)

pip install pycryptodome (for linux)

 

 

RSACipher 클래스는 아래 게시글을 참조하여 직접 구현하면 될 것이다.

https://stackoverflow.com/questions/30056762/rsa-encryption-and-decryption-in-python

 

RSA encryption and decryption in Python

I need help using RSA encryption and decryption in Python. I am creating a private/public key pair, encrypting a message with keys and writing message to a file. Then I am reading ciphertext from...

stackoverflow.com

https://pycryptodome.readthedocs.io/en/latest/src/cipher/pkcs1_v1_5.html

 

PKCS#1 v1.5 encryption (RSA) — PyCryptodome 3.14.0 documentation

A cipher object PKCS115_Cipher.

pycryptodome.readthedocs.io

 

 

Custom User Model 기반으로 jQuery ajax 처리하는 전체 코드는 https://github.com/jsk005/PythonDjango/tree/main/customUserModel2 를 참조하면 도움될 것이다.

이 코드에서 RSACipher 클래스의 함수 코드 일부를 삭제했으나, 공부해서 충분히 해결할 수 있을 것으로 본다.

블로그 이미지

Link2Me

,
728x90

장고에서 기본 제공하는 회원 로그인 기능과 회원가입 Form 기반 코드를 테스트하고 적어둔다.

기본 제공하는 것이라서 필요할까 싶어서 적어두지 않을까 하다가 언젠가는 필요할 수도 있을지 몰라 몇가지 기본적인 것만 적어둔다.

 

전체 소스코드 : https://github.com/jsk005/PythonDjango/tree/main/formProject

 

GitHub - jsk005/PythonDjango

Contribute to jsk005/PythonDjango development by creating an account on GitHub.

github.com

 

 

import os
from pathlib import Path
 
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 3rd party app
    'bootstrap4',  # add, pip install django-bootstrap4
    # local app
    'accounts.apps.AccountConfig',  # add
]
 
 
LANGUAGE_CODE = 'en-us'
 
TIME_ZONE = 'Asia/Seoul'
 
USE_I18N = True
 
USE_TZ = False
 
 
STATIC_URL = 'static/'
MEDIA_URL = 'media/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
    os.path.join(BASE_DIR, 'media'),
]
 
 
# 로그인 성공후 이동하는 URL
LOGIN_REDIRECT_URL = '/'
 

 

 

urls.py

 
# project urls.py
from django.contrib import admin
from django.urls import path
from accounts import views
from .views import homescreen_view
from django.contrib.auth import views as auth_views
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', homescreen_view, name='home'),
    path('signup/', views.register, name='signup'),
    path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
    path('logout/', views.logout_view, name='logout'),
]
 

 

views.py

# project views.py
from django.shortcuts import render
 
def homescreen_view(request):
    context = {}
    return render(request, "home.html", context)
 

 

 

forms.py

회원 칼럼을 마음껏 추가하는 것은 안된다.

회원 칼럼을 원하는대로 추가하려면 Custom User Model를 사용해야 한다.

# accounts/forms.py
from django import forms
from django.contrib.auth.models import User
 
class UserForm(forms.ModelForm):
    class Meta:
        model = User
        # 장고 기본 로그인 사용으로 fields 가 제한적이다.
        # re-password 위젯을 추가해봤으나 동작하지 않더라.
        fields = ['username''email''password',]
        widgets = {
            'username': forms.TextInput(attrs={
                'class'"form-control",
                'style''max-width: 300px;',
                'placeholder''userID'
            }),
            'email': forms.EmailInput(attrs={
                'class'"form-control",
                'style''max-width: 300px;',
                'placeholder''Email'
            }),
            'password': forms.PasswordInput(attrs={
                'class'"form-control",
                'style''max-width: 300px;',
                'placeholder''비밀번호'
            }),
 
        }
        labels = {
            'username''userID',
            'email''이메일',
            'password''비밀번호',
        }
 
    # 글자수 제한
    def __init__(self*args, **kwargs):
        super(UserForm, self).__init__(*args, **kwargs)
        self.fields['username'].widget.attrs['minlength'= 6
        self.fields['username'].widget.attrs['maxlength'= 15
 

 

 

views.py

회원가입 처리 부분이다. 회원 가입 도중에 중복된 ID가 있다고 모든 입력 폼을 다시 입력해야 하는 불편함을 없애고자 jQuery Ajax 처리를 했다.

회원 가입 완료되면, return render(request, 'home.html', context) 으로 처리해도 된다.

패스워드 입력체크는 jQuery 에서 하도록 했다.

# accounts/views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
from .forms import UserForm
 
@csrf_exempt
def register(request, *args, **kwargs):
    user = request.user
    context = {}
 
    if user.is_authenticated:
        # return HttpResponse("You are already authenticated as " + str(user.email))
        return render(request,'home.html',context)
 
    if request.POST:
        form = UserForm(request.POST)
        if form.is_valid():
            # form.save()
            account = User.objects.create_user(**form.cleaned_data)
            login(request, account)
            # return render(request, 'home.html', context)
            context['data'= '1'
            context['message'= '회원 가입 완료'
            return JsonResponse(context)
        else:
            context['data'= '0'  # 기존 가입된 회원
            context['message'= '이미 가입된 회원입니다'
            return JsonResponse(context)
    else:
        form = UserForm()
        context['form'= form
 
    return render(request, 'accounts/register.html', context)
 
 
def logout_view(request):
    logout(request)
    return redirect("home")
 

 

register.html

{% bootstrap_form form %} 과 views.py 의 register 함수의 context['form'] = form 은 굵은 색깔 부분이 같아야 한다.

비밀번호 확인은 forms.py 에서 추가가 안되어서 별도로 추가했다.

url: '{% url 'signup' %}'  과 urls.py 의 path('signup/', views.register, name='signup'), 은 굵은 색깔 부분이 같아야 한다.

jQuery ajax 는 원래 파이썬이 처리하는 기능을 가로채서 처리하므로 csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val() 와 action: 'post' 을 별도 추가해야 동작한다.

{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
 
{% block content %}
    <form method="POST" id="post-form">
        {% csrf_token %}
        {% bootstrap_form form %}
        <div class="form-group">
            <label for="id_repasswd">비밀번호 확인</label>
            <input type="password" name="repasswd" class="form-control" style="max-width: 300px;" required="" id="id_repasswd">
        </div>
        <button type="submit" class="btn btn-primary">가입</button>
        <a href="{% url 'home' %}">
            <button type="button" class="btn btn-warning">취소</button>
        </a>
    </form>
    <script type="text/javascript" src="{% static 'js/user/passwd_checker.js' %}"></script>
    <script>
        $(document).on('submit''#post-form'function (e) {
            e.preventDefault();
            const pwd = $("input[name=password]");
            const repwd = $("input[name=repasswd]");
            if (PasswordChk(pwd, repwd) == falsereturn false;
            $.ajax({
                type: 'POST',
                url: '{% url 'signup' %}',
                data: {
                    username: $("input[name=username]").val(),
                    email: $("input[name=email]").val(),
                    password: $("input[name=password]").val(),
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    if (json.data == 1) {
                        window.location.href = '/';
                        alert(json.message);
                    } else if (json.data == 0) {
                        alert(json.message);
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했습니다.');
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
    </script>
{% endblock content %}
 

 

 

로그인 처리는 장고가 기본 제공하는 기능을 이용하였기 때문에 jQuery ajax 코드를 추가하면 에러가 발생한다.

jQuery ajax 처리하려면, 별도로 views.py 에서 login_view 와 같은 함수를 구현하고 login.html 에서 ajax 코드를 추가해야 한다.

 

 

 

 

블로그 이미지

Link2Me

,
728x90

장고(django)가 기본 제공하는 유저 모델(User Model)이 아닌 커스텀 유저 모델(Custom User Model)을 활용 방법이다.

 

제대로 된 방법 찾으려고 엄청 삽질을 한 끝에 이해하고 테스트한 결과를 적어둔다.

구글링을 해도 제대로 이해가 잘 안되는 것은 설명이 부족해서 인지 이해가 부족해서인지 본인 스스로 판단해야 한다.

 

https://link2me.tistory.com/2109 에 게시한 내용은 장고 기본 User Model 과는 별도의 User Model를 생성해서 별개로 로그인하는 방법이다.

 

이번 주제는 장고 기본 User Model 대신에 원하는 칼럼을 마음껏 추가할 수 있는 Custom User Model 이다.

구글링을 하면 장고 기본 User Model을 약간 수정하는 정도를 다루는 방법이 소개되어 있기도 하다.

 

Custom User Model을 구현하기 위해서는 BaseUserManagerAbstractBaseUser 클래스를 상속받아 새롭게 구현해야 한다. 여기서 BaseUserManager는 유저를 생성하는 역할을 하는 헬퍼 클래스이고, AbstractBaseUser는 실제 모델이 상속받아 생성하는 클래스이다.

 

기본 설치사항

터미날에서 아래 사항을 실행한다.

python manage.py startapp account
python manage.py startapp blog
mkdir static
mkdir media
pip install django-bootstrap4
pip install pillow

 

settings.py 수정사항

import os
from pathlib import Path
 
ALLOWED_HOSTS = ['*']
 
if DEBUG:
    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # development only
 
AUTH_USER_MODEL = 'account.Account'
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.AllowAllUsersModelBackend',
    'account.backends.CaseInsensitiveModelBackend',
    )
 
# Application definition
 
INSTALLED_APPS = [
    # built in django app
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 3rd party app
    'bootstrap4',  # add, pip install django-bootstrap4
    # local app
    'account.apps.AccountConfig',  # add
    'blog.apps.BlogConfig'# add
 
]
 
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
        # 'ENGINE': 'django.db.backends.mysql', # mysqlclient librarly 설치
        # 'NAME': 'pythondb',
        # 'USER': 'root',
        # 'PASSWORD': 'autoset', # mariaDB 설치 시 입력한 root 비밀번호 입력
        # 'HOST': 'localhost',
        # 'PORT': ''
    }
}
 
 
LANGUAGE_CODE = 'en-us'
# 수정
TIME_ZONE = 'Asia/Seoul'
 
USE_I18N = True
# 수정
USE_TZ = False
 
 
STATIC_URL = 'static/'
MEDIA_URL = 'media/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
    os.path.join(BASE_DIR, 'media'),
]
 
BASE_URL = "http://127.0.0.1:8000"
 

 

models.py

테이블 구성요소를 작성한다.

 

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
 
class MyAccountManager(BaseUserManager):
    # 일반 user 생성, username 이 userID를 의미함
    def create_user(self, email, username, name, password=None):
        if not email:
            raise ValueError("Users must have an email address.")
        if not username:
            raise ValueError("Users must have an userID.")
        if not name:
            raise ValueError("Users must have an name.")
        user = self.model(
            email = self.normalize_email(email),
            username = username,
            name = name
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
 
    # 관리자 User 생성
    def create_superuser(self, email, username, name, password):
        user = self.create_user(
            email = self.normalize_email(email),
            username = username,
            name = name,
            password=password
        )
        user.is_admin = True
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user
 
class Account(AbstractBaseUser):
    email       = models.EmailField(verbose_name='email', max_length=60, unique=True)
    username    = models.CharField(max_length=30, unique=True)
    name        = models.CharField(max_length=40null=False, blank=False)
    create_at = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
    last_login  = models.DateTimeField(verbose_name='last login', auto_now=True)
    is_admin    = models.BooleanField(default=False)
    is_active   = models.BooleanField(default=True)
    is_staff    = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
 
    object = MyAccountManager()  # 헬퍼 클래스 사용
 
    USERNAME_FIELD = 'email'  # 로그인 ID로 사용할 필드
    REQUIRED_FIELDS = ['username''name'# 필수 작성 필드
 
    def __str__(self):
        return self.username
 
    def has_perm(self, perm, obj=None):
        return self.is_admin
 
    def has_module_perms(self, app_lable):
        return True
 

 

여기까지 하고 나서 아래 두줄을 터미널 창에서 실행한다.

python manage.py makemigrations
python manage.py migrate 

 

admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from account.models import Account
 
class AccountAdmin(UserAdmin):
    # 관리자 화면에 보여질 칼럼 지정
    list_display = ('email','username','name','create_at','last_login','is_admin','is_staff')
    search_fields = ('email''username''name')
    readonly_fields = ('id''create_at''last_login')
 
    filter_horizontal = ()
    list_filter = ()
    fieldsets = ()
 
admin.site.register(Account, AccountAdmin)
 

 

Templates

Layout 구성을 위해서는 Bootstrap Template를 받아서 활용하는 것이 좋다.

https://github.com/mdbootstrap/Bootstrap-4-templates 에서 템플릿을 받아서 static 폴더에 저장한다.

https://startbootstrap.com/previews/sb-admin-2 에서 무료 템플릿을 받아서 static 폴더에 저장한다.

둘 중 선택하거나 그 외 다른 템플릿을 선택해도 된다.

 

 

base.html

{% load static %}
 
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.11.2/css/all.css">
    <!-- Bootstrap core CSS -->
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <!-- Material Design Bootstrap -->
    <link rel="stylesheet" href="{% static 'css/mdb.min.css' %}">
    <!-- Your custom styles (optional) -->
    <link rel="stylesheet" href="{% static 'css/style.min.css' %}">
 
    <!-- SCRIPTS -->
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/popper.min.js' %}"></script>
    <script src="{% static 'js/jquery.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/bootstrap.min.js' %}"></script>
    <!-- MDB core JavaScript -->
    <script type="text/javascript" src="{% static 'js/mdb.min.js' %}"></script>
    <script type="text/javascript">
        // Animations initialization
        new WOW().init();
    </script>
 
    {% block head %}
    {% endblock %}
</head>
<body>
<header>
    {% include 'header.html' %}
</header>
<!--Main layout-->
<main class="mt-5 pt-5">
    <div class="container">
        {% block content %}
        {% endblock %}
    </div>
</main>
 
<!-- SCRIPTS -->
</body>
</html>

 

header.html

수정해서 사용할 영역이며, 완벽한 예시는 아니다.

 
{% load static %}
 
<!-- Navbar -->
<nav class="navbar fixed-top navbar-expand-lg navbar-light white scrolling-navbar">
    <div class="container">
 
        <!-- Brand -->
        <a class="navbar-brand waves-effect" href="#">
            <strong class="blue-text">Home</strong>
        </a>
 
        <!-- Collapse -->
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
 
        <!-- Links -->
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
 
            <!-- Left -->
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link waves-effect" href="#" target="_blank">About MDB</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link waves-effect" href="#" target="_blank">Free
                        tutorials</a>
                </li>
            </ul>
 
            <form class="search-bar justify-content-start" onsubmit="return executeQuery();">
                <input type="text" class="form-control" name="q" id="id_q_large" placeholder="Search...">
            </form>
 
            <!-- Right -->
            <ul class="navbar-nav nav-flex-icons">
                {% if request.user.is_authenticated %}
                    <li class="nav-item">
                        <a href="{% url 'logout' %}" class="nav-link waves-effect" title="로그아웃">
                            <i class="fas fa-sign-out-alt"></i>
                        </a>
                    </li>
                {% else %}
                    <li class="nav-item">
                        <a href="{% url 'login' %}" class="nav-link waves-effect" title="로그인">
                            <i class="fas fa-sign-in-alt"></i>
                        </a>
                    </li>
                    <li class="nav-item">
                        <a href="{% url 'register' %}" class="nav-link waves-effect" title="회원가입">
                            <i class="far fa-registered"></i>
                        </a>
                    </li>
                {% endif %}
            </ul>
 
        </div>
 
    </div>
</nav>
<!-- Navbar -->
<script type="text/javascript">
    function executeQuery() {
        var query = ""
        query = document.getElementById('id_q_small').value;
        if (query == ""){
            query = document.getElementById('id_q_large').value;
        }
        {#window.location.replace("{% url 'search' %}?q=" + query)#}
        return false
    }
</script>
 

 

 

home.html

{% extends "base.html" %}
{% load static %}
{% block title %}Home{% endblock %}
 
{% block content %}
 
    <div>Welcome to python django world!</div>
    {% if request.user.is_authenticated %}
    <div>{{ user.name }}</div>
    <div>{{ user }}</div>
    {% endif %}
 
{% endblock %}

 

register.html

 
{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
{% block title %}회원 가입{% endblock %}
 
{% block content %}
    <div class="row justify-content-center">
        <form method="post" class="text-center border border-light p-5">
            {% csrf_token %}
            <class="h4 mb-4">회원 가입</p>
            <div class="form-group">
                <input type="email" name="email" id="inputEmail" class="form-control"
                       placeholder="Email address" required autofocus>
            </div>
            <div class="form-group">
                <input type="text" name="username" id="inputUsername" class="form-control"
                       placeholder="UserID(아이디)" required>
            </div>
            <div class="form-group">
                <input type="text" name="name" id="inputName" class="form-control"
                       placeholder="UserNM(성명)" required>
            </div>
            <div class="form-group">
                <input type="password" name="password1" id="inputPassword1" class="form-control"
                       placeholder="Password" required>
            </div>
            <div class="form-group">
                <input type="password" name="password2" id="inputPassword2" class="form-control"
                       placeholder="Confirm password" required>
            </div>
            {% for field in registration_form %}
                <p>
                    {% for error in field.errors %}
                        <p style="color: red">{{ error }}</p>
                    {% endfor %}
                </p>
            {% endfor %}
            {% if registration_form.non_field_errors %}
                <div style="color: red">
                    <p>{{ registration_form.non_field_errors }}</p>
                </div>
 
            {% endif %}
 
            <button class="btn btn-primary btn-block" type="submit">Register</button>
 
        </form>
 
    </div>
 
{% endblock content %}

 

login.html

{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
{% block title %}로그인{% endblock %}
 
{% block content %}
    <div class="row justify-content-center">
        <form method="post" class="text-center border border-light p-5">
            {% csrf_token %}
            <class="h4 mb-4 text-center">로그인</p>
            <div class="form-group">
                <input type="email" name="email" id="inputEmail" class="form-control mb-4" placeholder="Email address"
                       required="required">
            </div>
            <div class="form-group">
                <input type="password" name="password" id="inputPassword" class="form-control mb-4"
                       placeholder="비밀번호 입력하세요" required="required">
            </div>
 
            {% for field in login_form %}
                <p>
                    {% for error in field.errors %}
                        <p style="color: red">{{ error }}</p>
                    {% endfor %}
                </p>
            {% endfor %}
            {% if login_form.non_field_errors %}
                <div style="color: red">
                    <p>{{ login_form.non_field_errors }}</p>
                </div>
 
            {% endif %}
            {% buttons %}
                <button class="btn btn-info btn-block my-4" type="submit">로그인</button>
            {% endbuttons %}
        </form>
 
        <div class="d-flex flex-column my-4">
            {#    <a class="m-auto" href="{% url 'password_reset' %}">Reset password</a>#}
        </div>
    </div>
 
{% endblock content %}
 

 

 

views.py

 
# account/views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.contrib.auth import login, authenticate, logout
from account.forms import RegistrationForm, AccountAuthForm
 
 
def register_view(request, *args, **kwargs):
    user = request.user
    if user.is_authenticated:
        return HttpResponse("You are already authenticated as " + str(user.email))
 
    context = {}
    if request.POST:
        form = RegistrationForm(request.POST)
        if form.is_valid():
            form.save()
            email = form.cleaned_data.get('email').lower()
            raw_password = form.cleaned_data.get('password1')
            account = authenticate(email=email, password=raw_password)
            login(request, account)
            # destination = kwargs.get("next")
            destination = get_redirect_if_exists(request)
            if destination: # if destination != None
                return redirect(destination)
            return redirect('home')
        else:
            context['registration_form'= form
    else:
        form = RegistrationForm()
        context['registration_form'= form
 
    return render(request, 'account/register.html', context)
 
 
def logout_view(request):
    logout(request)
    return redirect("home")
 
 
def login_view(request, *args, **kwargs):
    context = {}
 
    user = request.user
    if user.is_authenticated:
        return redirect("home")
 
    destination = get_redirect_if_exists(request)
    if request.POST:
        form = AccountAuthForm(request.POST)
        if form.is_valid():
            email = request.POST.get('email')
            password = request.POST.get('password')
            user = authenticate(email=email, password=password)
            if user:
                login(request, user)
                if destination:
                    return redirect(destination)
                return redirect("home")
    else:
        form = AccountAuthForm()
 
    context['login_form'= form
 
    return render(request, "account/login.html", context)
 
 
def get_redirect_if_exists(request):
    redirect = None
    if request.GET:
        if request.GET.get("next"):
            redirect = str(request.GET.get("next"))
    return redirect

 

forms.py

# account/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate
from account.models import Account
 
# 회원 가입 폼
class RegistrationForm(UserCreationForm):
    email = forms.EmailField(max_length=254, help_text='Required. Add a valid email address.')
 
    class Meta:
        model = Account
        fields = ('email''username''name''password1''password2', )
 
    def clean_email(self):
        email = self.cleaned_data['email'].lower()
        try:
            account = Account.objects.get(email=email)
        except Exception as e:
            return email
        raise forms.ValidationError(f"Email {email} is already in use.")
 
    def clean_username(self):
        username = self.cleaned_data['username']
        try:
            account = Account.objects.get(username=username)
        except Exception as e:
            return username
        raise forms.ValidationError(f"UserID {username} is already in use.")
 
 
 
# 로그인 인증 폼
class AccountAuthForm(forms.ModelForm):
    password = forms.CharField(label='Password', widget=forms.PasswordInput)
 
    class Meta:
        model = Account
        fields = ('email''password')
 
    def clean(self):
        if self.is_valid():
            email = self.cleaned_data['email']
            password = self.cleaned_data['password']
            if not authenticate(email=email, password=password):
                raise forms.ValidationError("Invalid login")
 

 

backends.py

# account/backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
 
class CaseInsensitiveModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None**kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            case_insensitive_username_field = '{}__iexact'.format(UserModel.USERNAME_FIELD)
            user = UserModel._default_manager.get(**{case_insensitive_username_field: username})
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user
 

 

urls.py

# project urls.py
from django.conf import settings
from django.contrib import admin
from django.conf.urls.static import static
from django.urls import path, include
from .views import home_screen_view
from account.views import register_view, login_view, logout_view
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_screen_view, name='home'),
    path('register/', register_view, name='register' ),
    path('login/', login_view, name='login'),
    path('logout/', logout_view, name='logout'),
]
 
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 

 

현재까지 테스트한 전체 소스 코드는 https://github.com/jsk005/PythonDjango 에 올려두었다.

 

참고자료

https://codingwithmitch.com/courses/real-time-chat-messenger/

 

CodingWithMitch.com

If you restart this course, all your progress will be reset

codingwithmitch.com

 

블로그 이미지

Link2Me

,
728x90

실제 운용할 서버가 아닌 개발 단계에서 makemigrations 를 자주 해서 여러번의 변경 이력이 있다면 합쳐서 적용하는 것이 좋다.

혹시라도 동작이 안되면 python manage.py squashmigrations accounts 006 과 같이 마지막으로 변경된 숫자를 확인해서 직접 적어주면 된다.

 

블로그 이미지

Link2Me

,
728x90

장고 회원가입과 로그인 기능을 함수형 view 에서 class 형 view 로 변경하는 연습을 하고 코드를 적어둔다.

 

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    {% block head %}
    {% endblock %}
</head>
<body>
<div class="container">
    {% block contents %}
    {% endblock %}
</div>
</body>
</html>

 

 

index.html

{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block contents %}
    <div>Hello world!</div>
    <div>{{ user.username }}</div>
    <div>{{ user }}</div>
{% endblock %}
 

 

urls.py

from django.contrib import admin
from django.urls import path,include
from . import views
 
urlpatterns = [
    path('admin/', admin.site.urls),
 
    path('', views.HomeView.as_view(), name='index'),
    path('user/', include('accounts.urls')),
]
 

 

HomeView

세션처리가 일반적인 케이스가 아니라서 여기서 개 삽질을 좀 했다.

로그인 세션 정보가 없으면 로그인 화면으로 자동 이동된다.

데이터베이스의 값을 템플릿에서 사용하려면 뷰에서 값을 전달해야 한다.

그때 context라는 형태로 템플릿에 값을 전달한다.

from django.views.generic import TemplateView
from django.utils.decorators import method_decorator
from accounts.decorators import login_required
import logging
from accounts.models import User
 
@method_decorator(login_required, name='dispatch')
class HomeView(TemplateView):
    template_name = 'index.html'
 
    def get_context_data(self**kwargs):
        user_id = self.request.session.get('user')
        if user_id:
            user = User.objects.get(pk=user_id)
            logging.warning(user.username)
            context = super(HomeView, self).get_context_data(**kwargs)
            context['user'= user
            return context
 

 

decorators.py

from django.shortcuts import redirect
from .models import User
 
def login_required(function):
    def wrap(request, *args, **kwargs):
        user = request.session.get('user')
        if user is None or not user:
            return redirect('/user/login')
        return function(request, *args, **kwargs)
 
    return wrap
 
 
def admin_required(function):
    def wrap(request, *args, **kwargs):
        user = request.session.get('user')
        if user is None or not user:
            return redirect('/login')
        
        user = User.objects.get(useremail=user)
        if user.level != 'admin':
            return redirect('/')
 
        return function(request, *args, **kwargs)
 
    return wrap
 

 

models.py and app urls.py, admin.py

함수형 뷰를 사용해도 되고, 클래스형 뷰를 사용해도 된다.

클래스형 뷰를 사용하기 위해서는 가장 먼저 urls.py 에서 클래스형 뷰를 사용한다는 걸 표시해줘야 한다.

# models.py
from django.db import models
 
class User(models.Model):
    username = models.CharField(max_length=32, verbose_name='사용자명')
    useremail = models.EmailField(max_length=128, verbose_name='emailID', unique=True)
    password = models.CharField(max_length=128,verbose_name='비밀번호')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='등록시간')
    updated_at = models.DateTimeField(auto_now=True)
 
    def __str__(self):
        return self.useremail
 
    class Meta:
        db_table = 'accounts'
        verbose_name = '사용자'
        verbose_name_plural = '사용자'
 
 
# app urls.py
from django.urls import path
from . import views
 
app_name = 'accounts'
urlpatterns = [
    # path('register/', views.register, name='ajax_register'), # ajax_register
    path('register/', views.UserRegisterView.as_view(), name='ajax_register'), # ajax_register
 
    # path('login/', views.login, name='ajax_login'),
    path('login/', views.UserLoginView.as_view(), name='ajax_login'),
 
    # path('logout/',views.logout),
    path('logout/',views.UserLogoutView.as_view()),
]
 
 
# admin.py
from django.contrib import admin
from .models import User
 
# Register your models here.
class UserAdmin(admin.ModelAdmin):
    list_display = ('username','useremail','password','created_at','updated_at')
 
admin.site.register(User,UserAdmin)
 

 

 

app views.py

from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
from django.views.decorators.csrf import csrf_exempt
 
from .models import User
 
# Create your views here.
def index(reqest):
    user_id = reqest.session.get('user')
 
    if user_id:
        user = User.objects.get(pk=user_id)
        return HttpResponse(user.useremail)
 
    return redirect('/')
 
 
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    elif request.method == 'POST':
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        res_data = {}
        try:
            user = User.objects.get(useremail=useremail)
            if check_password(password, user.password):
                # 비밀번호가 일치하면 세션 생성
                request.session['user'= user.id
                res_data['data'= '1'
                res_data['message'= '로그인 되었습니다.'
            else:
                res_data['data'= '0'
                res_data['message'= '입력 정보를 확인하세요.'
            return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            res_data['status'= '0'
            res_data['message'= '입력 정보를 확인하세요.'
            return JsonResponse(res_data)
 
        return render(request, 'login.html', res_data)
 
 
 
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        username = request.POST.get('username',None)
        useremail = request.POST.get('useremail',None)
        password = request.POST.get('password',None)
 
        res_data ={}
        try:
            user = User.objects.get(useremail=useremail)
            if user:
                res_data['status'= '0' # 기존 가입된 회원
                return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            user = User(
                username = username,
                useremail = useremail,
                password = make_password(password)
            )
            user.save()
            # session 생성
            user = User.objects.get(useremail=useremail)
            request.session['user'= user.id
            res_data['data'= '1' # 회원 가입 완료
            res_data['message'= '회원 가입 완료'
            return JsonResponse(res_data)
 
        return render(request,'register.html',res_data)
 
 
def logout(request):
    if request.session.get('user'):
        del(request.session['user'])
 
    return redirect('/user/login')
 
@method_decorator(csrf_exempt, name='dispatch')
class BaseView(TemplateView):
    @staticmethod
    def response(data={}, message='', status=200):
        result = {
            'data': data,
            'message': message,
            'status': status,
        }
        return JsonResponse(result)
 
class UserLogoutView(BaseView):
    def get(self, request):
        if self.request.session.get('user'):
            del (self.request.session['user'])
        return redirect('/')
 
class UserLoginView(BaseView):
    template_name = 'login.html'
    def post(self,request):
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        try:
            user = User.objects.get(useremail=useremail)
            if check_password(password, user.password):  # 비밀번호가 일치하면 세션 생성
                self.request.session['user'= user.id
                data = '1'
                message = '로그인 되었습니다.'
            else:
                data = '0'
                message = '입력 정보를 확인해주세요.'
            return self.response(data, message, status='')
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            data = '0'
            # message = '입력 정보를 확인해주세요.'
            message = '등록된 회원 정보가 없습니다.'
            return self.response(data, message, status='')
        return self.response()
 
 
class UserRegisterView(BaseView):
    template_name = 'register.html'
    def post(self,request):
        username = request.POST.get('username'None)
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        if not username:
            return self.response(data='0', message='성명을 입력해주세요.', status=400)
        if not password:
            return self.response(data='0', message='패스워드를 입력해주세요.', status=400)
        try:
            validate_email(useremail)
        except ValidationError:
            return self.response(data='0', message='올바른 이메일을 입력해주세요.', status=400)
 
        try:
            user = User.objects.get(useremail=useremail)
            if user:
                return self.response(data='0', message='이미 가입된 아이디입니다.', status=400)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            user = User(
                username=username,
                useremail=useremail,
                password=make_password(password)
            )
            user.save()
            # session 생성
            user = User.objects.get(useremail=useremail)
            self.request.session['user'= user.id
            return self.response(data='1', message='회원 가입 완료.', status=200)
 
        return self.response({'user.id': user.id})
 
 

 

User.objects.get(useremail=useremail) SELECT * FROM accounts WHERE useremail=useremail;

User.objects.all()  → SELECT * FROM accounts;

User.objects.all().order_by('-id')[:20] → SELECT * FROM accounts ORDER BY id DESC LIMIT 20;

 

 

login.html

POST 방식의 <form> 을 사용하는 탬플릿에서는 CSRF(Cross Site Request Forgery)  공격을 방어하기 위해 {% csrf_token %} 태그를 사용해야 한다.

 
{% extends "base.html" %}
{% block title %}로그인{% endblock %}
{% block contents %}
<div class="login-form pt-5">
    <form method="post" id="post-form">
        {% csrf_token %}
        <h2 class="text-center">로그인</h2>
        <div class="form-group">
            <input type="text" id="useremail" class="form-control" placeholder="user email 입력" required="required">
        </div>
        <div class="form-group">
            <input type="password" id="password" class="form-control" placeholder="비밀번호 입력" required="required">
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary btn-block">로그인</button>
        </div>
    </form>
    <class="text-center"><a href="{% url 'accounts:ajax_register' %}" id="register">회원 가입</a></p>
</div>
<script>
$(document).on('submit''#post-form', function (e) {
    e.preventDefault();
 
    $.ajax({
        type: 'POST',
        url: '{% url "accounts:ajax_login" %}',
        data: {
            useremail: $('#useremail').val(),
            password: $('#password').val(),
            csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
            action: 'post'
        },
        success: function (json) {
            if(json.data == 1){
                document.getElementById("post-form").reset();
                alert(json.message);
                window.location.href = '/';
                {#window.location.href = {% url 'index' %};#}
            } else if(json.data == 0){
                alert(json.message);
            }
        },
        error: function (xhr, errmsg, err) {
            alert('에러가 발생했습니다.');
            console.log(xhr.status + ": " + xhr.responseText); 
        }
    });
});
</script>
{% endblock %}
 

 

register.html

{% extends "base.html" %}
{% block title %}회원 가입{% endblock %}
{% block contents %}
    <div class="signup-form pt-5">
        <form method="post" id="post-form" action=".">
            {% csrf_token %}
            <h2 class="text-center">회원가입</h2>
            <div class="form-group">
                <div class="row">
                    <div class="col">
                        <input type="text" class="form-control" id="username" placeholder="사용자 이름" required="required" />
                    </div>
                </div>
            </div>
            <div class="form-group">
                <input type="email" class="form-control" id="useremail" placeholder="Email" required="required"/>
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="password" placeholder="비밀번호" required="required">
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="repasswd" placeholder="비밀번호 확인"
                       required="required">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-success btn-lg btn-block">회원 등록</button>
            </div>
        </form>
        <div class="text-center"><a href="{% url 'accounts:ajax_login' %}">로그인</a></div>
    </div>
    <script>
        $(document).on('submit''#post-form', function (e) {
            e.preventDefault();
            var pwd = $("#password");
            var repwd = $("#repasswd");
            if (PasswordChk(pwd, repwd) == falsereturn false;
 
            $.ajax({
                type: 'POST',
                url: '{% url "accounts:ajax_register" %}',
                data: {
                    username: $('#username').val(),
                    useremail: $('#useremail').val(),
                    password: $('#password').val(),
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    if (json.data == 1) {
                        document.getElementById("post-form").reset();
                        window.location.href = '/';
                        alert(json.message);
                    } else if (json.data == 0) {
                        alert(json.message);
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했네요.');
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
 
        function PasswordChk(pwd, repwd) {
            if (pwd.val() == '') {
                alert('비밀번호를 입력하세요');
                pwd.focus();
                return false;
            }
            if (pwd.val().indexOf(' '> -1) {
                alert("공백은 입력할 수 없습니다.");
                return false;
            }
 
            var check1 = /^(?=.*[a-zA-Z])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //영문,숫자
            var check2 = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9]).{8,15}$/.test(pwd.val()); //영문,숫자,특수문자
            var check3 = /^(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{10,15}$/.test(pwd.val()); //영문,특수문자
            var check4 = /^(?=.*[^a-zA-Z0-9])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //특수문자, 숫자
            if (!(check1 || check2)) {
                alert("영문/숫자 혼용시에는 10~15자리를 사용해야 합니다.\n영문/숫자/특수문자 혼용시에는 8~15자리를 사용해야 합니다.");
                return false;
            }
 
            // 동일한 문자/숫자 4자 이상
            if (/(\w)\1\1\1/.test(pwd.val())) { // /(\w)\1\1/.test(pwd)
                alert("같은 문자를 4번 이상 사용할 수 없습니다.");
                pwd.focus();
                return false;
            }
            if (isContinuedValue(pwd.val())) {
                alert("비밀번호에 4자 이상의 연속 문자 또는 숫자를 사용하실 수 없습니다.");
                pwd.focus();
                return false;
            }
 
            if (repwd.val() == '') {
                alert('비밀번호를 다시 한번 더 입력하세요');
                repwd.focus();
                return false;
            }
            if (pwd.val() !== repwd.val()) {
                alert('비밀번호를 둘다 동일하게 입력하세요');
                return false;
            }
            return true;
        }
 
        function isContinuedValue(value) {
            console.log("value = " + value);
            var intCnt1 = 0;
            var intCnt2 = 0;
            var temp0 = "";
            var temp1 = "";
            var temp2 = "";
            var temp3 = "";
 
            for (var i = 0; i < value.length - 3; i++) {
                temp0 = value.charAt(i);
                temp1 = value.charAt(i + 1);
                temp2 = value.charAt(i + 2);
                temp3 = value.charAt(i + 3);
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== 1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== 1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== 1) {
                    intCnt1 = intCnt1 + 1;
                }
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== -1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== -1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== -1) {
                    intCnt2 = intCnt2 + 1;
                }
            }
            return (intCnt1 > 0 || intCnt2 > 0);
        }
    </script>
{% endblock %}
 

'파이썬 > Django' 카테고리의 다른 글

Django Custom User Model - 회원가입, 로그인  (2) 2022.01.29
Django, squash migrations  (0) 2022.01.26
Python Django 회원가입 with jQuery(ajax)  (0) 2022.01.22
Python Django Login with jQuery(ajax)  (0) 2022.01.17
Django Login 예제  (0) 2022.01.16
블로그 이미지

Link2Me

,
728x90

Bootstrap UI에 회원가입시 에러메시지 처리 기능 대신에 jQuery Ajax 처리를 통해서 회원가입 방법을 테스트하고 적어둔다.

 

기존 views.py

from django.core.mail.backends import console
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from .models import User
 
# Create your views here.
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        username = request.POST.get('username',None)
        useremail = request.POST.get('useremail',None)
        password = request.POST.get('password',None)
        repasswd = request.POST.get('repasswd',None)
 
        res_data ={}
        if not (username and useremail and password and repasswd):
            res_data['error'= '모든 값을 입력해야 합니다.'
        elif password != repasswd:
            res_data['error'='비밀번호가 다릅니다.'
        else:
            user = User(
                username = username,
                useremail = useremail,
                password = make_password(password)
            )
            user.save()
 
        return render(request,'register.html',res_data)

 

회원 가입 UI 처리를 위해서 base.html 파일과 register.html 파일로 분리했다.

base.html

 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<div class="container">
    {% block contents %}
    {% endblock %}
</div>
</body>
</html>
 

 

 

register.html

장고 테이블에 회원정보를 저장하기 위해서는 name="username" 이라고 적어야 하지만, jQuery 로 처리하려고 id="usernmae" 으로 변경했다.

하단 <script></script> 사이에 Javascript 코드를 구현해야 한다. js 코드만 별도로 분리하는 것은 static 폴더에 넣으면 된다.

{% extends "base.html" %}
{% block contents %}
    <div class="signup-form pt-5">
        <form method="post" id="post-form" action=".">
            {% csrf_token %}
            <h2>회원가입</h2>
            <div class="form-group">
                <div class="row">
                    <div class="col">
                        <input type="text" class="form-control" id="username" placeholder="사용자 이름" required="required" />
                    </div>
                </div>
            </div>
            <div class="form-group">
                <input type="email" class="form-control" id="useremail" placeholder="Email" required="required"/>
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="password" placeholder="비밀번호" required="required">
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="repasswd" placeholder="비밀번호 확인"
                       required="required">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-success btn-lg btn-block">회원 등록</button>
            </div>
        </form>
        <div class="text-center"><a href="#">로그인</a></div>
    </div>
    <script>
        $(document).on('submit''#post-form'function (e) {
            e.preventDefault();
            if ($("#username").val() == '') {
                alert('이름을 입력하세요');
                $("#username").focus();
                return false;
            }
            if ($('#useremail').val() == '') {
                alert('email을 입력하세요');
                $('#useremail').focus();
                return false;
            }
 
            var pwd = $("#password");
            var repwd = $("#repasswd");
            if (PasswordChk(pwd, repwd) == falsereturn false;
 
            $.ajax({
                type: 'POST',
                url: '{% url "accounts:ajax_register" %}',
                data: {
                    username: $('#username').val(),
                    useremail: $('#useremail').val(),
                    password: $('#password').val(),
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    if (json.status == 1) {
                        document.getElementById("post-form").reset();
                        window.location.href = 'http://127.0.0.1:8000/';
                        alert('등록되었습니다.');
                    } else if (json.status == 0) {
                        alert('이미 등록된 회원입니다.');
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했네요.' + errmsg);
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
 
        function PasswordChk(pwd, repwd) {
            if (pwd.val() == '') {
                alert('비밀번호를 입력하세요');
                pwd.focus();
                return false;
            }
            if (pwd.val().indexOf(' '> -1) {
                alert("공백은 입력할 수 없습니다.");
                return false;
            }
 
            var check1 = /^(?=.*[a-zA-Z])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //영문,숫자
            var check2 = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9]).{8,15}$/.test(pwd.val()); //영문,숫자,특수문자
            var check3 = /^(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{10,15}$/.test(pwd.val()); //영문,특수문자
            var check4 = /^(?=.*[^a-zA-Z0-9])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //특수문자, 숫자
            if (!(check1 || check2)) {
                alert("영문/숫자 혼용시에는 10~15자리를 사용해야 합니다.\n영문/숫자/특수문자 혼용시에는 8~15자리를 사용해야 합니다.");
                return false;
            }
 
            // 동일한 문자/숫자 4자 이상
            if (/(\w)\1\1\1/.test(pwd.val())) { // /(\w)\1\1/.test(pwd)
                alert("같은 문자를 4번 이상 사용할 수 없습니다.");
                pwd.focus();
                return false;
            }
            if (isContinuedValue(pwd.val())) {
                alert("비밀번호에 4자 이상의 연속 문자 또는 숫자를 사용하실 수 없습니다.");
                pwd.focus();
                return false;
            }
 
            if (repwd.val() == '') {
                alert('비밀번호를 다시 한번 더 입력하세요');
                repwd.focus();
                return false;
            }
            if (pwd.val() !== repwd.val()) {
                alert('비밀번호를 둘다 동일하게 입력하세요');
                return false;
            }
            return true;
        }
 
        function isContinuedValue(value) {
            console.log("value = " + value);
            var intCnt1 = 0;
            var intCnt2 = 0;
            var temp0 = "";
            var temp1 = "";
            var temp2 = "";
            var temp3 = "";
 
            for (var i = 0; i < value.length - 3; i++) {
                temp0 = value.charAt(i);
                temp1 = value.charAt(i + 1);
                temp2 = value.charAt(i + 2);
                temp3 = value.charAt(i + 3);
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== 1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== 1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== 1) {
                    intCnt1 = intCnt1 + 1;
                }
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== -1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== -1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== -1) {
                    intCnt2 = intCnt2 + 1;
                }
            }
            return (intCnt1 > 0 || intCnt2 > 0);
        }
    </script>
{% endblock %}
 

 

 

models.py

models.py 파일은 데이터베이스 테이블을 만들고 그 안의 필드들을 생성 및 수정할 수 있는 역할을 한다.
혹시라도 칼럼을 추가할 경우에는 DB 를 삭제하고 다시 하는 것이 속 편하다. 그러므로 처음부터 칼럼 설계를 잘하자.
# models.py
from django.db import models
 
# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=32, verbose_name='사용자명',default='')
    useremail = models.EmailField(max_length=128, verbose_name='emailID',default='')
    password = models.CharField(max_length=128,verbose_name='비밀번호')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='등록시간')
    updated_at = models.DateTimeField(auto_now=True)
 
    def __str__(self):
        return self.email
 
    class Meta:
        db_table = 'accounts'
        verbose_name = '사용자'
        verbose_name_plural = '사용자'
 

 

수정 views.py

from django.core.mail.backends import console
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from .models import User
 
# Create your views here.
def home(reqest):
    user_id = reqest.session.get('user')
 
    if user_id:
        user = User.objects.get(pk=user_id)
        return HttpResponse(user.useremail)
 
    return HttpResponse('Home')
 
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        username = request.POST.get('username',None)
        useremail = request.POST.get('useremail',None)
        password = request.POST.get('password',None)
 
        res_data ={}
        try:
            user = User.objects.get(useremail=useremail)
            if user:
                res_data['status'= '0' # 기존 가입된 회원
                return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            user = User(
                username = username,
                useremail = useremail,
                password = make_password(password)
            )
            user.save()
            # session 생성
            user = User.objects.get(useremail=useremail)
            request.session['user'= user.id
            res_data['status'= '1' # 회원 가입 완료
            return JsonResponse(res_data)
 
        return render(request,'register.html',res_data)
 
 
def logout(request):
    if request.session.get('user'):
        del(request.session['user'])
 
    return redirect('/user/login')
 
 

 

 

app urls.py

# app urls.py
from django.urls import path
from . import views
 
app_name = 'accounts'
urlpatterns = [
    path('register/', views.register, name='ajax_register'), # name = that we will use in ajax url
    path('login/', views.login, name='ajax_login'),
    path('logout/',views.logout),
]
 
 

 

project urls.py

# project urls.py
from django.contrib import admin
from django.urls import path,include
from accounts.views import home
 
urlpatterns = [
    path('admin/', admin.site.urls),
 
    # path('', views.HomeView.as_view(), name='home'),
    path('user/', include('accounts.urls')),
    path('', home)
]
 

 

admin.py

# admin.py
from django.contrib import admin
from .models import User
 
# Register your models here.
class UserAdmin(admin.ModelAdmin):
    list_display = ('username','useremail','password','created_at','updated_at')
 
admin.site.register(User,UserAdmin)
블로그 이미지

Link2Me

,
728x90

Python 에서 SQLite3 데이터베이스 연동을 처리하는 예제이다.

SQLite DB 테이블 생성시 unique 인덱스 설정과 DB 데이터 추가시 중복 저장될 경우 처리를 고려하여 테스트했다.

 

import datetime
import sqlite3
 
def query_sqlite(input_query):
    import sqlite3
 
    # DB 생성 및 Auto Commit
    dbconn = sqlite3.connect('database.db', isolation_level=None)
    cur = dbconn.cursor()  # 커서 획득
    cur.execute(input_query)   # SQL 쿼리 실행
 
    # 데이타 Fetch
    rows = cur.fetchall()
    for row in rows:
        print(row)
 
    dbconn.close()  # Connection 닫기
 
 
 
# 날짜 삽입 과정
now = datetime.datetime.now()
print('now :', now)
 
nowDatetime = now.strftime('%Y-%m-%d %H:%M:%S')
print('nowDatetime :', nowDatetime)
 
print('sqlite3.version :', sqlite3.version)
print('sqlite.sqlite_version :', sqlite3.sqlite_version)
 
# DB 생성 및 Auto Commit
conn = sqlite3.connect('database.db', isolation_level=None)
 
# 커서 획득
= conn.cursor()
 
# 테이블 생성(Data Type : TEXT, INTEGER, REAL, BLOB, NUMERIC)
# 컬럼에 UNIQUE 제약 조건을 설정하면 대상의 컬럼에 중복 된 값이 저장될 수 없다.
sql = 'CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, username text, email text not null unique , phone text, regdate text)'
c.execute(sql)
 
# 여러 컬럼의 조합에 UNIQUE 제약 조건 설정
# CREATE TABLE 테이블명 (컬럼명1, 컬럼명2, ... , UNIQUE (컬럼명1, 컬럼명2, ...));
# create table users (id INTEGER PRIMARY KEY, username text, email text unique , phone text, regdate text, unique (id, email));
 
# 데이터 삽입
varlist = [2'이방원''bwlee@naver.com''010-0001-0002']
print(varlist)
varlist.append(nowDatetime)
print(varlist)
 
# 동일 데이터가 들어오면 저장하지 말라는 옵션 추가
# 일반 Query : INSERT INTO
var_string = ', '.join('?' * len(varlist))
query_string = 'INSERT OR IGNORE INTO users VALUES (%s);' % var_string
print(query_string)
c.execute(query_string, varlist)
 
# SQL 쿼리 실행
query_sqlite('select * from users')
 
# 다량의 데이터 추가
userList = (
    (3,'박창근''park@naver.com''010-0001-0003',nowDatetime),
    (4,'이원빈''wblee@naver.com''010-0001-0004',nowDatetime),
    (5,'장길산''gsjang@naver.com''010-0001-0005',nowDatetime),
)
 
sql = "INSERT OR IGNORE INTO users(id, username, email, phone, regdate) VALUES(?,?,?,?,?)"
c.executemany(sql,userList)
conn.close()
 
# sqlite3 에서는 ?   mariaDB 에서는 %s
 
 
# SQL 쿼리 실행
query_sqlite('select * from users')

 

 

블로그 이미지

Link2Me

,
728x90

MariaDB 에서 SQL 데이터를 읽어서 Pandas DataFrame 에 출력하는 코드이다.

mariadb 와 mysql 둘 모두 테스트 결과 잘 읽어온다.

 

# pip install pymysql 또는 pip install mariadb
# pip install numpy scipy matplotlib ipython scikit-learn pandas pillow imageio
 
import pandas as pd
 
def query_mariadb(query):
    import pandas as pd
    import pymysql
    import mariadb
 
    # Connect to MariaDB (mariadb.connect 대신 pymysql.connect 로 해도 된다)
    dbconn = mariadb.connect(
        user="root",
        password="autoset",
        host="localhost",
        port=3306,
        database="python_sample"
    )
 
    # dbconn = mydb.cursor()  # 이 명령어는 불필요.
    # mariaDB Query to Pandas DataFrame
    query_result= pd.read_sql(query,dbconn)
    dbconn.close()
 
    return query_result
 
 
if __name__ == '__main__':
    sql = "SELECT * FROM cate"
    df = query_mariadb(sql)
    print(df)
 
 

 

앞 게시글에서 저장했던 SQL 데이터를 출력하는 것이라서 결과는 아래와 같다.

'파이썬 > 데이터 분석' 카테고리의 다른 글

Python Pandas CSV 읽고 DB 저장  (0) 2022.01.19
Python Pandas 기초 학습  (0) 2022.01.18
Pycharm과 Jupyter Notebook 연결하기  (0) 2022.01.12
블로그 이미지

Link2Me

,