728x90

윈도우즈에서 관리자 권한으로 터미널 창을 여는 방법이다.

1. Windows 키 + R 을 눌러 실행창을 연다.

2. cmd 를 입력

3. Ctrl + Shift + Enter를 눌러 관리자 권한으로 실행한다.

 

윈도우 터미널 창에서 pip install --upgrade pip 명령어를 실행해도 안된다.

 

아래와 같이 curl을 이용해 다운받고,
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py

아래 명령을 실행해 설치 할 수 있다.
python get-pip.py

 

 

관리자 권한에서 유투브 번역 스크립트 API를 install 했더니 정상 동작한다.

 

 

728x90
블로그 이미지

Link2Me

,
728x90

Pytho 3.11.9 버전 설치하면서 숱한 에러 끝에 성공하고 적어둔다.

스크립트는 root권한에서 설정하였고 그냥 복사 붙여넣기 편하게 작성한 것이다.

root 권한에서는 #으로 된 것은 주석이다.

 

아래 설치 스크립트를 파일로 첨부하니 필요한 분은 받아서 활용하세요.

CentOS7 Python 3.11.9.txt
0.01MB

 

 

 
####################################################
### curl update
####################################################
 
# CentOS 7.X 에서 curl -V 를 하면 7.29 버전이 설치되어 있다.
# 아래 코드는 트랜드마이크로 서버 백신 설치를 위해서 필요한 옵션이다.
curl --version
# curl 7.29.0 (x86_64-redhat-linux-gnu)
vi /etc/yum.repos.d/city-fan.repo
[CityFan]
name=City Fan Repo
baseurl=http://www.city-fan.org/ftp/contrib/yum-repo/rhel$releasever/$basearch/
enabled=1
gpgcheck=0
 
저장하고 나온다.:wq
 
yum -y install epel-release
yum -y install curl libcurl
curl --version
 
########################################################################################
#### SQLite 설치
########################################################################################
# sqlite RPM 다운로드 방법
# https://kojipkgs.fedoraproject.org//packages/sqlite/ 에 접속하여 확인한다.
# Python 버전이 높으면 SQLite 버전도 높은 버전을 요구한다. rpm 높은 버전 설치 실패해서 포기했다.
 
# 먼저 3.10.2 버전으로 rpm 업데이트를 한다.
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 –version
.quit
 
cd /root
rm -rf sqlite3/
 
# 3.36 버전 설치
# 1. 3.36 버전 Download 및 설치
cd /root/
sudo wget https://sqlite.org/2021/sqlite-autoconf-3360000.tar.gz
sudo tar -zxf sqlite-autoconf-3360000.tar.gz
cd sqlite-autoconf-3360000/
./configure --prefix=/usr/local/sqlite
make
sudo make install
 
# /usr/local/sqlite 폴더가 생성된 것을 확인할 수 있다.
# 3.36 다운로드 버전 삭제
cd /root
rm -rf sqlite-autoconf-3360000/
rm -rf sqlite-autoconf-3360000.tar.gz
 
# 2. bashrc 에 영구 저장하기
cd /root
vi .bashrc
alias sqlite3=/usr/local/sqlite/bin/sqlite3
export LD_LIBRARY_PATH=/usr/local/sqlite/lib:${LD_LIBRARY_PATH}
 
:wq
 
source .bashrc
 
# 버전 확인
sqlite3 –version
.quit
# .quit 는 종료 명령어
 
# 수동으로 빌드설치한 버전은 prefix=/usr/local/sqlite 를 참조하여 
# /usr/local/sqlite/bin/sqlite3, /usr/local/sqlite/lib/libsqlite3* 에 존재한다.
# LD_LIBRARY_PATH 를 지정후 실행하면 우리가 빌드한 버전을 바라보게 된다.
# 서버를 재부팅하면 설정값이 날라가기 때문에 반드시 .bashrc 에 반영하자.
export LD_LIBRARY_PATH=/usr/local/sqlite/lib:$LD_LIBRARY_PATH
 
#########################################################
CentOS7 파이썬 설치
############ yum ########################################
# Python3.X를 설치하면 yum 명령어가 먹히지 않을 수 있다. 이를 방지하기 위해서 아래와 같이 변경해줘야 한다.
vi /usr/bin/yum
첫줄에 python 을 python2.7 로 변경하고 저장(:wq)한다.
 
vi /usr/libexec/urlgrabber-ext-down
첫줄에 python 을 python2.7 로 변경하고 저장(:wq)한다.
 
##########################################################
#1. Repository 변경
yum -y install epel-release
yum -y install wget make cmake gcc bzip2-devel libffi-devel zlib-devel
sudo yum -y groupinstall "Development Tools"
yum -y install zip unzip mc git net-tools
 
#2. openssl 설치
# openssl version 을 확인시 OpenSSL 1.1.1t  7 Feb 2023 이면 이미 최신으로 설치되었으니 다음단계로 넘어간다.
 
openssl version
cd /root
sudo yum -y remove openssl openssl-devel
openssl version
wget https://www.openssl.org/source/openssl-1.1.1t.tar.gz
tar xvf openssl-1.1.1t.tar.gz
cd openssl-1.1.1t/
./config --prefix=/usr/local/openssl --openssldir=/usr/local/openssl
make
sudo make install
sudo ldconfig
 
sudo tee /etc/profile.d/openssl.sh<<EOF
export PATH=/usr/local/openssl/bin:\$PATH
export LD_LIBRARY_PATH=/usr/local/openssl/lib:\$LD_LIBRARY_PATH
EOF
 
source /etc/profile.d/openssl.sh
 
 
# which openssl
/usr/local/openssl/bin/openssl
 
openssl version
OpenSSL 1.1.1t  7 Feb 2023
 
 
# 3. Python 설치
# https://www.python.org/ 에서 최신버전을 확인하여 최신버전으로 숫자를 변경한다.
# 완전 최신버전 보다는 약간 낮은 버전으로 설치했다.
cd /root
wget https://www.python.org/ftp/python/3.11.9/Python-3.11.9.tgz
tar -zxvf Python-3.11.9.tgz
cd Python-3.11.9
LDFLAGS="${LDFLAGS} -Wl,-rpath=/usr/local/openssl/lib" ./configure --with-openssl=/usr/local/openssl --enable-loadable-sqlite-extensions
make
sudo make altinstall
 
# 4. bashrc 에 영구 저장하기
cd /root
vi .bashrc
alias python3='/usr/local/bin/python3.11'
alias python='/usr/local/bin/python3.11'
alias pip='/usr/local/bin/pip3.11'
 
:wq (저장)하고 빠져나온다.
 
source .bashrc
 
# 5. 설치 확인
# 만약 버전이 3.10.2 로 나온다면 제대로 설치된 것이 아니다.
python3 -"import sqlite3; print(sqlite3.sqlite_version)"
 
python
>>> import sqlite3
>>> sqlite3.sqlite_version
 
Use exit() or Ctrl-D (i.e. EOF) to exit
>>> Ctrl-D
 
>>> import ssl
>>> ssl.OPENSSL_VERSION
 
python3 -V
 
# 6. 심볼링 링크 설정
sudo ln -s python3.11 python3
 
########################################################################################
# mysqlclient 설치 시점에 따라 버전업이 되어 있을 수 있다.
# mysqlclient-2.2.4.tar.gz 설치
# mariadb-devel 을 먼저 제거해준다.
# yum remove -y mariadb-devel
yum remove -y mariadb-devel.x86_64
 
# mysql Community Downloads
yum -y install https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm
 
# mysql-devel 설치
yum -y install mysql-community-devel.x86_64
 
########################################################################################################
# 방화벽 설정
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 --reload
firewall-cmd --list-all
 
################################
##### MariaDB 10.5 버전 설치 #####
################################
 
vi /etc/yum.repos.d/MariaDB.repo
# MariaDB 10.5 CentOS repository list
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
 
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 입력 후 엔터
Root 비밀번호 입력
Anonymous User 삭제 : Y 입력후 엔터
원격 root 로그인 불가 설정 : Y 입력후 엔터
Test DB 삭제 : Y 입력후 엔터
변경사항 반영 : Y 입력 후 엔터
 
# 서버 환경 설정
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 재시작
systemctl restart mariadb
 
# MaraiDB 접속 및 설정 확인
mysql -u root -p
status
quit
 
#######################################################################
####### DB 생성 및 User 권한 등록 예시
#######################################################################
create database pythondb default character set utf8;
 
use mysql;
create user codefox@localhost identified by 'foxfullwonder!#';
grant all privileges on pythondb.* to codefox@localhost;
flush privileges;
exit;
 
// DB 생성
use pythondb;
 
// 테이블 밀어넣기
source pythondb.sql; -- DB read
 
// 테이블 백업
mysqldump -uroot ---databases pythondb > pythondb.sql
 
########################################################################################
# Python 환경 설정
mkdir -/home/httpd/python/
cd /home/httpd/python/
python3 -m pip install virtualenv
python3 -m pip install --upgrade pip setuptools wheel
 
# django 생성 명렁어라서 다시 실행하면 이미 있다고 나옴.
virtualenv venv
cd venv
 
# 가상환경 활성화
source /home/httpd/python/venv/bin/activate
 
# 가상환경 활성화 상태에서 설치
pip install django
# 설치된 장고 버전 확인
django-admin --version
 
pip install djangorestframework
pip install mysqlclient
 
# 파이썬 디버깅 도구 설치
pip install pylint
pip install twisted
 
# 가상환경 비활성화
deactivate
 
########################################################################################
# 장고 프로젝트 생성 및 Web 서버 구동 테스트
source /home/httpd/python/venv/bin/activate
cd /home/httpd/python/
# manage.py 파일 생성
# django-admin startproject 프로젝트명
django-admin startproject userModel
cd userModel
 
#chmod 755 manage.py
 
# makemigrations 를 하기전에 반드시 sqlite 3.27 이상 버전이 설치되어 있어야 한다.
# MariaDB 로 설정 값을 변경했다면 MariaDB가 설치되어 있어야 한다. 아래 MariaDB 파트를 참조하여 설치한다.
python manage.py makemigrations
python manage.py migrate
 
# 관리자 User 등록
python manage.py createsuperuser
 
# Python 장고 동작여부 확인 : Web 브라우저로 접속되어야 한다.
python manage.py runserver 0.0.0.0:8000
 
 
# 설치된 장고 프로젝트에 app 추가 방법
python manage.py startapp accounts
python manage.py startapp product
python manage.py startapp order
 
 
########################################################################################################
# Google Chrome 설치
yum -y install https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
 
# 설치된 구글 크롬 버전 확인
google-chrome --version
 
########################################################################################################
 

 

 

728x90
블로그 이미지

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 브라우저에서 잘 동작되는 걸 확인할 수 있다.

 

 

 

 

 

 

 

 

728x90
블로그 이미지

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
 

 

728x90
블로그 이미지

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 설치 부분을 고려해줘야 한다.

 

728x90
블로그 이미지

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)
 

 

 

728x90

'파이썬 > 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])
 

 

 

728x90

'파이썬 > 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)),
]

 

 

 

 

728x90
블로그 이미지

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) : 모델에 정의한 필드를 참조해서 모델 폼을 만드는 역할을 한다.

 

 

728x90
블로그 이미지

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 클래스의 함수 코드 일부를 삭제했으나, 공부해서 충분히 해결할 수 있을 것으로 본다.

728x90
블로그 이미지

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 코드를 추가해야 한다.

 

 

 

 

728x90
블로그 이미지

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

 

728x90
블로그 이미지

Link2Me

,
728x90

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

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

 

728x90
블로그 이미지

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 %}
 
728x90

'파이썬 > 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)
728x90
블로그 이미지

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')

 

 

728x90
블로그 이미지

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 데이터를 출력하는 것이라서 결과는 아래와 같다.

728x90

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

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

Link2Me

,
728x90

판다스를 이용하면 대용량의 데이터도 쉽게 읽어서 분석할 수 있다.

 

 

https://www.data.go.kr/ 에서  샘플 데이터 가져오기

 

 

 

다운로드 받은 파일이 압축파일이며, 내용을 보면 아래와 같이  csv 파일로 되어 있다.

서울 파일을 영문이름으로 변경했다.

 

이제, 파이참(PyCharm)에서 코드를 실행해 본다.

https://www.w3schools.com/python/pandas/default.asp 에 기본적인 코드 사용법이 잘 나와있다.

 

 

 

import pandas as pd
 
# data = pd.read_csv('soho_seoul.csv', header=None) # KeyError: '상권업종대분류명'
df = pd.read_csv('soho_seoul.csv'# KeyError: '상권업종대분류명'
 
## Fastest would be using length of index
print("전체 행 : "len(df.index))
 
## If you want the column and row count then
row_count, column_count = df.shape
print("총 행(row) 수 : ", row_count)
print("총 칼럼(열) 수 : ", column_count)
 
# 데이터 셋 df의 종합적인 정보는 df.info( ) 함수를 통해 확인 가능
# Dtype의 int64는 정수, object는 문자열, float64는 실수를 의미
print(df.info())
 
# 데이터 프레임에서 결측치를 True, 값이 있으면 False를 반환
print(df.isnull())
 
# 각 column들이 몇개의 null값을 가졌는지 확인
is_null = df.isnull().sum()
print(is_null)
 

 

jupyter notebook 에서 df.info() 를 한 결과 화면이다.

 

 

열의 칼럼이 39개나 되어서 출력 결과가 보기 좋지 않아 칼럼 일부만 발췌해서 출력하는 걸 테스트했다.

 

# 위에서부터 지정된 개수만큼 출력하기
df_head = df.head(10# 개수를 지정하지 않으면 기본 5개 출력
print(df_head)
 
# 3 ~ 6번째 데이터 출력, 칼럼(열)은 10번째 열까지 출력
print(df.iloc[2:7,:10])
 
# 열 여러개 선택하기
new_df = df.head(20)[['상호명','상권업종대분류명','상권업종중분류명','상권업종소분류명','표준산업분류명','시군구명','행정동명','건물명']]
print(new_df)
 
# 맨 하단 10개 데이터를 칼럼(열) 10개만 출력
print(df.tail(10).iloc[:,:10])
 
print(df.tail(10)[['상가업소번호','상호명','상권업종대분류명','상권업종중분류명','상권업종소분류명','표준산업분류명','시군구명','행정동명','건물명']])
 
 

 

 

특정 칼럼 중복 제거 및 DB에 데이터 저장

# 특정 칼럼 중복 제거
item = df['상권업종대분류명'].drop_duplicates()
print(item)
 
# LIST로 변환
item_list = item.values.tolist()
print(item_list)
 
# 가나다순 정렬
varlist = sorted(item_list)
print(varlist)
 
# LIST to DB Insert
 

 

 

판다스에서 읽은 데이터를 mariaDB에 저장 테스트 목적으로 샘플 테이블을 생성했다.

다중 카테고리로 사용하는 테이블이다.

CREATE TABLE cate (
  `id` int(11NOT NULL,
  `parent_id` int(11NOT NULL DEFAULT 0,
  `depth` int(3NOT NULL DEFAULT 1,
  `name` varchar(50NOT NULL,
  `text` varchar(50DEFAULT NULL
ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
ALTER TABLE cate
  ADD PRIMARY KEY (`id`);
 
ALTER TABLE cate
  MODIFY `id` int(11NOT NULL AUTO_INCREMENT;
COMMIT;
 

 

데이터 무결성(중복 저장 방지)을 위해서 다중칼럼 UNIQUE 인덱스를 추가했다.

parent_id, depth, name 칼럼이 마치 1개의 칼럼처럼 동작하여 동일한 자료가 업로드되면 추가되지 않는다.

보통 userID 가 중복되지 않도록 1개의 칼럼에 UNIQUE 인덱스를 설정하는데, 데이터가 중복 저장되지 않도록 하기 위한 모든 칼럼에 다중 UNIQUE INDEX를 설정하면 중복 저장을 방지할 수 있다.

 

ALTER TABLE cate
  ADD UNIQUE KEY parentid_name (parent_id,depth,name);
 

 

DB에 데이터 Insert 과정의 소스 코드

csv 파일의 데이터를 읽어서 원하는 칼럼의 자료를 선택하고 중복된 값을 제거하고 DB에 카테고리화를 위한 데이터 저장 목적으로 코드를 연습해 본 것이다.

# pip install PyMySQL # mysql 연동시
# pip install mariadb
# pip install numpy scipy matplotlib ipython scikit-learn pandas pillow imageio
 
import pandas as pd
import mariadb
import sys
 
# Connect to MariaDB Platform
try:
    mydb = mariadb.connect(
        user="root",
        password="autoset",
        host="localhost",
        port=3306,
        database="python_sample"
 
    )
except mariadb.Error as e:
    print(f"Error connecting to MariaDB Platform: {e}")
    sys.exit(1)
 
# Get Cursor
dbconn = mydb.cursor()
 
 
# data = pd.read_csv('soho_seoul.csv', header=None) # KeyError: '상권업종대분류명'
df = pd.read_csv('soho_seoul.csv') # KeyError: '상권업종대분류명'
 
## Fastest would be using length of index
print("전체 행 : ", len(df.index))
 
## If you want the column and row count then
row_count, column_count = df.shape
print("총 행(row) 수 : ", row_count)
print("총 칼럼(열) 수 : ", column_count)
 
# 특정 칼럼 중복 제거
item = df['상권업종대분류명'].drop_duplicates()
print(item)
 
# LIST로 변환
item_list = item.values.tolist()
print(item_list)
 
# 가나다순 정렬
varlist = sorted(item_list)
print(varlist)
 
# 배열 사이즈 구하기
print(len(varlist))
 
# 배열 사이즈만큼 동일한 값 초기화, 두가지 방법 모두 가능
parent_id = [0 for i in range(len(varlist))]
depth = [1* len(varlist)
 
# Series 생성 및 DataFrame 전환 <== mariaDB 입력 데이터 생성 목적
df_parentid = pd.Series(parent_id)
df_depth = pd.Series(depth)
df_name = pd.Series(varlist)
 
df_all = pd.concat([df_parentid, df_depth, df_name, df_name], axis=1)
print(df_all)
 
# DataFrame to List
db_itemlist = df_all.values.tolist()
print(db_itemlist)
 
# LIST to mariaDB Insert
sql = "INSERT INTO cate(parent_id,depth,name,text) VALUES(%s,%s,%s,%s)"
dbconn.executemany(sql,db_itemlist)
 
 

 

 

DB에 잘 저장되었는지 여부를 확인해 보자.

 

 

파일을 다시 한번 더 실행하면 아래와 같은 에러 메시지를 출력한다.

 

위와 같은 메시지가 출력되는 걸 방지하고 싶다면, INSERT IGNORE INTO 로 변경하면 된다.

중복하려는 자료가 있다면 기존 자료를 유지하고, 새로운 자료는 무시하라는 명령어이다.

 

# LIST to mariaDB Insert
sql = "INSERT IGNORE INTO cate(parent_id,depth,name,text) VALUES(%s,%s,%s,%s)"
dbconn.executemany(sql,db_itemlist)
dbconn.close()
 

 

 

728x90

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

MariaDB to Python Pandas DataFrame  (0) 2022.01.21
Python Pandas 기초 학습  (0) 2022.01.18
Pycharm과 Jupyter Notebook 연결하기  (0) 2022.01.12
블로그 이미지

Link2Me

,
728x90

pandas란 엑셀처럼 테이블(표)로 된 데이터를 다룰 때 아주 유용한 도구이다.

판다스는 데이터 분석용 언어인 R의 data.frame 구조를 본뜬 DataFrame이라는 구조를 사용하기 때문에, R의 data.frame의 기능들을 대부분 사용할 수 있도록 만들어졌다.

index 를 제외한 각 열(Column)을 Series라고 부른다.

각 열 단위(Series)가 모여 하나의 표를 DataFrame 이라고 부른다.

 

 

아래 코드는 jupyter notebook 환경에서 실습하면 된다.

https://link2me.tistory.com/2094  게시글에 라이브러리 설치와 Jupyter notebook 설치하는 방법이 기술되어 있다.

 

index 를 내가 원하는 형식으로 변경할 수 있다.

import pandas as pd
# Series 는 인덱스와 값으로 구성된다.
 
array = pd.Series(['사과''바나나''당근'], index=['a','b','c'])
 
print(array)
print()
 
# index 출력하기
for i in array.index:
    print(i)
    
print()
 
# value 출력하기
for val in array.values:
    print(val)

 

import pandas as pd
data = {
    'a''사과',
    'b''바나나',
    'c''당근'
}
 
# Dict 자료형을 Series로 바꾸기
array = pd.Series(data)
 
print(array)
 

 

import pandas as pd
 
# 데이터 프레임 : 다수의 시리즈(Series)를 모아 처리하기 위한 목적으로 사용
# Dict 자료형
word_dict = {
    'Apple''사과',
    'Banana''바나나',
    'Carrot''당근'
}
 
frequency_dict = {
    'Apple'3,
    'Banana'5,
    'Carrot'7
}
 
word = pd.Series(word_dict)
frequency = pd.Series(frequency_dict)
 
# name : value
summary = pd.DataFrame({
    'word': word,
    'frequency': frequency
})
 
display(summary)
 
 

 

import pandas as pd
 
# Series 끼리 사칙연산이 가능하다. 사칙연산을 한 결과는 또 다른 Series이다.
 
word_dict = {
    'Apple''사과',
    'Banana''바나나',
    'Carrot''당근'
}
 
frequency_dict = {
    'Apple'3,
    'Banana'5,
    'Carrot'7
}
 
importance_dict = {
    'Apple'3,
    'Banana'2,
    'Carrot'1
}
 
word = pd.Series(word_dict)
frequency = pd.Series(frequency_dict)
importance = pd.Series(importance_dict)
 
summary = pd.DataFrame({
    'word': word,
    'frequency': frequency,
    'importance': importance
})
 
 
score = summary['frequency'* summary['importance']
summary['score'= score
 
display(summary)
 

 

DataFrame 의  칼럼(열) 순서를 바꿔보고 싶다면....

import pandas as pd
 
# 데이터 프레임의 슬라이싱
 
word_dict = {
    'Apple''사과',
    'Banana''바나나',
    'Carrot''당근',
    'Durian''두리안'
}
 
frequency_dict = {
    'Apple'3,
    'Banana'5,
    'Carrot'7,
    'Durian'2
}
 
importance_dict = {
    'Apple'3,
    'Banana'2,
    'Carrot'1,
    'Durian'1
}
 
word = pd.Series(word_dict)
frequency = pd.Series(frequency_dict)
importance = pd.Series(importance_dict)
 
summary = pd.DataFrame({
    'word': word,
    'frequency': frequency,
    'importance': importance
})
 
display(summary)
 
# DataFrame 의 칼럼(열) 순서를 바꿔보기
df = pd.DataFrame(summary, columns=['word''importance''frequency'])
display(df)
 
# 이름을 기준으로 슬라이싱
display(summary.loc['Banana':'Carrot''importance':])
 
 
# 인덱스를 기준으로 슬라이싱
display(summary.iloc[1:32:])
 

 

 

# 데이터 프레임의 연산
import pandas as pd
 
word_dict = {
    'Apple''사과',
    'Banana''바나나',
    'Carrot''당근',
    'Durian''두리안'
}
 
frequency_dict = {
    'Apple'3,
    'Banana'5,
    'Carrot'7,
    'Durian'2
}
 
importance_dict = {
    'Apple'3,
    'Banana'2,
    'Carrot'1,
    'Durian'1
}
 
word = pd.Series(word_dict)
frequency = pd.Series(frequency_dict)
importance = pd.Series(importance_dict)
 
summary = pd.DataFrame({
    'word': word,
    'frequency': frequency,
    'importance': importance
})
 
display(summary)
 
summary.loc['Apple''importance'= 9 # 데이터의 변경
summary.loc['Elderberry'= ['엘더베리'53# 새 데이터 삽입
 
display(summary)
 
print()
# 끝에서 마지막 2줄을 불러온다. tail() 로 하면 마지막 5개를 불러온다.
display(summary.tail(2))
 
print()
# 맨 처음부터 2줄을 불러온다.
display(summary.head(2))
 
# 데이터의 대략적인 통계적 정보 요약을 보여준다.
display(summary.describe())
 
 

 

# 엑셀로 내보내기/불러오기
import pandas as pd
 
word_dict = {
    'Apple''사과',
    'Banana''바나나',
    'Carrot''당근'
}
 
frequency_dict = {
    'Apple'3,
    'Banana'5,
    'Carrot'7
}
 
word = pd.Series(word_dict)
frequency = pd.Series(frequency_dict)
 
summary = pd.DataFrame({
    'word': word,
    'frequency': frequency
})
 
 
summary.to_csv("summary.csv", encoding="utf-8-sig")
# csv 파일 형태로 저장한 데이터는 텍스트 파일 형태
 
# csv 파일 엑셀 읽어오기 및 화면 출력
saved = pd.read_csv("summary.csv", index_col=0)
display(saved)
 
 

 

 

728x90

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

MariaDB to Python Pandas DataFrame  (0) 2022.01.21
Python Pandas CSV 읽고 DB 저장  (0) 2022.01.19
Pycharm과 Jupyter Notebook 연결하기  (0) 2022.01.12
블로그 이미지

Link2Me

,
728x90

장고 온라인 강좌 로그인 처리가 매끄럽지 못한 거 같아서 jQuery ajax 로그인 처리 기능을 테스트하고 적어둔다.

 

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>로그인</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>
</head>
<body>
<div class="container pt-5">
    <div class="login-form">
        <div class="row">
            <div class="col-12">
                {{ error }}
            </div>
        </div>
        <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 입력">
            </div>
            <div class="form-group">
                <input type="password" id="password" class="form-control" placeholder="비밀번호 입력">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-primary btn-block">로그인</button>
            </div>
        </form>
        <p class="text-center"><a href="#" id="register">회원 가입</a></p>
    </div>
</div>
</body>
<script>
    $(document).on('submit''#post-form'function (e) {
        e.preventDefault();
        if($('#useremail').val() == ''){
            alert('email을 입력하세요');
            $('#useremail').focus();
            return false;
        }
 
        if($('#password').val() == ''){
            alert('비밀번호를 입력하세요');
            $('#password').focus();
            return false;
        }
 
        $.ajax({
            type: 'POST',
            url: '{% url "user:ajax_login" %}',
            data: {
                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); 
            }
        });
    });
</script>
</html>
 

 

 

views.py

완벽 동작하도록 로직을 수정했지만, Secure Coding까지 고려하지는 않은 상태다.

로그인에서 비밀번호가 일치하지 않거나, 아이디가 없거나 할 경우에 동일한 메시지를 출력해야 한다.

 

 
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 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['status'= '1'
            else:
                res_data['status'= '0'
            return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            res_data['status'= '0'
            return JsonResponse(res_data)
 
        return render(request, 'login.html', res_data)
 
 
def logout(request):
    if request.session.get('user'):
        del(request.session['user'])
 
    return redirect('/user/login')
 
 

 

 

urls.py

from django.urls import path
from . import views
 
app_name = 'user'
urlpatterns = [
    path('register/', views.register, name='ajax_register'),
    path('login/', views.login, name='ajax_login'),
    path('logout/', views.logout),
]
 
 

 

참고자료

https://dev.to/thepylot/how-to-send-django-form-with-ajax-4bpo

 

How to send Django form with AJAX

What's up DEV Network? In this quick tutorial I am going to show you how to POST Django form without...

dev.to

 

728x90
블로그 이미지

Link2Me

,