728x90

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

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

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

 

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

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

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

 

1. 선행 작업사항

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

 

2. Python 설치 과정

 

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

 

3. MaraiDB 10.5 설치

 

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

 

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

 

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

 

 

파일 위치 찾기

 

개발 서버 구동

 

 

환경설정 파일

python_conf.zip
0.10MB

 

 

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

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

 

 

 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

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

 

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

 

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

 

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

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

 

블로그 이미지

Link2Me

,
728x90

테이블 설계 예시이다.

중복 체크를 하기 위해서 UNIQUE KEY 설정을 3개의 칼럼을 JOIN 으로 해서 하나의 KEY로 설정했다.

 

Engine 은 MyISAM 으로 한 이유는 Legacy PHP 로 코딩하는 경우, InnoDB 엔진으로 설정하면 원치 않는 에러 발생시 해결하기 난감한 경험을 겪어서이다.

CREATE TABLE voc (
  idx int(11NOT NULL,
  userID varchar(20NOT NULL COMMENT '고객ID',
  voctype varchar(100NOT NULL COMMENT 'VOC유형',
  vocCnt int(5NOT NULL DEFAULT 0 COMMENT 'VOC건수',
  YM varchar(12NOT NULL COMMENT '년월',
  CEndDate varchar(12DEFAULT NULL COMMENT '계약종료일',
  custtype varchar(20DEFAULT NULL COMMENT '고객분류',
  display int(2NOT NULL DEFAULT 1,
 reg_date timestamp NOT NULL DEFAULT current_timestamp() COMMENT '등록일자'
ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
ALTER TABLE voc
  ADD PRIMARY KEY (idx),
  ADD UNIQUE KEY id_ym_type (userID,YM,voctype);
 
ALTER TABLE voc
  MODIFY idx int(11NOT NULL AUTO_INCREMENT;
COMMIT;
 

 

 

'SQL' 카테고리의 다른 글

MySQL foreign key 예제  (0) 2022.05.16
[MySQL] 샘플 데이터베이스 설치하기  (0) 2022.04.28
MariaDB 대소문자 구분  (0) 2022.03.14
MySQL 중복 레코드 관리 방법  (0) 2022.03.02
MySQL 샘플 DB 설치  (0) 2022.01.14
블로그 이미지

Link2Me

,
728x90

세부사항은 https://developer.android.com/about/versions/12/behavior-changes-12?hl=ko

 

동작 변경사항: Android 12를 타겟팅하는 앱  |  Android Developers

모든 앱에 영향을 주는 Android 12의 변경사항을 알아봅니다.

developer.android.com

를 읽어보면 된다.

 

앱이 Android 12 이상을 타겟팅하고 인텐트 필터를 사용하는 활동이나 서비스, broadcast receiver를 포함하면 이러한 앱 구성요소의 android:exported 속성을 명시적으로 선언해야 한다.

<service android:name="com.example.app.backgroundService"
         android:exported="false">
    <intent-filter>
        <action android:name="com.example.app.START_BACKGROUND" />
    </intent-filter>
</service>

 

android:exported
- 앱의 활동에 인텐트 필터가 포함되면 다른 앱에서 Activity를 시작할 수 있도록 이 요소를 'true'로 설정해야 한다.
- 모든 앱에서 Activity에 액세스할 수 있으며 정확한 클래스 이름으로 활동을 시작할 수 있다.
- 인텐트 필터가 없는 경우의 기본값은 false 이다.
- 같은 애플리케이션의 구성요소나 사용자 ID가 같은 애플리케이션, 권한있는 시스템 구성요소에서만 시작될 수 있다.

 

앱 build.gradle

apply plugin: 'com.android.application'
 
android {
    compileSdkVersion 31
    defaultConfig {
        applicationId "com.spectrum.android.ping"
        minSdkVersion 23
        targetSdkVersion 31
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
 
dependencies {
    implementation project(':lib')
}

 

오래된 앱을 targetSdkVersion 31 로 업데이트하면 아래와 같은 에러가 발생한다.

 

아래와 같이 수정되면 OK

Android 10 이라도 targetSdkVersion 31로 하면 android:exported="true" 설정해야 동작된다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.spectrum.android.ping">
 
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        >
        <activity android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>
 

 

블로그 이미지

Link2Me

,

CentOS 7 Jenkins 설치

리눅스 2022. 3. 25. 23:57
728x90

CentOS 7 환경에서 Jenkins 설치 스크립트를 적어둔다.

Java 1.8 이 설치되어 있지 않으면 Jenkins 가 동작하지 않는다. 그래서 먼저 자바를 설치해준다.

# CentOS 7에 OpenJDK 1.8 설치
# 자바 1.8 설치
yum -y install java-1.8.0-openjdk
yum -y install java-1.8.0-openjdk-devel
 
# 환경변수 등록
readlink -/usr/bin/java
#/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.322.b06-1.el7_9.x86_64/jre/bin/java
 
vi /etc/profile
 
JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.322.b06-1.el7_9.x86_64
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
 
#:wq 저장하고 나온다.
 
# 수정한 파일 저장
source /etc/profile
 
# 설치 되어 있는 자바 버전 확인
yum list installed | grep java
 
 
######################################################
Jenkins 설치
######################################################
wget -/etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum -y install jenkins
 
vi /etc/sysconfig/jenkins
JENKINS_LISTEN_ADDRESS="0.0.0.0"
 
#:wq 로 저장하고 나온다.
# 기본 설정된 포트는 8080 이다.
 
######################################################
방화벽 설정
######################################################
# 방화벽 설치
yum -y install firewalld
 
# 마스크 처리되었다고 나올 때
systemctl unmask firewalld
 
# 기본 설정은 /usr/lib/firewalld/ 에 위치
# 방화벽 데몬 시작
systemctl start firewalld
 
# 방화벽 데몬 중지
systemctl stop firewalld
 
# 서버 부팅 시 firewalld 데몬 자동 시작 설정
systemctl enable firewalld
 
# 방화벽 상태(실행여부) 확인
systemctl status firewalld
firewall-cmd --state
 
# 서비스 추가
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=22/tcp
firewall-cmd --permanent --zone=public --add-port=2022/tcp
firewall-cmd --permanent --zone=public --add-port=80/tcp
firewall-cmd --permanent --zone=public --add-port=443/tcp
firewall-cmd --permanent --zone=public --add-port=8080/tcp
firewall-cmd --permanent --zone=public --add-port=9000/tcp
 
# 방화벽 재시작
# firewall 설정 후 reload 하지 않으면 적용이 되지 않는다.
firewall-cmd --reload
 
# 방화벽 확인
firewall-cmd --list-all
 
#######################################################
# 젠킨스 등록 및 시작
systemctl enable jenkins 
systemctl start jenkins
 
 
# 초기 패스워드 확인 및 복사
cat /var/lib/jenkins/secrets/initialAdminPassword
 

 

 

 

 

설치 완료후 첫 계정 만드는 화면

 

 

설치완료 후 보이는 화면

 

참고자료

https://suwoni-codelab.com/linux/2017/06/04/Linux-CentOS-jenkins/

 

 

 

'리눅스' 카테고리의 다른 글

CentOS 5.8 APM 설치 과정  (0) 2022.05.14
VMWare Network 설정  (0) 2022.04.23
Apache log Full  (0) 2022.02.22
CentOS 7 docker 설치 및 컨테이너 사용 방법  (2) 2021.12.31
[CentOS 7] Python + MariaDB 10.4 + PHP 7.4 설치 스크립트  (0) 2021.11.06
블로그 이미지

Link2Me

,
728x90

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

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

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

 

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

 

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

 

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

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

 

참고자료

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

 

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

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

brightwhiz.com

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

 

블로그 이미지

Link2Me

,
728x90

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

 

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

 

 

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

 

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

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

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

 

 

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

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

Link2Me

,
728x90

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

 

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

 

 

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

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

 

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

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

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

 

 

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

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

Link2Me

,

MariaDB 대소문자 구분

SQL 2022. 3. 14. 11:40
728x90

MariaDB 테이블명이 대소문자 구분이 되는 경우와 되지 않는 경우가 있다.

 

확인 방법

show variables like 'lower_case_table_names';

lower_case_table_names = 0

- UNIX 기반 시스템의 기본값

- 테이블이름, 별명 및 데이터베이스 이름이 대소문자를 구분하여 비교

 

 

lower_case_table_names = 1

- 테이블이름, 별명 및 데이터베이스 이름이 소문자로 저장되며 대소문자를 구분하지 않는다.

 

 

변경방법

vi /etc/my.cnf.d/server.cnf

lower_case_table_names=1

 

 

블로그 이미지

Link2Me

,
728x90

Python 에서 정규표현식을 사용하여 문자열을 추출하는 법 예제 코드이다.

 

greedy 수량자와 non-greedy 수량자의 차이점을 확인할 수 있다.

import re
 
reg = re.search(r'a[bcd]*b''abcdccb')
print(reg)
 
reg = re.search(r'a[bcd]*?b''abcdccb')
print(reg)
 
reg = re.search(r'a[bcd]*?b''abbcdccb')
print(reg)
 
reg = re.search(r'a[bcd]*?c''abbcdccb')
print(reg)
 
reg = re.search(r'a[bcd]*c''abbcdccb')
print(reg)
 
reg = re.search(r'a[bcd]+?b''abcdccb')
print(reg)
 
reg = re.search(r'a[bcd]+?b''abbcdccb')
print(reg)
 

 

 

\w matches any word character ( alphanumeric plus "_" ).

import re
 
samples = [
    '서울시 강남구 대치동 123번지',
    '서울시 강동구 상일동 123번지 201동 501호',
    '서울시 강동구 천호4동 123번지 103동 208호',
    '서울시 강동구 길동 0412번지 0008호 마루빌딩 6층',
    '서울시 강동구 길동 412번지 8호 마루빌딩 6층 제103동, 제104동 새올',
]
# 다른 언어에서는 한글은 word 로 인식 하지 않기 때문에 \b 로 구분할 수 없다.
for i in samples:
    # findall : 문자열의 전체를 검색 (결과 : 문자열 리스트)
    print(re.findall("(?:.+[도시]\s)(?:.+[시구]\s)(.+동)\s",i)[0])
    print(re.findall("(?:.+[도시]\s)(?:.+[시구]\s)(.+?동)\s",i)[0])
    # ?: : 그룹핑 대상에서 제외하라.
    # [도시] : 도 or 시
    # \s : 공백
    # .+ : 임의의 문자(.)를 + (1개 이상 반복) 동 글자를 만나는 문장 끝까지
    # .+? : 임의의 문자(.)를 +?(1개 이상 반복) 동 글자를 만나는 문장 첫글자까지
 
print('-'*10)
 
# Python 과 .NET(C#) 에서는 한글, 영문, 숫자, 언더바(_)를 word 로 인식하는 것 같다.
for i in samples:
    print(re.findall("(\w+?동)\s",i)[0])
 

 

 

https://regex101.com/ 에서 검증을 해보시라.

 

블로그 이미지

Link2Me

,
728x90

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

 

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

 

Home - Django REST framework

 

www.django-rest-framework.org

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

 

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

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

 

프로젝트 수준의 urls.py

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

 

api/serializers.py

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

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

 

api/views.py

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

 

api/urls.py

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

 

 

 

 

블로그 이미지

Link2Me

,
728x90

PHP 로 정규표현식 문자열 추출하는 것을 다양한 예제를 통해서 알아본다.

잘못 파싱할 수 있는 오류를 방지하기 위해 아래 예제를 하나 하나 실행해 보시라.

아직 정규표현식을 완전히 마스터하지 못해서 처리하는 수준이 미약함.

 

문자열을 복사하여 https://regexr.com/ 에 붙여넣고 정규표현식을 육안으로 확인해보면 도움이 많이 된다.

 

읍면동 앞까지의 주소 추출하는 정규표현식

<?php
 
$str = "서울시 강동구 명일동 123번지 101동 702호";
if(strpos($str'동'!== false ){
    // (abc) : capture group
    // (?:abc) : non-capturing group (그룹으로 결과를 반환하지 말라)
    preg_match('/(.+?)[읍면동]\s/'$str,$out);
    echo trim($out[0]).'<br />';
}
 
?>

.+  와  .+? 의 차이점을 이해하면 원하는 걸 얻을 수 있다.

 

<?php
ini_set("display_startup_errors"1);
ini_set("display_errors"1);
error_reporting(E_ALL);
 
 
$str = "서울시 강동구 명일동 123번지 101동 702호";
if(strpos($str'동'!== false ){
    $stArr = explode(" "$str);
    foreach($stArr as $item){
        if(strpos($item'동'!== false ){
            echo $item.'<br />';
            // 원하는 결과를 반환하고 있는가?
        }
    }
echo '-----------------------------------------------<br/>';
 
// \b(word boundary) 는 영문자와 숫자는 포함되지만 한글은 포함되지 않는다.
// word는 한글과 같은 2바이트 문자를 포함하지 않기 때문에, 한글의 경계는 \b로 처리할 수 없다.
$str = "Hello, Java!";
preg_match('/\bJava\b/'$str,$out);
if(count($out)>0){
    // 그룹으로 묶지 않아서 일치하면 1개만 반환
    echo $out[0].'<br/>';
}
 
$str = "Hello, Java!";
preg_match('/\b(Java)\b/'$str,$out);
if(count($out)>0){
    // 그룹()으로 묶어서 일치하면 2개 반환
    echo '<pre>';print_r($out);echo '</pre>';
}
 
$str = "서울시 강남구 대치동 123번지";
if(strpos($str'동'!== false ){
    preg_match('/\b대치동\b/'$str,$out);
    if(count($out)>0){
        echo $out[0].'<br/>';
    } else {
        echo '추출 못함<br/>';
    }
}
 
 
$str = "서울시 강남구 대치동 123번지"
$str = "서울시 강동구 명일동 123번지 101동 702호"// 101동을 반환
if(strpos($str'동'!== false ){
    // (abc) : capture group
    // (?:abc) : non-capturing group (그룹으로 결과를 반환하지 말라)
    preg_match('/(?:.+)\s(.+)동/'$str,$out);
    echo '<pre>';print_r($out);echo '</pre>';
}
 
$str = "서울시 강동구 상일동 123번지 201동 501호"
if(strpos($str'동'!== false ){
    // (abc) : capture group
    // (?:abc) : non-capturing group (그룹으로 결과를 반환하지 말라)
    // 숫자로 시작하는 동은 대상에서 제외.
    preg_match('/(?:.+)\s([^0-9]+)동/'$str,$out);
    echo '<pre>';print_r($out);echo '</pre>';
}
 
// 공백으로 문자열을 분리하여 동이 들어간 문자열 반환
$str = "서울시 강동구 천호동 123번지 103동 208호";
if(strpos($str'동'!== false ){
    $stArr = explode(" "$str);
    foreach($stArr as $item){
        if(preg_match('/(.*)동$/'$item)){
            // 문자열의 끝($)이 동으로 끝나는 것만 반환하라.
            preg_match('/(.*)동$/'$item$out);
            echo '<pre>';print_r($out);echo '</pre>';
        }
    }
}
 
$str = "서울시 강동구 천호4동 123번지 103동 208호";
if(strpos($str'동'!== false ){
    $stArr = explode(" "$str);
    foreach($stArr as $item){
        if(preg_match('/(^[^0-9]{1,1}.+)동$/'$item)){
            preg_match('/(.*)동$/'$item$out);
            echo '<pre>';print_r($out);echo '</pre>';
        }
    }
}
 
?>
 

 

 

<?php
/*  정규표현식 문자 추출 예제 */
$str = "서울시 강동구 상일2동 123번지 201동 501호"
//$str = "서울시 강남구 대치2동 123번지"; 
echo getDongName($str);
echo '<br />';
 
function getDongName($str){
    if(strpos($str'동'!== false ){
        // (abc) : capture group
        // (?:abc) : non-capturing group (그룹으로 결과를 반환하지 말라)
        // 숫자로 시작하는 동은 대상에서 제외.
        preg_match('/(?:.+)\s([^0-9]{1,1}.+)동/'$str,$out);
        if(preg_match('/[^0-9]{1,1}.+동/'$out[1])){
            preg_match('/([^0-9]{1,1}.+)동/'$out[1],$rs);
            return $rs[1];
        } else {
            return $out[1];
        }
    }
}
?>

 

<?php
// .*? : lazy quantifier
$str = "서울시 강동구 길2동 123번지 103동 208호";
if(strpos($str'동'!== false ){
    preg_match('/(?:.+[도시]\s)(?:.+[시구]\s)(.+?)동/'$str$out);
    echo '<pre>';print_r($out);echo '</pre>';
}
 
$str = "서울시 강동구 천호3동 654번지 303동 502호";
if(strpos($str'동'!== false ){
    $out = preg_replace('/(?:.+[도시]\s)(?:.+[시구]\s)([^0-9]{1}.*?)동(?:\s.+)/',"\\1" ,$str);
    echo '<pre>';print_r($out);echo '</pre>';
}
 
$str = "서울시 강동구 천호2동 888번지 803동 502호";
if(strpos($str'동'!== false ){
    $out = preg_replace('/(?:.+[도시]\s)(?:.+[시구]\s)([^0-9]{1}.*?)동(?:\s.+)/',"$1" ,$str);
    echo '<pre>';print_r($out);echo '</pre>';
}
?>
 

 

블로그 이미지

Link2Me

,
728x90

1. 개요

MySQL에는 아래 3가지 방법을 이용하여 중복 레코드를 관리할 수 있다.

  1. INSERT IGNORE ...
  2. REPLACE INTO ...
  3. INSERT INTO ... ON DUPLICATE UPDATE

각 방법의 특징을 요약하면 다음과 같다.

INSERT IGNORE ... 최초 등록된 레코드가 남아 있음
최초 등록된 레코드의 AUTO_INCREMENT 값은 변하지 않음
REPLACE INTO ... 최초 등록된 레코드가 삭제되고, 신규 레코드가 INSERT됨
AUTO_INCREMENT의 값이 변경됨
INSERT INTO ... ON DUPLICATE UPDATE INSERT IGNORE의 장점 포함함
중복 키 오류 발생 시, 사용자가 UPDATE될 값을 지정할 수 있음

방법 특징

2. 사전 조건 및 중복 처리 방법

중복 레코드 관리를 위해선 테이블에 UNIQUE INDEX가 필요하다.

자동으로 증가하는 Primary Key 로 중복관리를 하는 것은 쉽지 않다.

중복 레코드 관리를 위한 KEY가 2개의 칼럼 조합일 수도 있다.

 

create database androidsample default character set utf8 COLLATE utf8_general_ci;
 
CREATE TABLE IF NOT EXISTS person (
  id int(11NOT NULL,
  name varchar(20DEFAULT NULL,
  address varchar(150DEFAULT NULL,
  cnt int(4NOT NULL DEFAULT 0,
  PRIMARY KEY (id),
  UNIQUE INDEX (name) -- 중복 검사용 필드
ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
 
-- INSERT IGNORE INTO
-- INSERT IGNORE는 중복 키 에러가 발생했을 때 신규로 입력되는 레코드를 무시
INSERT IGNORE INTO person VALUES (NULL'홍길동''서울',0);
INSERT IGNORE INTO person VALUES (NULL'신시아''용인',0);
INSERT IGNORE INTO person VALUES (NULL'홍길동''평택',1);
INSERT IGNORE INTO person VALUES (NULL'신시아''서울',1);
INSERT IGNORE INTO person VALUES (NULL'홍길동''인천',2);
 
 
-- INSERT INTO ON DUPLICATE KEY UPDATE
INSERT INTO person VALUES (NULL'강감찬''대전'3)
           ON DUPLICATE KEY UPDATE address = VALUES(address), cnt=VALUES(cnt);
 
INSERT INTO person VALUES (NULL'홍길동''인천'5)
           ON DUPLICATE KEY UPDATE address = VALUES(address), cnt=VALUES(cnt);
 
INSERT INTO person(name,cnt) VALUES ('신시아'8)
           ON DUPLICATE KEY UPDATE cnt=VALUES(cnt);
 
INSERT INTO person VALUES (NULL'이순신''충청'9)
           ON DUPLICATE KEY UPDATE address = VALUES(address), cnt=VALUES(cnt);
 
-- 테이블 비우기
TRUNCATE person;
 
INSERT IGNORE INTO person VALUES (NULL'홍길동''서울',0);
INSERT IGNORE INTO person VALUES (NULL'신시아''용인',0);
INSERT IGNORE INTO person VALUES (NULL'홍길동''평택',1);
INSERT IGNORE INTO person VALUES (NULL'신시아''서울',1);
INSERT IGNORE INTO person VALUES (NULL'홍길동''인천',2);
 
-- REPLACE INTO
-- REPLACE INTO는 중복이 발생되었을 때 기존 레코드를 삭제하고 신규 레코드를 INSERT하는 방식
REPLACE INTO person VALUES (NULL'홍길동''서울',0);
REPLACE INTO person VALUES (NULL'신시아''용인',0);
REPLACE INTO person VALUES (NULL'홍길동''평택',1);
REPLACE INTO person VALUES (NULL'신시아''서울',1);
REPLACE INTO person VALUES (NULL'홍길동''인천',2);
 

 

UPDATE하고자 할 때는 항상 column=VALUES(column)와 같이 적어줘야 한다는 점이다.

 

두가지 케이스로 테스트를 해보면서, id 값이 어떻게 변경되는지 확인해 보시라.

 

Primary Key 값의 변동없이 Insert INTO, Update를 처리하려면 PHP 등과 같은 별도의 백엔드 언어를 통해서 처리하는 코드를 구현해야 한다.

'SQL' 카테고리의 다른 글

MariaDB 멀티 인덱스(index) 설정  (0) 2022.04.05
MariaDB 대소문자 구분  (0) 2022.03.14
MySQL 샘플 DB 설치  (0) 2022.01.14
DB 접속툴 DBeaver Community 설치 및 DB 접속  (0) 2021.12.03
회원 테이블(members SQL) 예시  (0) 2021.11.09
블로그 이미지

Link2Me

,
728x90

먼저 https://getbootstrap.com/docs/4.6/components/pagination/ 사이트에서 기본적인 Paging 처리에 대한 예제를 살펴본다.

또는 https://www.w3schools.com/bootstrap4/bootstrap_pagination.asp 에서 Pagination 항목을 선택해서 봐도 된다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Bootstrap4 Pagination Example</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<?php
/*
    // bootstrap4 에서 px 에서 em 으로 변경
    m : Margin, p : Padding
    t : top, b : bottom, l : left, r : right,
    x : x축 -> left , right, y : y축 -> top , bottom
 
    0 : 0
    1 : .25rem( font-size가 16px이면, 4px) 크기
    2 : .5rem( font-size가 16px이면, 8px) 크기
    3 : 1rem( font-size가 16px이면, 16px) 크기
    4 : 1.5rem( font-size가 16px이면, 24px) 크기
    5 : 3rem( font-size가 16px이면, 48px) 크기
    auto : margin 자동 세팅
 
    n : negative을 의미
    n1 : -.25rem( font-size가 16px이면, -4px) 크기
    n2 : -.5rem( font-size가 16px이면, -8px) 크기
    n3 : -1rem( font-size가 16px이면, -16px) 크기
    n4 : -1.5rem( font-size가 16px이면, -24px) 크기
    n5 : -3rem( font-size가 16px이면, -48px) 크기
 
    my- = it sets margin-left and margin-right at the same time on y axes
*/
?>
<div class="container mt-2">
    <div id="paging" class="mt-2">
        <ul class="pagination">
            <li class="page-item"><a class="page-link" href="#">Previous</a></li>
            <li class="page-item"><a class="page-link" href="#">1</a></li>
            <li class="page-item"><a class="page-link" href="#">2</a></li>
            <li class="page-item"><a class="page-link" href="#">3</a></li>
            <li class="page-item"><a class="page-link" href="#">Next</a></li>
        </ul>
    </div>
</div>
 
</body>
</html>

 

 

ul 태그의 pagination 클래스, li 태그의 page-item 클래스 등 페이징 처리를 위한 구조를 보고 PHP 코드로 변환 준비를 한다.

PHP 코드는 MySQL DB와 연동하여 전제 페이지 수, 현재 페이지 등의 정보를 고려하여 코드를 구현한다.

<?php
class bbsClass extends DBDataClass {
 
    function PageLinkView($link_url$totalcnt$rowsPage$curPage$m) {
        echo '<div id="paging">';
        echo '<ul class="pagination">';
        $Info = $this->PageList($totalcnt$rowsPage$curPage'');
        if ($Info['current_block'> 2) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=1&$m'>◀</a></li> ";
        }
        if ($Info['current_block'> 1) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $Info['prev'] . "&$m'>◁</a></li> ";
        }
        foreach ($Info['current'as $w) {
            if ($curPage == $w) {
                echo "<li class='act'><a class='page-link' href='" . $link_url . "?p=" . $w . "&$m'><span style='color:red;'>" . $w . "</span></a></li> ";
            } else {
                echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $w . "&$m'>" . $w . "</a></li> ";
            }
        }
        if ($Info['current_block'< ($Info['total_block'])) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $Info['next'] . "&$m'>▷</a></li> ";
        }
        if ($Info['current_block'< ($Info['total_block'- 1)) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $Info['totalPage'] . "&$m'>▶</a></li> ";
        }
        echo '</ul>';
        echo '</div>';
    }
 
    // $curPage : 현재 페이지, $totalcnt : 총 게시물수
    // $block_limit : 한 화면에 뿌려질 게시글 개수
    function PageList($totalcnt,$rowsPage,$curPage,$block_limit) {
        $block_limit = $block_limit ? $block_limit : 10;  // 한 화면에 보여줄 개수 기본 10으로 설정
 
        // 총 페이지수 구하기
        $totalPage = ceil($totalcnt/$rowsPage);
        if($totalPage == 0) {
            ++$totalPage;
        }
        $total_block = ceil($totalPage / $block_limit); //전체 블록 갯수
 
        $curPage = $curPage ? $curPage : 1// 현재 페이지
 
        // 현재 블럭 : 화면에 표시될 페이지 리스트
        $current_block=ceil($curPage/$block_limit);
        // 현재 블럭에서 시작페이지
        $fstPage = (((ceil($curPage/$block_limit)-1)*$block_limit)+1);
        // 현재 블럭에서 마지막 페이지
        $endPage = $fstPage + $block_limit -1;
        if($totalPage < $endPage) {
            $endPage = $totalPage;
        }
 
        // 시작 바로 전 페이지
        $prev_page = $fstPage - 1;
        // 마지막 다음 페이지
        $next_page = $endPage + 1;
 
        foreach(range($fstPage$endPageas $val) {
            $row[] = $val;
        }
        // 배열로 결과를 돌려준다.
        return array(
            'total_block' => $total_block,
            'current_block' => $current_block,
            'totalPage' => $totalPage,
            'fstPage' => $fstPage,
            'endPage' => $endPage,
            'prev' => $prev_page,
            'next' => $next_page,
            'current' => $row
        );
    }
 
    //총페이지수
    function getTotalPage($num,$rec){
        return @intval(($num-1)/$rec)+1;
    }
 
}//end class boardClass

 

 

아래 Class 는 PDO(PHP Data Object) 기반으로 구현한 코드이다.

Legacy PHP 기반의 Class 는 https://link2me.tistory.com/1112 를 참조하면 된다.

 
<?php
class DBDataClass {
    protected $db// 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.
 
    public function __construct() {
        $this->dbConnect();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }
 
    private function dbConnect() {
        require_once 'dbinfo.php';
        try {
            // MySQL PDO 객체 생성
            $this->db = new PDO(_DSN, _DBUSER, _DBPASS);
            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
        } catch(PDOException $ex) {
            die("오류 : " . $ex->getMessage());
        }
    }
 
    /*
     $sql = "INSERT INTO users (name, surname, sex) VALUES (?,?,?)";
     $stmt= $pdo->prepare($sql);
     $stmt->execute(array($name, $surname, $sex));
    */
    public function recur_quot($cnt) {
        $R = array();
        for ($i = 0$i < $cnt$i++) {
            array_push($R"?");
        }
        return implode(","$R); // 배열을 문자열로
    }
 
    // 신규 자료 추가(ok)
    function putDbInsert($table$key$params) {
        try {
            $this->db->beginTransaction();
            $sql = "insert into " . $table . " (" . $key . ") values(" . $this->recur_quot(count($params)) . ")";
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params); // $params 는 배열 값
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
 
    function getDbUpdate($table$set$params$where) {
        $sql = "update " . $table . " set " . $set . ($where ? ' where ' . $where : '');
        try {
            $this->db->beginTransaction();
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params);
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
    public function getDbDelete($table$where$params) {
        if ($this->isDataExisted($table$where$params)) {
            try {
                $this->db->beginTransaction();
                $sql = "delete from " . $table . ($where ? ' where ' . $where : '');
                $stmt = $this->db->prepare($sql);
                $status = $stmt->execute($params);
                if ($status) {// action 실행여부에 대한 결과이지 실제 데이터 삭제와는 무관하네.
                    $this->db->commit();
                    return 1// 삭제 성공
                } else {
                    return 0// 삭제 실패
                }
            } catch (PDOException $pex) {
                $this->db->rollBack();
                echo "에러 : " . $pex->getMessage();
            }
        } else {
            return 0// 삭제할 데이터가 없음
        }
 
    }
 
    // 삭제할 데이터의 존재 유무 파악
    public function isDataExisted($table$where$params) {
        $sql = 'select * from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if ($row = $stmt->fetch()) {
            return $row;
        } else {
            return false;
        }
    }
 
    //SQL필터링
    public function getSqlFilter($sql) {
        //$sql = preg_replace("/[\;]+/","", $sql); // 공백은 제거 불가
        return $sql;
    }
 
    // Simple function to handle PDO prepared statements
    function sql($sql$params) {
        // $params : array 를 사용해야 한다.
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(); // foreach 문으로 결과 처리
    }
 
    // 검색조건에 일치하는 데이터 가져오기
    public function putDbData($sql,$params$returntype = '') {
        // $params : array 를 사용해야 한다.
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
 
    public function putDbArray($sql$params) {
        // $params : array 를 사용해야 한다.
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리
    }
 
    public function putDbArrayALL($sql) {
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리
    }
    
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbData($table$where$column$returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
 
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbDataAll($table$where$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // 검색조건에 일치하는 데이터 가져오기
    public function fetchDbData($table$where$column$returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch();
        } else {
            return $stmt->fetchAll();
        }
    }
 
    // table 결과 조회 용도
    public function fetchDbArray($table$where$column$orderby$rowsPage$curPage) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '') . ($orderby ? ' order by ' . $orderby : '') . ($rowsPage ? ' limit ' . (($curPage - 1* $rowsPage) . ', ' . $rowsPage : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리 하면 됨
    }
 
    // table 결과 조회 용도
    public function getDbArray($table$where$column$orderby$rowsPage$curPage) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '') . ($orderby ? ' order by ' . $orderby : '') . ($rowsPage ? ' limit ' . (($curPage - 1* $rowsPage) . ', ' . $rowsPage : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        //return $stmt;
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리 하면 됨
    }
 
    // WhereArgs 조건에 일치하는 선택 DbData 가져오기(ok)
    public function getDbDataFromIdx($table$whereArgs$idx) {
        $sql = 'select * from ' . $table . ' where ' . $whereArgs . '=?';
        $stmt = $this->db->prepare($sql);
        $stmt->execute(array($idx));
        return $stmt->fetch();
    }
 
    // DB 레코드 총 수(ok)
    public function getDbRows($table$where) {
        $sql = 'select count(*) from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($row = $stmt->fetch()) {
            return $row[0];
        } else {
            return false;
        }
    }
 
}
?>

 

<?php
define('_DBHOST','localhost');
//define('_DBHOST','192.168.1.240'); // PHP 서버와 DB서버가 분리된 경우
define('_DBNAME',''); // DB 명
define('_DBUSER',''); // DB 사용자
define('_DBPASS'''); // DB 패스워드
define('_DBTYPE','mysql'); // 데이터베이스 종류
define('_DSN',_DBTYPE.':host='._DBHOST.';dbname='._DBNAME.';charset=utf8');
?>

 

블로그 이미지

Link2Me

,
728x90

박상권님이 GitHub 에 오픈해 주신 https://github.com/ParkSangGwon/TedNaverMapClustering 

 

GitHub - ParkSangGwon/TedNaverMapClustering: 네이버지도용 클러스터 유틸리티 라이브러리

네이버지도용 클러스터 유틸리티 라이브러리. Contribute to ParkSangGwon/TedNaverMapClustering development by creating an account on GitHub.

github.com

Android 용 TedNaverMapClustering 파일을 받아서 구현한 네이버 지도에 Clustering 기능을 구현했다.

 

라이브러리 적용 전 코드와 적용 후 코드를 구분하여 적어둔다.

서버로부터 받아오는 데이터 구조가 아래와 같이 구현되어 있었다. (일부 변수는 삭제)

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
 
@Parcelize
data class ItemData (
    val code: String,
    val bldNM: String// 빌딩명
    val address: String// 주소
    var latitude: Double, // 위도
    var longitude: Double, // 경도
): Parcelable

 

TedNaverMapClustering 적용 후 dataclass

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
 
@Parcelize
data class ItemData (
    val code: String,
    val bldNM: String// 빌딩명
    val address: String// 주소
    var latitude: Double, // 위도
    var longitude: Double, // 경도
): Parcelable
 

 

ItemData data class 는 그대로 두고 별도로 ItemData 에서 필수 항목만 뽑아서 아래와 같이 코드를 작성한다.

import ted.gun0912.clustering.clustering.TedClusterItem
import ted.gun0912.clustering.geometry.TedLatLng

data class NaverItem(var position: LatLng) : TedClusterItem {
    override fun getTedLatLng(): TedLatLng {
        return TedLatLng(position.latitude, position.longitude)
    }
 
    var code: String= null
    var bldNM: String= null
    var chtype: Int = 0
    var Cust: Int = 0
 
    constructor(lat: Double, lng: Double) : this(LatLng(lat, lng)) {
        code = null
        bldNM = null
        chtype = 0
        Cust = 0
    }
 
    constructor(lat: Double, lng: Double, code: String?, bldNM: String?, chtype: Int, Cust: Int) : this(
        LatLng(lat, lng)
    ) {
        this.code = code
        this.bldNM = bldNM
        this.chtype = chtype
        this.Cust = Cust
    }
}

 

 

기존 marker 처리 코드

https://navermaps.github.io/android-map-sdk/guide-ko/5-1.html 부분에 "대량의 오버레이를 다룰 경우 객체를 생성하고 초기 옵션을 지정하는 작업은 백그라운드 쓰레드에서 수행하고 지도에 추가하는 작업만을 메인 쓰레드에서 수행하면 메인 쓰레드를 효율적으로 사용할 수 있습니다." 라고 나온다. 많은 marker 를 생성시에는 백그라운드 쓰레드에서 처리하도록 하고 있다.

var activeMarkers: MutableList<Marker>= null
private fun makeAllMarker(itemList: List<ItemData>) {
    val executor = Executors.newSingleThreadExecutor()
    val handler = Handler(Looper.getMainLooper())
    executor.execute {
        // 백그라운드 쓰레드
        activeMarkers = ArrayList()
        for (i in itemList.indices) {
            val itemData = itemList[i]
            val marker = Marker()
            marker.position = LatLng(itemData.latitude, itemData.longitude)
            marker.icon = OverlayImage.fromBitmap(makePinBitmap(itemData.Cust,itemData.chtype)!!)
            //marker.setIconTintColor(Color.RED); // 덧입힐 색상
            marker.width = Marker.SIZE_AUTO
            marker.height = Marker.SIZE_AUTO
            //marker.setCaptionMinZoom(12);
            //marker.setCaptionMaxZoom(16);
            marker.captionText = itemData.bldNM
            marker.isHideCollidedCaptions = true // 겹치는 캡션 자동 숨김 처리
            marker.isHideCollidedSymbols = true //  마커와 겹치는 지도 심벌을 자동으로 숨기도록 지정
            //marker.setHideCollidedMarkers(false);
            marker.tag = itemData
            activeMarkers?.add(marker)
            marker.onClickListener = Overlay.OnClickListener { overlay: Overlay ->
                mInfoWindow.open(marker)
                val position = LatLng(itemData.latitude, itemData.longitude) // 현재 위치
                val cameraUpdate = CameraUpdate.scrollTo(position).animate(CameraAnimation.Easing)
                mNaverMap!!.moveCamera(cameraUpdate)
                onClickMarker(overlay)
                true
            }
        }
        handler.post {
            // 메인 스레드
            for (marker in activeMarkers!!) {
                marker.map = mNaverMap
            }
        }
    }
}
 
private fun freeActiveMarkers() {
    if (activeMarkers == nullreturn
    for (marker in activeMarkers!!) {
        marker.map = null // 마커들 지도에서 삭제
    }
}
 

 

 

위 코드를 TedNaverMapClustering 코드로 변경한 코드이다.

import ted.gun0912.clustering.naver.TedNaverClustering

lateinit var tedNaverClustering: TedNaverClustering<NaverItem>
private fun makeClusterMarker(itemList: List<NaverItem>){
    tedNaverClustering = TedNaverClustering.with<NaverItem>(this, mNaverMap)
        .customMarker{ itemData ->
            Marker(itemData.position).apply {
                icon = OverlayImage.fromBitmap(makePinBitmap(itemData.Cust,itemData.chtype)!!)
                captionText = itemData.bldNM!!
                tag = itemData
            }
        }
        .markerClickListener { itemData ->
            val position = itemData.position
            val cameraUpdate = CameraUpdate.scrollTo(position).animate(CameraAnimation.Easing)
            mNaverMap!!.moveCamera(cameraUpdate)
            onClickClusterMarker(itemData.code)
        }
        .minClusterSize(20)
        .make()
 
    val executor = Executors.newSingleThreadExecutor()
    val handler = Handler(Looper.getMainLooper())
    executor.execute {
        // 백그라운드 스레드
        tedNaverClustering.addItems(itemList)
        Log.e("TAG""Number of tedNaverClustering :" + itemList.size)
    } // end executor.execute
}
 

 

fun onClickClusterMarker(code: String?){
    // 호출해야 할 데이터가 다른 ArrayList 에서 code 값이 서로 일치하는 데이터만 추출해야 한다.
    val itemData: ItemData = itemDataList.filter {it.code.equals(code)}.single()
 
}

 

서버에서 가져온 데이터 List 에 추가하는 코드 중에서 핵심만 발췌

response.body()?.let {
    if (it.status.contains("success")) {
        itemDataList.clear() // 서버에서 가져온 데이터 초기화
        naverItemList.clear()
        val itemData: List<ItemData> = it.iteminfo
        Log.e("TAG""Number of itemData :" + itemData.size)
        for (item in itemData) {
            // 자동으로 타입 변환을 하는 듯함.
            itemDataList.add(item)
            putNaverItem(item.latitude,item.longitude,item.code,item.bldNM,item.chtype,item.Cust)
        }
        showMapMarker(itemDataList)
    } else {
        Utils.showAlert(context, it.status, it.message)
    }
 
fun putNaverItem(lat: Double, lng: Double, code: String, bldNM: String, chtype: Int, Cust: Int){
    // NaverItem 생성자에 데이터 추가 및 naverItemList 에 추가
    var naverItem = NaverItem(lat,lngitude,code,bldNM,chtype,Cust)
    naverItemList.add(naverItem)
}

 

코드 구현 이슈

Thread Pool size 를 초과하는 데이터 크기(서버로부터의 Input 데이터 개수)는 좀 더 신경써야 할 거 같다.

 

 

 

블로그 이미지

Link2Me

,
728x90

현재 위치한 반경 km 범위내 데이터를 가져오는 쿼리문

숫자가 직접 기입된 부분을 userLAT, userLNG 로 바꿔줘야 한다.

 

--쿼리문
SELECT *, (6371 * acos(cos(radians(37.504696)) * cos(radians(latitude)) * cos(radians(longitude) - radians(127.041237)) + sin(radians(37.504696))*sin(radians(latitude)))) AS distance 
FROM blding 
HAVING distance <= 0.5 
ORDER BY distance;
 

 

지도를 움직일 경우 ** 정보를 조회해서 화면에 표시될 수 있도록 한다.

지도를 일정 레벨 이상으로 확대했을 경우, 해당 위치에 있는 정보 List를 서버에 요청한다.

onCameraMove → showList() → getDataFromServer() → addMarker()

 

 

 

블로그 이미지

Link2Me

,

Apache log Full

리눅스 2022. 2. 22. 16:43
728x90

Apache Log 가 Full 이 되어 홈페이지 세션 등 전혀 동작되지 않는 현상이 발생했다.

 

access_log 파일과 error_log 파일을 강제로 삭제하고

apache 를 재시작해주었더니 정상적으로 파일이 생성되면서 동작된다.

블로그 이미지

Link2Me

,

MDB4 select

Web 프로그램/MDB 2022. 2. 12. 21:34
728x90

mdbootstrap4 기반 select

https://mdbootstrap.com/docs/b4/jquery/forms/select/

 

Bootstrap Select - examples & tutorial. Basic & advanced usage

Bootstrap select is a form control which after a click displays a collapsable list of multiple values which can be used in forms, menus or surveys.

mdbootstrap.com

<select class="browser-default custom-select">
  <option selected>Open this select menu</option>
  <option value="1">One</option>
  <option value="2">Two</option>
  <option value="3">Three</option>
</select>

 

 

MySQL DB 기반 select

 

<?php
require_once 'config.php';
require_once 'dbconnect.php';
 
$sql = "SELECT DISTINCT YM FROM customer ORDER BY YM DESC LIMIT 3";
$result = mysqli_query($db$sql);
?>
 
<form id="form" class="text-center border border-light p-5">
 
    <p class="h4 mb-4">Data 업데이트</p>
 
    <select class="browser-default custom-select" name="ym">
      <option value="" selected>-년월-</option>
    <?php
    while($R = mysqli_fetch_row($result)):
        echo '<option value="'.$R[0].'">'.$R[0].'</option>';
    endwhile;
    ?>
    </select>
 
    <div>
        <button type="submit" id="btnSubmit" class="btn btn-outline-default">전송</button>
        <button type="reset" id="btnReset" class="btn btn-outline-default">취소</button>
    </div>
    <br/>
</form>
 
<script>
$('#form').submit(function(e) {
    e.preventDefault();
    var yearmonth = $("select[name=ym]").val();
    if(yearmonth == '') {
        alert('연월을 선택하세요');
        $("select[name=ym]").focus();
        return false;
    }
 
    $.ajax({
        url: phpUpfilename,
        type: 'POST',
        data: { YM : yearmonth },
        dataType: 'text',
        success: function (data) {
            console.log(data);
            alert(data);
        },
        error: function(data) {
            alert("error in ajax form submission");
        }
    });
});
</script>
 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

파이썬은 정규 표현식을 지원하기 위해 re(regular expression의 약어) 모듈을 제공한다.

re 모듈은 파이썬을 설치할 때 자동으로 설치되는 기본 라이브러리이다.

import re
 
str = 'love people around you, love your work, love yourself'
 
# 1) match : 문자열의 처음부터 정규식과 매치되는지 조사 (결과 : 1개의 match 객체)
# 문자열의 처음에 일치하는 문자열이 없으면 None 반환
result0 = re.match('love'str)
print(result0)

result1 = re.match('people'str)
print(result1)

# 2) search : 문자열의 전체를 검색하여 정규식과 매치되는지 조사 (결과 : 1개의 match 객체)
result = re.search('people'str)
print(result)
 
# 2.1) group() : 매칭된 문자열을 반환
print("matched string : {}".format(result0.group()))
print("matched string : {}".format(result.group()))
 
# 2.2) start() : 매칭된 문자열의 시작 위치 반환
print(result.start())
 
# 2.3) end() : 매칭된 문자열의 끝 위치 반환
print(result.end())
 
# 2.4) span() : 매칭된 문자열의 (시작, 끝) 위치 튜플을 반환
print(result.span())
 
 
# 3) findall : 문자열의 전체를 검색 (결과 : 문자열 리스트)
# search 가 최초로 매칭되는 패턴만 반환한다면, findall은 매칭되는 전체의 패턴을 반환
# https://link2me.tistory.com/2129 에서 제대로 된 예제 확인 가능
rst_all = re.findall('love'str)
print(rst_all)
 
# 4) finditer : 문자열의 전체를 검색 (결과 : match 객체 iterator)
results = re.finditer('love'str)
print(results)
 
for result in results:
    print(result)
 
# 5) fullmatch 패턴과 문자열이 완벽하게 일치하는지 검사
str2 = 'Hey Guys, read books'
result = re.fullmatch('.*', str2)
print(result)
 
# 1. Group 그룹
# 1) 매칭되는 문자열 1개
str1 = '010-1112-6780'
result = re.match('\d{2,3}-\d{3,4}-(\d{4})$', str1)
print(result.group(1))
 
# 2) 매칭되는 문자열 여러개
str2 = '010-2343-7888,010-2343-1234,010-2343-5678,010-2343-9999,010-2343-2222'
results = re.finditer('\d{2,3}-\d{3,4}-(\d{4})(?=,|$)', str2)
# (?=,|$) : 전방탐색 , 기준으로 전방탐색, 문자열 마지막($) 기준으로 전방탐색

for idx, result in enumerate(results, 1):
    print(f'{idx}. {result.group(1)}')
 
 
# 2. Substitution (교체) - 전화번호 마스킹 처리
# sub : 주어진 문자열에서 일치하는 모든 패턴을 replace
# 그 결과를 문자열로 다시 반환함.
# 두번째 인자는 특정 문자열이 될 수도 있고, 함수가 될 수도 있음.
# count가 0인 경우는 전체를 치환하고, 1이상이면 해당 숫자만큼 치환 됨.
str3 = '010-1113-5680'
후방탐색 : ?<=
result = re.sub('(?<=\d{3}-\d{4}-)\d{4}''****', str3)
print(result)
 
result = re.sub('(?<=\d{3}-\d{2})\d{2}-\d{2}''**-**', str3)
print(result)
 

 

import re
 
# compile
# 동일한 정규표현식을 매번 다시 쓰기 번거로움을 해결
# compile로 해당 표현식을 re.RegexObject 객체로 저장하여 사용 가능
 
email_regex = re.compile("([A-Za-z]+[A-Za-z0-9]+@[A-Za-z]+\.[A-Za-z]+)")
# email_regex = re.compile("(\.)")
email_input = input("이메일 입력 : ")
email_validation = email_regex.search(email_input.replace(" ",""))
 
if email_validation:
    print("It's vaild")
else:
    print("It's invaild")
블로그 이미지

Link2Me

,
728x90

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

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

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

 

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

login_url 과 redirect_field_name 을 추가 해준다.

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

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

 

forms.py

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

 

 

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

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

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

 

 

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

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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

 

블로그 이미지

Link2Me

,