Singleton 패턴은 전체 프로그램 스콥에서, 특정 클래스를 사용할 때, 단 하나의 인스턴스만 생성해서 사용하는 패턴입니다. 하나의 인스턴스를 사용하게 되면 다음과 같은 장점이 있습니다.
- 공유 자원에 대해서 동시 접근을 제한할 수 있습니다.
- 전역에서 사용가능한 리소스를 생성할 수 있습니다.
- 프로그램 스콥에서 단 하나의 인스턴스만 생성해서 사용하기 때문에 메모리 낭비를 막을 수 있습니다.
모듈 레벨의 Singleton
python에서는 기본적으로 모든 module 은 singleton 으로 정의되어 있습니다. 다음의 예시는 하나의 프로그램 내에서 실행하거나 혹은 동일 shell에서 실행해야 확인이 가능합니다.
## singleton.py
# shared_variable에 초기값 지정
shared_variable = "init value"
## module1.py
import singleton
# 원래 shared_variable에 있는 내용을 확인
print(singleton.shared_variable)
# shared_variable에 내용을 추가
singleton.shared_variable += ", some text"
## module2.py
import singleton
# shared_variable에 있는 내용을 확인
print(singleton.shared_variable)
실행결과
클래스 레벨의 Singleton
이번에는 클래스를 정의해서 singleton 패턴을 구현해 보도록 하겠습니다.
python에서는 클래스의 생성자에서 특정 attribute을 정의해서 항상 같은 값을 바라보는 결과를 리턴하게 처리하면 됩니다.
class SingletonClass():
def __new__(cls):
if not hasattr(cls, "instance"):
cls.instance = super().__new__(cls)
return cls.instance
python shell에서 다음과 같이 실행해 보면, SingletonClass로 생성한 instance의 결과가 같다는 것을 알 수 있습니다.
Python 의 decorator 는 funciton 이나 class에 코드의 내용을 변경하지 않고도 기능을 확장할 수 있는 아주 유용한 툴입니다.
Decorator를 이해하기 위해서는 몇 가지 알아두면 좋은 내용들이 있어서 먼저 알아보고, decorator에 대해서 알아보도록 하겠습니다.
First Class Objects
먼저 한국말로는 일급 객체라고 이야기하는 first class objects에 대해서 알아보겠습니다. 일급 객체는 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킵니다. 보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 합니다.
일급 객체는 다음과 같은 특징이 있습니다. (by 로빈 포플스톤)
모든 요소는 함수의 실제 매개변수가 될 수 있다.
모든 요소는 함수의 반환 값이 될 수 있다.
모든 요소는 할당 명령문의 대상이 될 수 있다.
모든 요소는 동일 비교의 대상이 될 수 있다.
Python에서는 function 도 일급 객체로 분류됩니다. 아래 예시를 보면 쉽게 이해가 될 겁니다.
def add_10(val):
return val + 10
def run_func(f, val):
return f(val)
# (add_10 함수를) 매개변수로 사용
run_func(add_10, 10)
# (add_10 함수를) 반환값으로 사용
def plus_10():
return add_10
# (add_10 함수를) 다른 변수에 할당
plus_ten = add_10
# 비교의 대상으로 사용
print(add_10 is run_func)
여기서 일급객체를 특별히 설명한 이유는 Python에서는 "function"이 일급객체이기 때문에, 매개변수로 사용할 수 있으며, function을 리턴 값으로 사용할 수 있다는 특징을 상기시키기 위함입니다. 많은 프로그래밍 언어들이 function을 일급객체로 사용하기 때문에 특별히 거부감이 있는 컨셉은 아닐겁니다.
Inner Functions
Python에서는 함수의 내부에 다른 함수를 정의하는 것이 가능합니다. 내부에 정의된 함수를 inner functions 라고 부르는 데요.. 한국말로는 내부 함수가 적절할 표현일 것 같습니다. (어떤 분은 내장 함수라고 번역하시기도 했는데요, 일반적으로 내장함수는 프로그래밍 언어에서 지원해주는 기본 함수를 이야기 하기 때문에 내부 함수라는 표현이 더 적절할 것 같습니다.)
아래 예시를 보면, parent 함수 내에 child 함수를 정의해서 사용하였습니다. child 함수의 scope은 parent 함수 내에서만 유효합니다.
위의 예시에는 decorator() 함수와, some_function() 함수가 정의되어 있고, decorator() 함수에는 wrapper() 라는 내부 함수가 정의되어 있습니다. 그리고 마지막 줄에서는 some_function()을 decorator() 함수를 실행한 결과로 변경하였습니다.
여기서 some_function()을 실행하면 아래와 같은 결과가 화면에 출력될 겁니다.
>>> some_function()
BEFORE
RUN
AFTER
원래 some_function() 함수에서는 "RUN"만 출력해 주는데, decorator()를 통해서 변경한 some_function()은 "BEFORE", "RUN", "AFTER"를 차례로 출력해 줍니다. 쉽게 이야기해서 decorator() 함수가 some_function() 함수를 감싸서, 변경해 주었다고 할 수 있습니다.
실제로 decorator를 사용할 때는, 아래와 같이 @ 기호를 붙여서 함수 앞에 붙여 주면 됩니다. 아래 예시에서는 @decorator 는 some_function = decorator(some_function) 를 짧게 표현해 주는 방법으로 생각할 수 있습니다.
from django.urls import include, path
from . import views
urlpatterns = [
path("test/", views.test, name="name-test")
]
<app>/views.py
from django.urls import reverse
from django.http import HttpResponse
def test(reqeust):
return HttpResponse("Hello World!")
# using the named URL
# will return "index/"
reverse("name-test")
# passing a callable object
# will return "index/"
reverse(test)
@ url namespace - case 1
<app>/urls.py
from django.urls import include, path
from . import views
app_name = "poll"
urlpatterns = [
path("test/", views.test, name="name-test")
]
<app>/views.py
from django.urls import reverse
from django.http import HttpResponse
def test(reqeust):
return HttpResponse("Hello World!")
# using the named URL with app_name
# will return "index/"
reverse("poll:name-test")
include에 module 혹은 module name으로 매핑하는 경우에는 namespace는 <app>/urls.py 에 있는 app_name 이 우선 적용된다. 즉, <app>/urls.py 에 app_name 이 있으면 <app_name>:<path_name> 형식
ex) reverse("poll:name-test")
만약 <app>/urls.py 에 app_name이 없다면 <app_namespace>:<path_name> 이 된다.
ex) reverse("url_name_space:name-test")
@ url namespace - case 3
<config>/urls.py
from django.urls import include, path
from poll.urls import urlpatterns as poll_urlpatterns
urlpatterns = [
path("poll/", include((poll_urlpatterns, "url_name_space"))
]
include에 urlpattern 을 매핑하는 경우에는 <app>/urls.py 전체가 로딩되지 않고, <app>/urls.py/urlpatterns만 읽어서 등록하기 때문에, namespace는 <config>/urls.py 에 있는 app_namespace (여기서는 "url_name_space") 가 적용이 된다. <app_namespace>:<path_name> 형식
ex) reverse("url_name_space:name-test")
@ url in Django Template
Template 에서는 url 키워드를 이용해서 reverse와 같은 결과를 받아올 수 있다.
from django.contrib.auth import get_user_model
user = get_user_model().objects.get(username='<username>')
groups = user.groups.all()
cf. django.contrib.auth.models.User 를 이용해도 상관 없음
@ Group에 속한 User 리스트 가져오기
from django.contrib.auth.models import Group
group = Group.objects.get(name='<group_name>')
users = group.user_set.all()
@ User Group Check
from django.contrib.auth import get_user_model
user = get_user_model().objects.get(username='<username>')
# 유저가 특정 그룹에 속해 있는지 체크
user.groups.filter(name='<groupname>').exists()
# 유저가 그룹 리스트에 속해 있는지 체크
user.groups.filter(name__in=['<groupname1>', '<groupname2>']).exists()
# 유저 instance 없이 체크
get_user_model().objects.filter(username='<username>', groups__name='<groupname>').exists()
@ User Permission Check
# 유저가 가진 개별 permission 체크
user.user_permissions.filter(codename='<permission_code>').exists()
# 유저가 속한 그룹의 permission 체크
user.groups.filter(permission__codename='<permission_code>').exists()
Django 프로젝트를 배포하는 방법은 서비스 규모나 환경에 따라서 여러가지가 있겠지만..
여기서는 단일 서버에 docker를 이용하여 서비스를 운영한다고 가정을 하고 배포하는 상황을 준비하였습니다.
서비스 구성
기본적으로 source code는 git을 통해서 관리가 되고 있고, 서버에 docker는 이미 설치되어 있다고 가정하겠습니다. 실제 상황에서는 CI/CD 툴을 이용해서 관리하겠지만, 여기서는 Django 프로젝트를 배포하는 것에만 포커스 하도록 하겠습니다.
대략적인 서비스 구성은 아래 그림과 같습니다.
실제 서비스라면 database도 준비가 되어야겠지만.. 여기서는 docker에서 nginx와 python만 container를 띄울 예정입니다. 여기서 storage는 static 파일들이 들어갈 위치인데요, Django의 경우 운영환경에서는 django app은 wsgi (혹은 asgi) 형태로 서비스를 제공하며, static 파일들은 웹서버가 직접 처리해 주는 형태로 서비스를 하기 때문에, storage를 별도로 지정해 두었습니다.
외부에서 해당 서버에 요청이 오면, 먼저 nginx가 요청을 받아서 처리를 하게 되는데, static 파일 등은 nginx 가 알아서 처리해주고, 그 외에 django app이 처리해야할 부분만 reverse proxy 형태로 전달해 주게 됩니다.
Django 서비스 배포 절차
Django 서비스를 배포할 때는 다음과 같은 과정들이 필요합니다.
1. python 가상환경 준비 (처음 한번만 필요)
2. 소스 코드 업데이트
3. 의존 모듈 설치
4. static 파일 collect
5. model migration
6. django app service launch
다음에는 각각의 작업을 command로 살펴보겠습니다.
Django 서비스 배포 - 디렉터리 설정
위에서 언급하진 않았지만, 여기서는 아래와 같이 디렉터리를 설정해 보았습니다.
기본적으로 /opt/docker 폴더에서 각각의 서비스 컨테이너에 대한 폴더를 생성합니다. (nginx, python)
각 서비스 컨테이너 별로 루트에 docker-compose.yaml 파일들을 위치시키게 됩니다.
nginx 에 대해서는 설정파일들은 conf 폴더에 넣을 예정이고, static 파일들은 static 폴더에 넣어 두면 됩니다.
python 도 역시 설정파일들을 conf 폴더에 넣고, 그 외에 필요한 script들은 scripts 폴더에 보관합니다.
그 외에 python 에는 app 폴더를 생성할 예정인데요, app 폴더는 git clone 후에 rename 해서 생성할 예정입니다.
Django 서비스 배포 - 가상환경 준비
가상환경은 application 별로 독립된 python 환경을 구성하기 위해서 준비하는 과정인데, docker image를 빌드하는 경우에는 따로 독립된 환경을 사용하지 않아도 되지만, 여기서는 범용 python 이미지를 사용할 예정이기 때문에, local storage에 가상 환경을 생성해서 사용할 예정입니다. command는 다음과 같은데, 가상환경은 python docker container 안에서 실행할 예정입니다.
> python -m venv .venv
Django 서비스 배포 - 소스 코드 업데이트
소스코드 업데이트는 git pull (혹은 clone) 명령어로 처리가 가능합니다. 물론 먼저 서버에 git이 설치가 되어 있어야 합니다.
처음 소스를 받아올 때는 clone 명령어를 사용합니다.
> git clone <repository url>
추후에 업데이트 하는 경우에는 pull 명령어를 이용하면 됩니다.
> git pull
Django 서비스 배포 - 의존 모듈 설치
python에서 필요로 하는 모듈을 설치합니다. 로컬 개발환경에서는 간단히 pip 명령어로 처리가 되지만.. 여기서는 가상환경을 이용하기 때문에 아래와 같이 명령어를 입력합니다. 역시 python docker container 안에서 실행되어야 합니다.
모델을 migration 합니다. 실제로는 database 테이블을 생성 또는 변경 하는 과정입니다. 역시 python docker container 안에서 실행되어야 합니다.
> ./.venv/bin/python manage.py migrate
Django 서비스 배포 - django app service launch
django app 서비스를 실행합니다. 앞에서 잠시 언급한데로 wsgi 혹은 asgi 를 이용할 예정이기 때문에, 로컬 개발환경에서와는 조금 다른 명령어를 사용합니다. 역시 python docker container 안에서 실행되어야 합니다. 그리고 host를 지정하지 않으면 127.0.0.1 로 지정이 되는데, 컨테이너 외부에서 접속을 하기 위해서는 0.0.0.0 으로 바인드를 해줘야 합니다. port는 nginx에서 매핑할 예정이기 때문에 크게 중요하지 않아서 default 포트인 8000으로 지정해 주었습니다.
django app 이 asgi 형태로 실행되고 있어서, static 파일들 (css, image 등) 이 화면에 표현되지 않습니다.
테스트는 잘 실행이 되었지만, docker compose 실행이 제대로 되지 않는다면, 아마도 asgi 파일의 위치 문제일 가능성이 큽니다. run.sh 파일을 편집에서 해당 프로젝트에서 asgi 파일이 있는 경로를 지정해 주면 됩니다.
static 파일 문제를 해결하기 위해서 nginx를 셋업하도록 하겠습니다.
nginx 셋업
nginx도 docker로 이용할 예정이기 때문에 별다른 설치는 필요하지 않습니다.
nginx에서는 static 파일에 대한 요청을 처리하고, 나머지 요청은 django app으로 보내 줍니다. 실제로는 ssl 처리 등의 복잡한 기능이 필요하지만, 여기서는 아래와 같이 간단한 설정 파일을 준비합니다. 여기서 proxy_pass에 사용한 172.17.0.1 은 docker의 기본 host 주소 입니다. 원래는 docker network 을 이용해야 하지만, 간단한 예시이기 때문에, docker 기본 host 아이피를 적어주었습니다.
/opt/docker/nginx/conf/app.conf
server { listen 80; listen [::]:80; server_name localhost;
에러 내용은.. 허용되지 않는 host 라는 건데요.. 해결하기 위해서는 django app의 소스코드를 수정해야 합니다.
/opt/docker/python/app/config/settings.py 파일을 열어서 28번째 줄을 다음과 같이 변경합니다.
실제 운영환경에서는 이런 식의 소스 코드 변경을 막기위해서 이러한 설정 값은 모두 환경변수에서 읽어서 처리하게 해야 합니다.
그리고 python (django app) 의 docker 를 새로 띄우면, 아래와 같이 잘 나오게 됩니다.
눈치 빠르신 분은 이미 알아채셨겠지만.. 접속 주소가 localhost:8000 에서 localhost 로 변경이 되었습니다. nginx 가 연결을 해주기 때문에 이제는 localhost 로 접속하면 됩니다. 기존의 8000 포트는 iptables 혹은 firewall 등을 이용해서 외부에서 접근하는 것을 막아 두는 것이 좋습니다.
이번 포스트에서는 PGP Key가 어떤 것인지 알아보고, PGP키를 생성 / 삭제 및 PGP 키를 이용해서 데이터를 encrypt / decrypt 하는 방법 등에 대해서 소개하겠습니다.
PGP 키
먼저, PGP는 Pretty Good Privacy 의 약자입니다. Phil Zimmermann 에 의해서 개발되었습니다. 초창기 PGP는 보안에 민감한 데이터를 취급할 때, 데이터를 받는 쪽에서만 decrypt 할 수 있게 하기 위해서 사용되었습니다.
PGP key를 이용해서 데이터를 받는 과정입니다.
1. 데이터를 받는 쪽에서 먼저 PGP 키를 생성합니다.
2. 데이터를 보내주는 쪽에 생성한 public key를 전달해 줍니다.
3. 데이터를 보내는 쪽에서는 전달 받은 public key를 이용해서 데이터를 encrypt 합니다.
4. 데이터를 전송합니다.
5. 데이터를 받는 쪽에서는 private key를 이용해서 데이터를 decrypt 합니다.
PGP 키는 발급시 public key 와 private key 가 생성되는데, public key는 encrypt 시에 사용되며, private key는 decrypt 시에 사용이 됩니다. 이런 비대칭성 (asymmetry) 때문에, public key는 공개 되어도 상관이 없습니다.
PGP vs GPG
PGP 키를 발급하는 과정에서, 검색을 하다보면 PGP 대신 GPG 가 검색이 되는 경우가 많습니다. GPG는 GNU Privacy Guard 의 약자로 Open Source 버전의 PGP 라고 생각하면 됩니다. Ubuntu 에서 기본으로 제공이 되기 때문에, 이 포스트에서는 GPG 커맨드를 이용해서 키를 관리해 보도록 하겠습니다.
PGP 키 생성
먼저, PGP키를 생성해 봅니다.
아래 커맨드를 이용하면 키를 생성할 수 있습니다.
> gpg --full-generate-key
참고로, 옵션 중에 --generate-key, --quick-generate-key 를 사용해도 키를 생성할 수 있으나, 알고리즘, 키 사이즈 혹은 유효기간 등을 원하는 방식으로 설정하기 위해서는 --full-generate-key 옵션을 사용해야 합니다.
커맨드를 실행하면, 몇 가지 물어보게 됩니다. 위에서 잠시 이야기 했던, 알고리즘, 키 사이즈, 유효기간을 설정하면 됩니다.
위의 필수 정보를 입력하고 나면, PGP 키 사용자에 대한 정보를 입력해야 합니다. (Real name, Email address, Comment)
이 중에 Real name은 아이디라고 생각하면 되는데, local에서 사용할 경우, Real name 만 입력해도 문제 없습니다. public 사이트에 공개해야 하는 경우에는 Email address도 입력해서 unique 한 인증 정보를 입력해야 합니다.
저는 Real name을 testpgp 로 설정하였습니다. 마지막에 O를 입력해서 Okay를 하면, 키가 생성이 됩니다.
PGP 키 확인
PGP 키가 생성이 되면, 생성된 키들은 key storage에 위치하게 됩니다.
키를 확인하기 위해서는 아래 커맨드를 사용합니다.
Pubilc key 확인 (소문자 k)
> gpg -k
Private key 확인 (대문자 K)
> gpg -K
실제로 입력을 해보면, 아래와 같이 생성된 키 정보를 확인할 수 있습니다.
데이터 encrypt, decrypt
이번에는 public key를 이용해서 데이터를 encrypt 해보고, private key를 이용해서 encrypt 된 데이터를 decrypt 해보도록 하겠습니다.
테스트를 위해서 간단한 텍스트 파일을 하나 생성하였습니다.
아래 커맨드를 이용해서 test.txt 파일을 encrypt 합니다.
> gpg -o test.gpg -e -r testpgp test.txt
커맨드에서 사용한 옵션은 다음과 같습니다.
-o: output filename, 여기서는 encrypt 되서 출력될 파일을 "test.gpg" 로 지정하였습니다.
-e: encrypt, encrypt 하라는 의미입니다.
-r: recipent, 수신자로 public key 이름을 적으면 됩니다.
마지막으로 test.txt는 encrypt 할 입력 파일 이름입니다.
커맨드 실행 후에는 아래와 같이 test.gpg 파일이 생성되었습니다.
이번에는 decrypt 하는 커맨드 입니다.
> gpg -o test_decrypted.txt -d test.gpg
커맨드에서 사용한 옵션은 다음과 같습니다.
-o: output filename, 여기서는 decrypt 되서 출력될 파일을 "test_decrypted.txt" 로 지정하였습니다.
-d: decrypt, decrypt하라는 의미입니다.
마지막으로 test.gpg 는 encrypt 된 입력 파일 이름입니다.
커맨드 실행 후에 생성된 test_decrypted.txt 파일을 열어보면, test.txt 파일과 같은 내용이 있음을 확인할 수 있습니다.
PGP 키 내보내기
이번에는 키를 export 해보겠습니다. public 키와 private 키 각각 export 옵션은 다음과 같습니다.
Pubilc key export
> gpg --export -a -r testpgp -o testpgp_pub.asc
Private key export (password 입력 필요)
> gpg --export-secret-key -a -r testpgp -o testpgp_pri.asc
위의 커맨드를 실행해보면, public key는 "testpgp_pub.asc" 파일로, private key는 "testpgp_pri.asc" 파일로 생성이 됩니다.
PGP 키 삭제하기
이번에는 키박스에 저장된 키들을 삭제해 보겠습니다. public key와 private key가 같이 있는 경우에는 private key 부터 삭제 합니다.
Private key 삭제
> gpg --delete-secert-key testpgp
Public key 삭제
> gpg --delete-key testpgp
커맨드 실행 후, 확인 명령어로 키를 체크해 보면, 삭제된 것을 알 수 있습니다.
PGP 키 가져오기
마지막으로 export 해 뒀던 키를 키박스로 가져오는 커맨드 입니다.
Pubilc key import
> gpg --import testpgp_pub.asc
Private key import (password 입력 필요)
> gpg --import testpgp_pri.asc
위의 명령어를 실행하면 아래와 같이 출력되면서, 키박스에 키들이 저장됩니다.
그런데, 가져오기 후에 PGP 키를 확인해 보면, 아래와 같이 trust 상태가 unknown으로 나오게 됩니다.
trust 상태를 바꿔주기 위해서 --edit-key 옵션을 사용합니다.
> gpg --edit-key testpgp
명령어를 입력하면 아래와 같은 프롬프트 화면이 나오게 됩니다.
프롬프트에 "trust" 를 입력하고, trust 상태를 지정합니다. (여기선 5)
y를 입력해서 변경사항을 확인해 주면 저장이 완료됩니다.
마지막으로 quit 를 입력해서 프롬프트를 종료합니다.
pgp 키 정보를 확인해 보면 trust 상태가 "ultimate" 으로 변경된 것을 확인할 수 있습니다.
Django의 Proxy 모델은 하나의 데이터 모델을 가지고, 다른 인터페이스를 제공하는 클래스라고 생각하면 됩니다.
모델 상속의 경우, 각 subclass 에 대해서 새로운 데이터 테이블이 생성되지만, proxy 모델은 별도로 데이터 테이블을 생성하지 않고, 기존 테이블을 참조해서 사용하게 됩니다.
Proxy 모델의 정의
Proxy 모델은 원본 모델 클래스를 상속받아, Meta 클래스에 proxy 값을 True 로 설정해 주면 됩니다.
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
Proxy 모델 활용 - method 재정의
아래 예시에서는 원본 데이터 클래스인 Person 에서는, 인스턴스 출력시, DB에 저장되어 있는 이름을 그대로 출력되고, Proxy 클래스인 MyPerson 에서는, 인스턴스 출력시, 이름이 대문자로 변경되어서 출력됩니다. 같은 데이터를 참조하면서, method 를 다르게 정의해서 사용할 수 있습니다.
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def __str__(self):
return f"{self.firat_name} {self.last_name}"
class MyPerson(Person):
class Meta:
proxy = True
def __str__(self):
return super().__str__().upper()
Proxy 모델 활용 - manager: get_queryset
이번에는 proxy 모델과 manager를 이용해서, 원본 데이터를 필터링해서 관련 데이터만 사용하는 예시입니다.
먼저 SomeClass 라고 하는 데이터 모델을 정의하고, 세 개의 필드 status, title, content 를 정의합니다.이어서 정의할 manager 클래스에서는 SomeClass의 status 값에 따라 원본 데이터를 필터해서 돌려 줄 수 있게 처리합니다. 그리고 마지막으로 proxy 모델들은 원본 데이터 클래스 (SomeClass) 를 상속하고, objects 에 해당 manager 클래스의 인스턴스를 지정해 줍니다.
from django.db import models
STATUS_TYPES = (
('n', 'New'),
('i', 'In Process'),
('c', 'Completed'),
)
# 원본 데이터 클래스
class SomeClass(models.Model):
status = models.CharField(max_length=1, choices=STATUS_TYPES)
title = models.CharField(max_length=100)
content = models.TextField(blank=True, null=True)
# 매니저 클래스
class NewStatusManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='n')
# 원본 데이터 테이블에서 status 가 'n' 인 데이터만 가지고 오는 proxy 클래스
class NewStatusSomeClass(SomeClass):
objects = NewStatusManager()
class Meta:
proxy = True
# 매니저 클래스
class InProcessStatusManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='i')
# 원본 데이터 테이블에서 status 가 'i' 인 데이터만 가지고 오는 proxy 클래스
class InProcessStatusSomeClass(SomeClass):
objects = InProcessStatusManager()
class Meta:
proxy = True
# 매니저 클래스
class CompletedStatusManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='c')
# 원본 데이터 테이블에서 status 가 'c' 인 데이터만 가지고 오는 proxy 클래스
class CompletedStatusSomeClass(SomeClass):
objects = CompletedStatusManager()
class Meta:
proxy = True
Proxy 모델 활용 - manager: create
이번에는 proxy 클래스로 신규 레코드를 생성하는 케이스에 대해서 정의해 보겠습니다.
Proxy 모델에 연결된 manager 클래스에 create method 를 재정의해서, Proxy 형태를 구분하는 컬럼의 값을 지정해 줄 수 있습니다.
먼저 프로세스를 검색해 보겠습니다. 메모장(notepad)를 하나 열고 아래 명령어를 입력합니다.
Get-Process -Name Notepad
아래와 비슷한 결과가 출력될 것입니다.
이름으로 프로세스를 검색하는 경우에는, -Name은 생략이 가능합니다.
만약 프로세스 아이디를 알고 있다면 아래처럼 입력하면 됩니다. (여기서는 아이디가 4556)
Get-Process -Id 4556
정확한 프로세스 이름을 모른다면 와일드카드(*)를 이용할 수 있습니다.
Get-Process -Name Note*
위의 출력결과에서 Id만 화면에 출력하고 싶다면, 아래와 같이 입력합니다.
Get-Process -Name Notepad | Select-Object Id
여기서 | (pipe) 는 바로 앞의 커맨드의 결과를 받아서 처리하라는 의미이고, Select-Object는 프로세스 리스트에서 Id 만 선택해서 보여주라는 의미 입니다.
결과는 아래처럼 출력됩니다.
결과를 리스트로 받기 위해서는 아래와 같이 ExpandProperty 옵션을 추가합니다.
Get-Process -Name Notepad | Select-Object -ExpandProperty Id
파이프 기능을 활용하면 이전 결과를 필터 할 수도 있습니다. 아래 명령어는 Where-Object 기능을 이용해서 전체 프로세스 리스트 중에 ProcessName이 Notepad 인 것만 필터해서 출력해 줍니다. 즉, 앞에서 입력했던 Get-Process Notepad 와 같은 결과가 나옵니다. 여기서 $_ 는 이전 결과 리스트의 아이템(원소)를 의미합니다.
먼저 map 의 예시입니다. map은 리스트를 받아서 리스트를 돌려줍니다. 아래 예시는 [1, 2, 3, 4, 5, 6, 7, 8, 9] 리스트를 생성해서 각 원소에 2를 곱한 결과를 다시 리스트로 반환합니다. 결과는 [2, 4, 6, 8, 10, 12, 14, 16, 18] 이 됩니다.
1..9 | ForEach-Object -Process {$_ * 2}
위의 예시에서 -Process 는 생략가능하며, ForEach-Object 대신 %{$_ * 2} 을 사용해도 같은 결과가 리턴됩니다.
이번에는 reduce 예시입니다. reduce는 리스트를 받아서 연산한 결과값을 돌려줍니다. 아래 예시는 [1, 2, 3, 4, 5, 6, 7, 8, 9] 리스트를 생성해서 원소들의 합을 구해서 돌려줍니다. 결과는 45 가 됩니다.