이번 시간에는 앞 시간의 플라스크(flask) 시간에 이어서, 파이썬을 대표하는 웹 프레임워크로 알려져 있는 장고(Django) 를 살펴보는 시간을 가지려고 한다. 플라스크를 살펴볼 때와 비슷한 방식으로 공식 메뉴얼을 기준으로 전체적인 장고의 구조에 대해서 살펴보고, 플라스크에서 구현했던 2개의 예제(MSSQL 테이블 표시, d3.js 그래프 그리기)를 장고 환경에서 똑같이 구현하는 과정을 보여 줌으로서, 플라스크와는 어떤 다른 측면들이 있는지를 설명하려고 한다.
[목차]
0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?
1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?
2. 파이썬 설치와 환경, 버전 선택 하기의 이유.
3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기
4. 데이터 베이스에서 내용 가져와 출력하기
5. 암호화 모듈을 이용해 암복호화 해보기
6. 퍼즐 조각들을 합쳐보기
7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기
8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기
9. 입력과 결과를 GUI 화면과 연결해 보기
10. Whois API 이용해 보기
11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)
12. 자동화 - 웹 자동화(with Selenium)
13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)
14. 자동화 - 작업 자동화
15. 수학 라이브러리 살펴보기
16. 그래픽 라이브러리 살펴보기
17. 머신러닝에서의 파이썬의 역활
18. 웹 프로그래밍 - Legacy Web
19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)
20. 웹 프로그래밍 - Django 살펴보기
21. 정리 - 이런저런 이야기
[들어가면서]
우선 시작하기 전에 혹시 19교시의 플라스크(flask)에 대해서 보지 않은 상태라면, 먼저 해당 내용을 보고 오기를 바란다. 장고를 살펴본 결과 플라스크와 대비되는 부분을 짚어가며 설명하면 효율적이라는 생각이 들었다. 플라스크에서 개념을 설명했던 url routing(url 라우팅), static files(정적 파일), rendering templates(템플리트 랜더링)의 개념은 거의 동일 하게 사용되기 때문이다. 또 템플릿 소스도 일부만 수정해 그대로 사용할 계획이다. 이번 시간에는 ORM, MVT(MVC), ADMIN 등 장고에 특화된 개념만 추가로 설명할 계획이다. 개인적으로 플라스크를 살펴볼때 보다 2~3배 정도의 시간이 장고에 소요됬었고, 플라스크를 이해한 상태라면 장고도 비교적 접근하기 수월하다는 생각이 들었다.
[한글 문서 여부]
역시 플라스크와 비슷하게, 구글을 찾다보면 아래의 한글 페이지가 있긴한데, 제목까지만 다 번역되어있고, 실제 내용은 튜토리얼 까지만 번역되어 있다. 다만 튜토리얼에 전체 구조를 이해시키는 핵심적인 얘기들이 많으므로, 튜토리얼을 한글로 보며 대충 돌아가는 상황을 파악후, 영문 문서들을 그담에 보길 권장한다.
https://django-document-korean.readthedocs.io/ko/master/
또 다른 튜토리얼 레벨의 괜찮은 한글 문서는 구글에서 'django sample' 로 찾으면 나오는 아래의 장고걸 사이트 문서이다. 개인적으로 봤을때 체계적으로 잘 정리되어 있어 처음에 개념을 잡는데 도움이 됬었다. 파이썬 버전도 3.5 버전 기준이라 문법에 위화감도 없다.
https://tutorial.djangogirls.org/ko/django_start_project/
그리고 마지막으로 영문 예제 사이트는 아래 사이트가 괜찮았다. 여기도 마찬가지로 3.5.x 버전이고, 위의 3개 사이트의 내용을 보면 대충 장고 사이트가 어떻게 돌아가는지에 대해 전체적으로 감이 잡히기 시작할 것이다.
https://scotch.io/tutorials/build-your-first-python-and-django-application
[Django Documentaton 보기]
구글에서 'django document' 라고 검색하면 아래의 문서가 나오는데, 현재 최신이 1.11 버전(이게 '일점 일일' 버전이 아니라 '일점 십일' 버전이다. 첨에 이것땜에 구글을 검색하때 1.7, 1.8 같은 미래 버전이 왜 있지 하고 좀 헷깔렸다--;)의 공식 메뉴얼 페이지를 볼수 있다.
https://docs.djangoproject.com/en/1.11/
'First steps' 안에 있는 Overview, Installation, Tutorial 에는 장고를 이용해 사이트를 세팅하는 것부터, 라우팅(URLconfs. 라고 보통 말한다)을 설정하는 법(urls.py), 모델을 만들어 해당 구조를 데이터베이스 및 어드민 기능과 싱크(migration) 시키는 법, 뷰와 템플릿을 사용하여 표시하고, 폼을 전송하여 받아 처리하고, 정적(static) 파일들을 설정하고 접근하는 법에 대한 전체적인 흐름을 보여준다.
'The model layer' 에서는 모델을 상세하게 다루어, 모델에 대해 어떻게 클래스로 정의하고, 정의된 모델들을 지원하는 메쏘드들을 이용해 쿼리를 요청해 값을 가져오고(QuerySet), 데이터베이스와 모델을 어떻게 싱크시키고(Migrations), 모델을 벗어난 커스텀 쿼리를 데이터베이스에 어떻게 날리며(Raw SQL), 데이터베이스별로 모델을 적용하는데 필요한 여러가지 참고사항과 주의사항들을 얘기한다.
'The view Layer' 에서는 어떻게 라우팅을 구성 하며(URLconfs), 어떻게 요청(request)을 하고받아서, 모델에 대한 검색을 지원하는 QuerySet 를 이용하여 결과를 가져와서, 어떻게 응답을 하는지(Requsest and response objects), 파일 업로드를 구현하는 법(File upload), 내장된 뷰(generic view)를 이용하는 방법(Built-in display view) 등을 다룬다.
'The template layer' 에서는 템플릿에서 여러가지 장고에서 지원하는 템플릿 지원 로직들을 사용하여 표현하는 방법을 얘기한다(for 라든지 url 이라든지 여러 문법들을 사용하는 방법을 얘기하는데, 앞의 플라스크와 비슷하지만 조금 더 확장된 기능이라고 보면 될듯 하다)
이후 'Forms' 에서 폼을 넘기고 처리하는 부분을, 'The Admin' 에서 모델과 연관되어 자동으로 업데이트 되는 관리 페이지를 커스터 마이즈해 사용하는 부분을, 'Security' 에서 잘 알려진 보안 이슈들에 대응하는 설계를 구현하는 방법을, 'Common Web application tools' 에서 웹사이트를 개발하면서 주로 만나게 되는 세션, 캐싱, 스태틱 파일, 사이트맵 등등에 대한 구현을 지원하는 기능들을 얘기한다. 그 외의 섹션에서는 유니코드라든지, 로케일이라든지, 개발, 테스팅 방법이라든지 하는 여러 내용들을 다루고 있다.
위에서 얘기했던 내용들을 이해한대로 그려보면 아래의 그림과 같다. 브라우저가 폼이나 API를 통해 요청을 하면, 장고의 웹 모듈이(WGSI - 메뉴얼에 이 기능은 테스트에만 사용하고 운영시에는 아파치 등을 연동해 쓰라고 명시되어 있다) 요청을 받아, 라우팅(URLConfs) 기능을 통해 해당되는 뷰의 콜백 함수에 전달한다.
모델은 ORM(Object-relational mapping)이라는 패턴 기법을 이용해 데이터베이스를 가상의 프로그래밍적 객체로 모델링하여 정의하고, migrate 명령어를 이용해, 실제 데이터베이스에 테이블을 만들거나, 수정하여, 장고와 데이터베이스 사이의 구조를 싱크(뭐 migration 이 한 방향의 의미긴 하지만 넓게 보면 싱크 개념인것 같다) 시킨다. 또한 데이터 베이스에 SQL 문을 날리듯, 모델에서 QuerySet 이라는 검색용 메써드를 제공해, ORM 객체로부터 데이터를 조회해오게 한다(select, where, order by, join 등을 실제 비슷하게 구현한다. 앞에 진행했던 시간을 생각해 보면, 'Panda' 가 가상의 메모리 객체(dataframe)를 만들어서 비슷한 행동을 했었다).
양 쪽을 싱크하는 과정에서 어드민에서 필요한 몇몇 테이블도 데이터베이스 안에 들어가게 되고, Admin 쪽에는 모델에서 정의한 ORM 객체들을 살펴보고, 데이터를 넣거나, 수정하거나, 지우거나 하는 관리 행동을 할수 있는 기본적인 인터페이스가 자동으로 싱크되어 구현된다.
이러한 모델에서 만들어진 ORM 개체들은 뷰에서 QuerySet 을 이용하여 호출되어, 사용자가 요청한 조건에 적합하도록 가공되며, 템플릿과 조합되어 동적인 UI를 생성해 제공되거나, Json 응답 등으로 템플릿과 상관없이 독립된 형태로 사용자에게 응답을 줄 수 있다. 템플릿은 기본적으로 HTML 베이스로 구성된 응답(response)을 위한 기본 문서이며 여러가지 장고에서 제공되는 템플릿용 지원 기능 들과 CSS, Javascript 같은 스태틱 파일들을 이용하여 적절한 UI 를 구성하여 사용자에게 처리결과를 보여 주게 된다.
위의 그림중에 대부분의 요소들은 앞의 레거시 웹이나, 플라스크(flask) 살펴보기 시간에 얘기했던 주제들이거나, 추후 메뉴얼을 보면서 자세히 항목들을 살펴봐야 될 주제들 같아서, 이 시간에는 ORM 과 그에 연관된 주제들(QuerySet, Migration), 그리고 장고의 뼈대를 구성하는 MVT 라는 패턴 구조에 대해서 설명한 후 나머지 플라스크와의 자잘한 차이는, 실제 샘플을 구현하면서 중간 중간 얘기하려 한다.
[ORM]
ORM 은 앞에서도 얘기했지만, 데이터베이스의 테이블의 구조와 관계를 클래스와 프로퍼티(속성), 메쏘드를 이용해, 객체의 관계로서 모델링 하는 기법이다. 구글에서 'orm pros and cons' 나 'orm 장단점' 이라고 검색하면, 아래와 같은 많은 페이지가 나오면서 난상 토론을 보게 되는데,
https://stackoverflow.com/questions/494816/using-an-orm-or-plain-sql
https://stackoverflow.com/questions/35955020/hibernate-orm-framework-vs-jdbc-pros-and-cons
https://gs.saro.me/#!m=elec&jn=718
http://layered.tistory.com/entry/ORM%EC%9D%80-%EC%95%88%ED%8B%B0%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%8B%A4-ORM-is-an-antipattern
https://okky.kr/article/286812
ORM 사용에 대한 판단은 각각 하는게 맞겠지만, 몇 가지 생각해 봐야할 것 같은 부분들이 있다. 우선 1번째는 'ORM' 은 'ODBC' 나 Selenium 의 'webdriver' 처럼 원소스 멀티유즈를 표방한다. 그러다 보니, 같은 모델이 여러 데이터베이스와 100% 호환된다는 보장은 사실 힘들것 같다(메뉴얼의 'The model layer' 의 뒷 부분이 이런 차이들과, 모델로서 해결하기 힘든 경우 raw sql 을 사용하는 부분들을 가이드 하고 있다). 데이터베이스마다 미묘한 문법차이나, 설계 차이가 있을 수 있고, 데이터베이스 버전 별로 100% 호환이 되도록 충분히 지원하고 테스트 되었다고 보기 힘들 수 있다. 마치 셀레늄에서 webdriver 의 종류에 따라 서로 다른 브라우저 끼리의 동작이 미묘하게 달랐던 것 처럼 말이다. 그래서 가능한 ORM 을 쓸 경우, 해당 프레임워크가 가장 기본으로 지원하는 데이터베이스를 사용하는게 업데이트도 계속 지원되고, 호환이 안되어 곤란한 일이 안 생길 것 같다(예를 들어 Django 는 PostgreSQL, MySQL, Oracle, Sqlite3 를 공식적으로 메뉴얼에서 다룬다).
2번째는 ORM 이 데이터베이스에서 구조와 성능을 위해서 지원하는 주요 기능 요소들을 충분히 모델상에서 포함하고 있는지에 대해서이다. 장고에서 기본적으로 하나의 모델 객체는 하나의 실제 테이블에 마이그레이션이되어 매칭 되는 구조인데, 실제 복잡한 사이트에서 효율적인 설계가 해당 방식으로 100% 이루어질 수 있는지를 체크해 봐야한다(현실에서는 수많은 테이블이 서로 join 등으로 엃혀있는 관계를 가진 경우가 많아서, ORM 모델로 구현시 가독성이나, 복잡도가 감당할만 한지 등을 따져봐야 할 것 같다)
3번째는 레거시 데이터베이스를 사용하는 경우이다. 관계형 데이터베이스의 특성상 많은 테이블들이 서로 연관을 가지면서 파편화 되있을 가능성이 많은데, 해당 부분을 현재의 모델로 흡수하여 구현하게 되면, DB프로시저 등을 생산해서 추가적인 중계가 필요할 수도 있고, 그런 경우엔 장고의 ORM 에서 구현한 장점 중 하나인 마이그레이션 기능을 원활히 사용할수 없을 가능성이 높아진다.
4번째는 DBA 인력들과의 협업이다. 개발 쪽에서 독자적으로 데이터베이스를 관리하고 책임지는 구조라면 모르겠지만(뭐 요즘 유행인 DevOps 등의 조직에선 모르겠다), DB 관련 팀 쪽에서 데이터베이스의 성능, 테이블 스키마 관리 등을 책임 지고 있다면, 만들어진 모델을 검증하기 위해서, DBA 인력이 장고의 ORM 을 구현한 클래스들과 QuerySet 구성을 이해하고, 해당 부분이 실제 현재의 데이터베이스에 어떤 영향을 주는지 모델의 변경이 생길때마다 매번 검증을 해야하는 상황이 생기게 되는데, 이런 방식이 가능한 시나리오인지는 의문이 생긴다.
마지막으로는, 장고 쪽에 마이그레이션 기능을 위해, 데이터베이스의 스키마를 자동으로 바꿀 수 있는 권한을 주는 부분이 보안적으로 적절한가에 대한 고려와, Admin 의 요구사항이 경험적으로 단순히 모델 객체에 데이터를 넣거나 편집하는 것으로 단순하게 이루어지진 않기 때문에 자동화된 어드민이 실제 얼마나 유용할까에 대한 의문이다.
결론적으로 적절히 우호적인 환경에서는 장고의 ORM 을 사용해 데이터베이스를 마이그레이션 하여 관리하는 것도 좋지만, 해당 경우 발생할 여러가지 반대 급부를 생각해 봐야 하며, ORM 을 사용하는 것 자체가 개발자에게 데이터베이스를 덜 이해해도 된다는 면죄부가 되는 것은 아니라는 것을 얘기하고 싶다. 오히려 개인적인 생각에는 ORM 을 사용해 모델을 설계해서 테이블과 싱크 시키고 싶다면, 장고의 모델과 QuerySets, 마이그레이션 쿼리, 객체지향 설계를 잘 이해하고, 동시에 사용하는 데이터베이스의 여러 성능 요소를 결정하는 미묘한 특징들에 대해서 잘 이해해야지만 충분히 규모가 커져도 유지보수가 가능한 좋은 설계가 나오지 않을까 싶다. 뭐 그냥 개인적인 생각이라는 것을 꼬리로 단다.
[MVT]
MVT(Model, View, Template) 패턴은, 기존에 많이 쓰이는 용어가 MVC 이니 두 개를 비교해 보면서 살펴보도록 하자, 구글에서 'mvc vs mvt' 로 검색하면 아래의 페이지가 나온다.
https://stackoverflow.com/questions/6621653/django-vs-model-view-controller
If you’re familiar with other MVC Web-development frameworks, such as Ruby on Rails, you may consider Django views to be the controllers and Django templates to be the views.
해당 내용 중 위와 같은 내용이 있는데, MVC 의 model 은 장고에서 설명한 구조와 거의 같이 데이터베이스와 매핑되는(사실 어떻게 보면 웹 프로그램이라서 그렇지 원래 ORM 은 굳이 매핑되는 대상이 데이터베이스일 필요는 없는것 같다) 부분을 얘기하고, 컨트롤러(controller)는 장고나 플라스크의 url 요청을 받아 해당되는 함수에 연결해 주는 라우팅 부분을 얘기한다. 그리고 뷰(View)는 실제 모델로부터 데이터를 받아서 보여주는 역활을 한다.
그래서 장고의 경우 위의 그림에서 그렸듯이, 뷰에 URLconfs 기능이 있기 때문에 MVT의 뷰는 컨트롤러 개념을 가지고 있다고 하고(사실 뷰에서 템플릿을 사용안하고 바로 응답값을 줄수도 있기때문에, 템플릿과 뷰 개념을 같이 가지고 있다고 보는것도 맞을듯 싶다), 실제 상으로 템플릿에서 모든 결과를 보여주기 때문에 MVT 의 템플릿은, MVC 의 뷰와 같다고 말하는 것이다. 사실 좀 말장난 같은 요소가 있으며, 기능이 어느 편에 붙었는지에 상관없이 전체적인 기능 요소들은 거의 동일 하므로, 두 개가 사실상 같은 개념이라고 봐도 무방할듯 싶다(언어로 따짐 사투리 관계인데 뭐가 표준어인지는 모르겠다^^;).
[사전 준비 - Django 설치]
파이썬 3를 지원하므로 pip 명령어로 설치하면 된다.
c:\Python\code>pip install django
Collecting django
...
Successfully installed django-1.11.4
[DB 에서 데이터 불러와 HTML 테이블로 보여주기]
그럼 본격적으로 장고를 이용해서 앞의 시간에 플라스크로 구현해 봤던, 4교시에서 만들었던 예제를 응용한, MSSQL 데이터베이스에서 데이터를 불러다 HTML 테이블 형태로 웹에 표시하는 부분을 구현해 보기로 하자. ORM 모델을 무시하고, 4교시와 비슷하게 pymssql 모듈을 사용하여 모델을 무시하고 호출하는 것도 가능하겠지만, 그러면 장고의 모델 부분을 살펴 볼수 있는 기회가 없어지기 때문에 ORM 을 최대한 이용하도록 구현을 해보려고 한다.
[장고의 모델에서 MSSQL 을 지원하게 해주는 모듈 찾기]
우선 공식 메뉴얼에는 MSSQL 지원에 대해서 언급된 내용이 없으므로, 구글에서 'django mssql' 이라 검색해서, 아래의 페이지를 찾는다.
https://django-mssql.readthedocs.io/en/latest/
스토어드 프로시저(stored procedure)도 호출할 수 있고, 메뉴얼이랑 사용법등이 그런데로 괜찮은듯 한데, 지원하는 버전을 보니, django 1.8 까지 지원한다. 적용하면 돌아갈것 같긴한데 이젠 지원이 끊긴듯 해서 찜찜하다. 그래서 다시 다른 페이지를 찾다보니 아래 레딧 페이지에 'django-pyodbc-azure' 를 쓰라는 조언이 있다.
https://www.reddit.com/r/Python/comments/4iq7zb/django_19_with_mssql_as_backend/
그래서 다시 구글을 검색해, 해당 모듈의 깃허브 페이지로 가니, 장고 1.11 최신버전과 MSSQL 2016 까지 지원해 주는, 현재 활발히 유지되고 있는 모듈이 있다.
https://github.com/michiya/django-pyodbc-azure
그럼 설치 가이드에 있는 것처럼, pyodbc 와 django-pyodbc-azure 를 각각 설치해 보자(ODBC 개념은 4교시때 간단히 설명했다)
c:\Python\code>pip install pyodbc
Collecting pyodbc
...
Successfully installed pyodbc-4.0.17
c:\Python\code>pip install django-pyodbc-azure
Collecting django-pyodbc-azure
...
Successfully installed django-pyodbc-azure-1.11.0.0
[이미 만들어진 스키마를 장고 모델로 가져오는 방법 찾기]
이제 모듈이 설치되었는데, 모듈이 잘 동작하는지 보려면, 장고 모델을 만들어서 마이그레이션으로 MSSQL 데이터베이스와 싱크를 해야될 것 같다. 그런데 다시 supermarket 테이블에 대한 모델을 만들어, 마이그레이션을 시키고 다시 장고의 어드민이나 SQL Management Studio 를 통해 데이터를 넣고 하는게 귀찮기도 하고, 기존 레거시 데이터베이스를 사용하는 경우 모델을 어떻게 구성하나도 궁금하기도 해서 구글에 'django pre existing database' 라고 검색해 아래의 메뉴얼 페이지를 찾았다. 근데 다행히도 설정파일에 연결문자열만 맞춰 놓으면, manage.py 의 inspectdb 명령어를 사용하면, 데이터베이스의 테이블을 자동으로 읽어와서, 해당 되는 테이블 구조에 맞는 model.py 파일을 만들어 준단다. 그럼 조금 뒤에서 모델을 만들때 이 기능을 사용하기로 해본다.
https://docs.djangoproject.com/en/1.11/howto/legacy-databases/
[프로젝트 만들기]
일단 앞의 플라스크와 마찬가지로 가상환경을 쓸 필요는 없을 듯해 virtul env 설정은 생략하고, 앞의 장고걸의 예제들을 따라해 보다보니 설정 문제인지 현재 환경에서(windows 10) 일부 명령어 실행에 에러가 나서, 첨에 소개한 아래의 영문 설명 페이지를 기준으로 virtual env 설정 하는 부분과 migration 하는 부분만 제외하고 적당히 따라해 보기로 했다.
https://scotch.io/tutorials/build-your-first-python-and-django-application
먼저 프로젝트를 생성한다. 아래의 명령어를 치면, c:\python\code 밑에 djangoweb 이라는 프로젝트를 만든다. 뭐랄까 GUI 메뉴를 사용하는 방식은 아니지만, 이제 부터는 비주얼 스튜디오나 이클립스 등으로 프로젝트를 만드는 느낌으로 따라오면 된다.
c:\Python\code>django-admin startproject djangoweb
이렇게 되면 메뉴얼에도 나오지만 아래와 같이 c:\python\code 폴더 아래 djangoweb 이란 폴더가 생성되면서, 아래와 같은 서브폴더와 파일 구조를 가지게 된다.
|
djangoweb/
manage.py
djangoweb/
__init__.py
settings.py
urls.py
wsgi.py |
cs |
뭐 각각의 파일이 모두 나름의 역활은 있지만, 자세한건 메뉴얼을 보고, 현재 시간에 의미 있는 요소들만 얘기하면,
1) mamage.py - 어플리케이션(프로젝트 안에서 실제 돌아가는 프로그램 모듈 1개를 얘기한다)을 만들거나, 데이터베이스에 모델 정보를 마이그레이션 하거나 하는 등의 여러 프로젝트의 관리에 필요한 기능들을 모아논 장고 전용의 작은 파이썬 프로그램이라고 보면 된다.
2) settings.py - 프로젝트 전반에 필요한 설정들을 저장한 파일. 데이터베이스 연결 문자열, 사용하는 어플리케이션들의 정의, 로케일, 스태틱 파일 경로, wsgi(web server gateway interface - 사용자로부터의 웹 요청을 처리하는 모듈) 지정 등등이 들어가 있다고 보면 된다/
3) urls.py - 1차 라우팅 설정이 들어 있다. 1차라고 하는 이유는 나중에 어플리케이션을 만들면 또 그 안에 라우팅 설정이 또 있다. 들어온 요청을 각 어플리케이션에 분배하는 1차 라우팅 단계라고 보면 된다.
[프로젝트 안에 어플리케이션 만들기]
그 담에는 어플리케이션을 만들기 위해서, 'cd' 명령어를 이용하여 새로 만들어진 'djangoweb' 폴더 안으로 이동하여 'supermarket' 테이블에서 조회해서 보여줄 어플리케이션을 생성하는 명령어를 실행 한다(뭐 복잡해 보인다고 생각할지 모르지만, 어차피 장고를 사용하려면 정해져 있는 방식이라서 그대로 따라해야 되는 부분이다).
c:\Python\code>cd djangoweb
c:\Python\code\djangoweb>python manage.py startapp supermarket
이 후에는 내부의 폴더에 supermarket 폴더가 생기며 안에 담긴 파일들은 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 |
djangoweb/
manage.py
djangoweb/
__init__.py
settings.py
urls.py
wsgi.py
supermarket/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py |
cs |
역시 이번 시간에 주로 의미가 있는 파일들만 언급하면,
1) models.py - ORM 모델들이 클래스로 정의되어 있는 파일이다. 현재는 빈 어플리케이션을 만들었기 때문에 안을 보면 내용이 비어있다.
2) views.py - 뷰가 정의되어 있는 파일이다.
-> 위의 생성된 리스트를 보면 좀 이상하다라고 생각할수 있는데, 'templates' 폴더나, 어플리케이션 용 'urls.py' 파일은 안보인다. 뒤에서 보겠지만, 해당 파일은 수동으로 생성해 주어야 된다.
[프로젝트 파일 설정에 어플리케이션 추가]
가이드에 나와 있는데로, 프로젝트의 셋팅 파일에, 새로 만든 supermarket 어플리케이션을 인식할 수 있도록 추가해 주자. c:\Python\code\djangoweb\djangoweb\settings.py 파일에서 아래 부분을 추가해준다.
|
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'supermarket',
]
|
cs |
[프로젝트 라우팅에 어플리케이션 쪽 라우팅 추가]
그 다음엔 '프로젝트의 urls.py' 를 수정해 요청이 들어왔을때, 요청의 처리를 'supermarket 어플리케이션' 쪽에 위임하도록 해보자. c:\Python\code\djangoweb\djangoweb\urls.py 파일에서 아래 import 문과, supermarket.url 항목 부분을 추가해 준다. url 쪽이 플라스크와 하나 다른건 8교시때 배웠던 정규표현식으로 파싱을 한다는 것이다. 플라스크 처럼 사용하려면 '^원하는문자열$'(시작과 끝 사이에 원하는 문자열만 있음) 패턴으로만 url 을 정의해 쓰면 될듯 하다. 아래에서는 ^(시작하자마자) 후에 바로 supermarket.urls 로 라우팅 되기 때문에 결국 supermarket 쪽으로 전체 라우팅 제어권을 넘기는 것과 마찬가지가 된다.
|
from django.conf.urls import include
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('supermarket.urls')), |
cs |
[어플리케이션 라우팅 파일 만들기]
자 그럼 이제 프로젝트에서 supermarket 어플리케이션으로 던진 경로를 해석해주는 어플리케이션 라우팅 부분이 있어야 한다. c:\Python\code\djangoweb\supermarket\urls.py 을 메모장에서 만들어서 아래의 내용을 넣어 utf-8 인코딩으로 저장한다(혹시 한글 주석을 달려면 utf-8로 파일이 저장되어야 장고 실행시 에러가 안 난다). 안의 내용은 일단 아직 뭐가 뭔지 잘 모르기 때문에, 위의 튜토리얼의 예제 내용을 그대로 넣어보자. 웹 서버 루트 경로(^$-시작과 끝 사이에 아무 값도 없음)를 호출했을 경우, 앞서 잠시 얘기한 장고에서 제공하는 generic view 를 사용하여 보여주는 형태 이다(뭐 장고가 잘 돌아가는지만 보려 하는거니 상세한 문법은 넘어가기로 하자)
|
from django.conf.urls import url
from supermarket import views
urlpatterns = [
url(r'^$', views.HomePageView.as_view()),
]
|
cs |
[어플리케이션 뷰 파일에 샘플 뷰 추가하기]
자 그럼 이제 어플리케이션에서 해당 되는 라우팅에 대한 샘플 뷰를 만들어 보자. c:\Python\code\djangoweb\supermarket\views.py 파일에서 아래의 내용을 추가한다. 대충 문법을 보면 generic view 중 TemplateView 라는 것을 이용해 index.html 이라는 템플릿 파일을 지정했다.
|
from django.shortcuts import render
from django.views.generic import TemplateView
# Create views
class HomePageView(TemplateView):
def get(self, request, **kwargs):
return render(request, 'index.html', context=None) |
cs |
[어플리케이션 뷰 파일에 해당 되는 템플릿 만들기]
마지막으로 템플릿 파일을 만들면 된다. c:\Python\code\djangoweb\supermarket\ 폴더에 templates 폴더를 생성한다(c:\Python\code\djangoweb\supermarket\templates\). 이후 해당 폴더안에 c:\Python\code\djangoweb\supermarket\templates\index.html 을 만들어 저장한다. 역시 한글 주석을 위해서는 utf-8 인코딩으로 저장하자.
|
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>장고샘플</title>
</head>
<body>
<h1>동작함</h1>
</body>
</html> |
cs |
[샘플 페이지 실행]
그럼 다시 c:\Python\code\djangoweb 경로에서 아래의 명령어를 실행해 보자(manage.py 를 실행 시켜야 해서 실행 경로가 맞아야 한다)
c:\Python\code\djangoweb>python manage.py runserver
Performing system checks...
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
...
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[05/Aug/2017 17:30:55] "GET / HTTP/1.1" 200 222
뭐 기본 기능에 대한 마이그레이션이 안됬으니 어쩌니 얘기가 나오긴 하지만, 해당 부분은 무시하고 일단 웹페이지를 띄워, http://127.0.0.1:8000/ 을 호출하면 아래와 같이 웹페이지가 동작함을 볼 수 있다.
참고로 현재 까지의 파일 구조는 아래와 같다. 추가된 파일과 폴더들은 색으로 표시했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
djangoweb/
manage.py
djangoweb/
__init__.py
settings.py
urls.py
wsgi.py
supermarket/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
templates/
index.html
urls.py
views.py |
cs |
[MSSQL 연결 문자열 추가하기]
MSSQL 연결 문자열을 추가하기 위해(앞의 'django-pyodbc-azure' git 페이지의 문서를 참고했다), 프로젝트 내의 c:\Python\code\djangoweb\djangoweb\settings.py 을 열어 DATABASE 설정 부분을 디폴트인 sqlite3 에서 MSSQL 로 수정한다.
<원본>
|
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
|
cs |
<수정>
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
DATABASES = {
'default': {
'ENGINE': 'sql_server.pyodbc',
'NAME': 'mytest',
'USER': 'pyuser',
'PASSWORD': 'test1234',
'HOST': 'localhost',
'PORT': '1433',
'OPTIONS': {
'driver': 'ODBC Driver 13 for SQL Server',
},
},
}
|
cs |
[모델 자동으로 만들기]
앞에서 봤던 레거시 테이블을 자동으로 만드는 inspectdb 명령어를 이용하여 model.py 파일을 만들어 본다.
https://docs.djangoproject.com/en/1.11/howto/legacy-databases/
일단 제대로 연결 문자열이 설정됬나 보기 위해서 아래의 명령어를 실행해 본다. 잘은 모르겠지만, 대충 출력된 내용을 보니 해당 데이터베이스안에 기존에 만들어 놓은 'Play' 와 'supermarket' 테이블을 가져오고, 필드 이름들도 정상적으로 파싱해 가져오는거 같다.
c:\Python\code\djangoweb>python manage.py inspectdb
# This is an auto-generated Django model module.
....
class Play(models.Model):
...
class Meta:
managed = False
db_table = 'play'
class Supermarket(models.Model):
itemno = models.IntegerField(db_column='Itemno', blank=True, null=True) # Field name made lowercase.
...
price = models.IntegerField(db_column='Price', blank=True, null=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'supermarket'
그럼 models.py 파일을 만들어 내기 위해서, 아래의 명령어를 실행한다. inspectdb 를 하여 출력된 내용을 리다이렉션('>') 을 이용해서 기존의 자동 생성된 models.py 파일을 덮어쓰기 한다.
c:\Python\code\djangoweb>python manage.py inspectdb > supermarket\models.py
확인을 하고 싶으면 c:\Python\code\djangoweb\supermarket\models.py 파일을 열어보면, 테이블의 구조가 아래와 같이 model 파일로 자동으로 만들어 진다. 내용을 보면 우리가 클래스는 배운적이 없지만 걍 내용을 담는 박스라고 생각하자. 클래스 안에 각 디비의 컬럼들이 정의 되어 있는데, 앞의 소문자로 된 itemno, categoty 등이 나중에 QuerySet 등에서 명시되어 사용되게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 |
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# * Make sure each ForeignKey has `on_delete` set to the desired behavior.
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from __future__ import unicode_literals
from django.db import models
class Play(models.Model):
original = models.CharField(max_length=30, blank=True, null=True)
encrypted = models.CharField(max_length=200, blank=True, null=True)
decrypted = models.CharField(max_length=30, blank=True, null=True)
class Meta:
managed = False
db_table = 'play'
class Supermarket(models.Model):
itemno = models.IntegerField(db_column='Itemno', blank=True, null=True) # Field name made lowercase.
category = models.CharField(db_column='Category', max_length=20, blank=True, null=True) # Field name made lowercase.
foodname = models.CharField(db_column='FoodName', max_length=30, blank=True, null=True) # Field name made lowercase.
company = models.CharField(db_column='Company', max_length=20, blank=True, null=True) # Field name made lowercase.
price = models.IntegerField(db_column='Price', blank=True, null=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'supermarket'
|
cs |
[마이그레이션 - 일단 생략]
굳이 여기서 쓸것도 아니고, 모델과 데이터베이스를 계속 싱크 시킬 것도 아니기 때문에 마이그레이션 명령을 돌리는건 생략 하지만, 저도 에러 땜에 이것저것 찾아보다 한번 돌려봤다. 마이그레이션 명령어를 실행하게 되면 아래와 같이 어드민 관련 테이블이나, 장고에서 지원되는 세션 같은 기능에서 사용하는 기본 테이블들이 데이터베이스안에 만들어지게 된다. 실제로 안돌려도 되니 참고만 하자(뭐 돌려도 상관은 없지만 말이다).
c:\Python\code\djangoweb>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying admin.0001_initial... OK
....
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
[inspectdb 를 통한 마이그레이션 성공 여부 확인 하기]
실제 페이지를 모두 만들어서, 만들어진 모델의 Queryset 이 정상으로 돌아가는지 확인 하려면, 만드는 도중에 또 다른 실수를 할지 모르므로, 실제 사이트를 만들기 전에 아래의 장고걸 사이트에서 나온데로, 장고쉘 기능을 이용해서(파이썬 쉘과 비슷하다고 보면 된다), 모델이 잘 생성됬는지 확인해 본다.
https://tutorial.djangogirls.org/ko/django_orm/
일단 쉘을 실행 하자.
c:\Python\code\djangoweb>python manage.py shell
잘 실행되서 '>>>' 프롬프트가 나오면 아래의 명령어를 넣어 슈퍼마켓 모델을 가져오고, 슈퍼마켓 모델에서 모든 내용들을 다 가져와 보자
>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
File "C:\python\lib\site-packages\sql_server\pyodbc\base.py", line 545, in execute
return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: ('42S22', "[42S22] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]\ufffd\ufffd \ufffd\u0338\ufffd 'id'\ufffd\ufffd(\ufffd\ufffd) \ufffd\u07f8\ufffd\ufffd\u01fe\ufffd\ufffd\ufffd\ufffd\u03f4\ufffd. (207) (SQLExecDirectW)")
흠 그런데 이상한 SQL 에러가 난다. 근데 에러 파일의 위치를 보면('pyodbc\base.py') 장고쪽은 아니고, pyodbc 쪽 에러같아서 mssql 모듈쪽은 이상없는것 같아 일단 안심은 된다. 어떤 에러인지 찾기 위해서 구글에서 'pyodbc 42S22 error' 라고 검색해서 아래 페이지를 찾아서 보니, 컬럼이 없는 경우의 에러인 것 같다. 그러고 보니 위의 에러에서 'id' 라는 컬럼이름 같은 항목이 보인다.
https://stackoverflow.com/questions/36202976/pandas-with-pyodbc-nan-error-42s22-error-attribute-qnan-not-found-31
그런데 supermarket 테이블에는 'id' 라는 컬럼 자체를 만든적이 없으니 이상하다고 생각하던 중, 예전에 장고 메뉴얼을 읽다가, 마이그레이션 설명 쪽에서 모델에 primary 키(겹치지 않는 유일한 값을 가진 필드 속성)가 없으면, 자동으로 테이블에 'id' 라는 이름으로 자동으로 숫자가 증가하는 primary 키를 만든다는 내용을 스쳐가듯 본 듯한 기억이 났다. 해당 테이블에는 당연히 primary 키 설정이 없었었고, 그래서 장고가 마이그레이션한 테이블처럼 id 필드가 무조건 있으리라 생각하고, 데이터베이스에 select 쿼리를 보낼 때 'select id, ...' 를 하여 에러가 났나보다.
https://docs.djangoproject.com/en/1.11/topics/db/models/
https://docs.djangoproject.com/en/1.11/topics/db/models/#automatic-primary-key-fields
그럼 2가지 해결책이 있을 듯 하다. 하나는 현재의 supermarket 테이블에, 자동으로 숫자가 증가되는(auto increment) id 필드를 추가하는 방법이 있고, 다른 하나는 기존 supermarket 테이블을 삭제하고 다시 만들면서, 특정한 필드를 primary 키로 지정하여 생성하는 것이다(개인적으로 두 방식 모두 해봤는데 둘 다 잘 해결되긴 했다). 여기선 간단하게 가기위해서, supermarket 테이블에 id 컬럼을 하나 추가하자. 구글에서 'mssql add column autoincrement' 라고 검색하여 아래 페이지를 찾았다.
https://stackoverflow.com/questions/4862385/sql-server-add-auto-increment-primary-key-to-existing-table
ALTER TABLE dbo.YourTable
ADD ID INT IDENTITY
아래의 명령어를 4교시에 설명한 MSSQL Managment Studio 를 실행해 쿼리 입력 창에서 mytest db 를 대상으로 실행한다(SSMS 사용법이 기억이 잘 안나면 4교시를 참고한다)
|
use mytest
go
ALTER TABLE dbo.supermarket
ADD id INT IDENTITY
ALTER TABLE dbo.supermarket
ADD CONSTRAINT PK_supermarket
PRIMARY KEY(id)
select * from dbo.supermarket(nolock) |
cs |
명령 실행후 supermarket 테이블을 셀렉트 한 내용을 보면 아래와 같이 숫자가 증가하는 id 값이 추가로 생겼다.
이후 다시 장고 쉘에서 같은 명령어를 입력해 보면 정상적으로 결과를 가져온다. 값이 아니라 오브젝트 자체를 가져오기 때문에 안의 내용은 표시되진 않지만, 설치한 mssql 용 장고 모듈이 잘 동작되는 것을 확인 했으니 이제 실제 코드를 만들면 될듯 하다.
>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
<QuerySet [<Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>]>
※ 참고로 기존 테이블에 id 를 추가하는 게 싫어서 테이블을 재생성 하고 싶으면, 아래와 같은 itemno 에 primary 키 속성이 있는 테이블을 만들고, 데이터를 다시 채워 넣음 된다(물론 그전에 'drop table' 명령어로 기존 supermarket 테이블은 지워야 한다)
|
CREATE TABLE [dbo].[supermarket](
[Itemno] [int] NOT NULL PRIMARY KEY,
[Category] [char](20) NULL,
[FoodName] [char](30) NULL,
[Company] [char](20) NULL,
[Price] [int] NULL
)
|
cs |
[supermarket 용 url 만들기]
아까 샘플 url 을 만든것 처럼, c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 supermk url 을 추가 한다.
|
from django.conf.urls import url
from supermarket import views
urlpatterns = [
url(r'^$', views.HomePageView.as_view()),
url(r'^supermk$', views.supermk),
]
|
cs |
[supermarket 용 view 만들기]
마찬가지로 c:\Python\code\djangoweb\supermarket\views.py 파일의 기존 내용의 마지막에, 아래의 supermk 뷰를 추가한다(따라오시다 헷깔리시는 경우는 나중에 전체 소스를 맨뒤의 부록 섹션에 첨부할테니 그걸 참고하시기 바란다). 예전 플라스크에서 구현한 것과 비슷한 구조로 Supermarket 모델에서 모든 값을 가져오고(supers = Supermarket.objects.all()), 이후에 가져온 데이터를(supers)을 super.html 과 같이 랜더링 한다.
|
from .models import Supermarket
def supermk(request):
supers = Supermarket.objects.all()
return render(request, 'super.html', {'supers': supers}) |
cs |
[supermarket 용 template 만들기]
마찬가지로 c:\Python\code\djangoweb\supermarket\templates\super.html 파일을 메모장으로 utf-8 인코딩으로 저장해 만들면서 아래의 코드를 넣는다. 역시 플라스크와 비슷하게
'{{ }}' 와 '{% %}' 를 사용하여 루프를 돌리면서 <td> 태그 안에 각 컬럼 값을 넣게 된다(템플릿 엔진이 같은가도 싶다).
|
<table border="1" cellpadding="5" cellspacing="5">
{% for super in supers %}
<tr>
<td>{{ super.itemno }}</td>
<td>{{ super.category }}</td>
<td>{{ super.foodname }}</td>
<td>{{ super.company }}</td>
<td>{{ super.price }}</td>
</tr>
{% endfor %}
</table>
|
cs |
[supermarket 페이지 결과 보기]
아까 runserver로 웹서버를 실행한 상태로 두었다면 소스의 변경사항이 자동으로 반영되었을 테고(플라스크도 이랬다), 종료했다면 아래의 명령어를 다시 쳐서 실행한다)
c:\Python\code\djangoweb>python manage.py runserver
이제 브라우저를 띄워 http://127.0.0.1:8000/supermk 를 실행 하면, 아래와 같이 플라스크로 구현한 것과 비슷한 화면을 볼수 있다.
현재까지의 트리는 아래와 같다. 추가된 파일은 색으로 표시했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 |
djangoweb/
manage.py
djangoweb/
__init__.py
settings.py
urls.py
wsgi.py
supermarket/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
templates/
index.html
super.html
urls.py
views.py |
cs |
[D3.js 에서 Json 데이터 URL 를 호출해 웹으로 그래프 보여주기]
이 예제를 만들기 위해 플라스크에 있는 예제를 전환하면서 또 한바탕 헤메긴 했었다. 헤멘 부분이 플라스크와 장고의 차이를 보여주기 때문에 일부는 말로 설명하고 일부는 해결 과정을 보여주려고 한다.
[d3.js 샘플 페이지 찾아보기]
일단 플라스크 때 구현한 코드를 장고로 옮기려면 두 가지가 필요하다. 첫번째는 d3.js 를 사용하는 템플릿 코드의 일부를 장고에 맞추어 변경해야 할 것 같고, 또 d3.js 에서 호출했던 json 데이터를 반환하는 URL 을 장고로 구현해야 한다.
구글에서 'django d3.js json' 을 찾아서 아래 페이지를 보니 2개의 힌트가 있다. 첫번째는 d3.js 에서 url 을 호출 하는 방식이다. urlconfs 파일에서 url 을 정의 할때 'name' 속성을 이용해 정의하고, d3.js 에서 'url' 문법을 이용해 경로를 호출 한다. 또 json 데이터를 만들어 내는 play_count_by_month 함수는 QuerySet 을 이용해 결과를 가져와서 JsonResponse 함수를 이용해 json 응답을 생성한다(JsonResponse(list(data), safe=False)). 'safe=False' 옵션이 있는 이유는, JsonResponse는 dictionary 형태의 데이터만 기본적으로 중계하고, 다른 데이터 형일 경우는 'safe=False' 옵션을 넣어야만 형변환 에러가 안난다.
https://stackoverflow.com/questions/26453916/passing-data-from-django-to-d3
좀더 자세히 json 을 반환하는 것을 살펴 보려고 추가적인 페이지도 찾아보았다. JsonResponse 를 사용하는게 1.11 버전에서는 적절해 보인다.
https://simpleisbetterthancomplex.com/tutorial/2016/07/27/how-to-return-json-encoded-response.html
https://docs.djangoproject.com/en/dev/ref/request-response/#jsonresponse-objects
[urlconf 파일 수정]
우선 c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 'data' 와 'd3sample' 경로를 추가한다. 'data' url에는 아까 샘플에서 봤듯이 name 속성이 추가됬다.
|
urlpatterns = [
url(r'^$', views.HomePageView.as_view()),
url(r'^supermk$', views.supermk),
url(r'^data$', views.data, name='data'),
url(r'^d3sample$', views.d3sample),
]
|
cs |
[view 파일 수정]
다음으로 c:\Python\code\djangoweb\supermarket\views.py 파일을 수정한다. data 함수 안의 내용은 거의 플라스크때 json 형태로 만든 샘플 데이터를 최종으로 JsonReponse 에 넘기는 변경만 했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
import json
import numpy as np
from django.http import JsonResponse
def data(request):
x = np.array(['2017-07-10', '2017-07-11', '2017-07-12', '2017-07-13', '2017-07-14'])
y = np.array([58.13, 53.98, 67.00, 89.70, 99.00])
myData = json.dumps([{"date": x[i], "close": y[i]} for i in range(5)])
return JsonResponse(myData, safe=False)
def d3sample(request):
return render(request, 'd3sample.html', context=None)
|
cs |
[templete 파일 생성]
메모장을 열어서 예전 플라스크 때의 코드를 복사해 json URL 호출 하는 부분만 샘플에서 참고한 내용을 기준으로 변경 후에, 인코딩을 utf-8 로 하여, c:\Python\code\djangoweb\supermarket\templates\d3sample.html 파일로 저장한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 |
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// 축 정의
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// 그래프 선 정의
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// 캔버스 객체 생성
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// 실데이터에 맞춰 그래프 범위 지정
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// 선 그리기.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// x축 그리기?
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// y 축 그리기?
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
// django 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}", callback);
</script>
</body>
|
cs |
[에러를 만나다]
그런데 브라우저를 열어 http://127.0.0.1:8000/d3sample 을 호출하여 보니 그래프가 표시되지 않는다. 피들러를 띄워 확인해 보니 d3sample url 이 호출되고, 이후 /data url 까지 잘 호출한다.
그래서 json 형태로 넘어오는 데이터를 확인하기 위해 브라우저에서 http://127.0.0.1:8000/data url 을 호출해보니, 파일을 저장하라는 창이 뜬다. 뭔가 이상하긴 하다. 해당 json 파일을 c:\python\code 폴더에 저장해서 메모장으로 열어 보니, 아래와 같이 (") 문자앞에 역슬래시(\) 가 들어가 이스케이프 처리가 되어있다(아마 여기서는 안전하지 못한 코드의 경우 JsonResponse 메써드가 방어를 하는 차원인거 같긴하다).
|
"[{\"close\": 58.13, \"date\": \"2017-07-10\"},
{\"close\": 53.98, \"date\": \"2017-07-11\"},
{\"close\": 67.0, \"date\": \"2017-07-12\"},
{\"close\": 89.7, \"date\": \"2017-07-13\"},
{\"close\": 99.0, \"date\": \"2017-07-14\"}]" |
cs |
그래서 브라우저에서 F12키를 눌러서, 예전에 배운 IE 개발자 도구를 열은 후, 'F5'키를 눌러서 http://127.0.0.1:8000/data 페이지를 다시 로딩 한다. 개발자 도구창에서 '콘솔' 탭을 클릭한 후 동그란 !표 아이콘을 클릭해 보니, 아래와 같이 개체가 상이하여 루프 메서드를 실행할 수 없다는 자바스크립트 에러가 난다.
에러 메시지 밑의 라인 링크('d3smaple (75,5)')를 클릭하면, 해당되는 소스 라인에 가게 되는데, 아무래도 json 데이터를 받아온 내용이 담겨있는 data 변수가 기존 플라스크 때와는 다르게 json 형식으로 잘 해석이 안 된것 같다(아마 위의 \" 이스케이프 처리 때문인 것 같긴하다)
그래서 자바스크립트 안에 'document.write(data)' 구문을 넣어 data 변수를 뿌려보니 아래와 같이 파일로 저장했을때는 보였던 escape 문자(\) 는 d3.json 함수를 통과하면서 없어진듯 하고, 정상적으로 뿌려짐을 볼수 있다(해당 디버그 코드들은 최종 샘플에 주석처리해 놓았으니 참고만...). 문법적으로 json 형태는 맞는거 같아, 그렇다면 왠지 이스케이프 문자를 d3.json 이 처리는 해줬지만, json 데이터라고 생각을 안해 변환을 안해 준거 같다는 생각이 든다.
|
// d3.json 으로 받아온 값을 담고 있는 data 변수의 값을 찍어봄
document.write(data) |
cs |
그래서 data 변수의 타입을 확인해 보기로 했다. 구글에서 'javascript print typeof' 로 검색하니, 아래의 페이지에서 type 을 문자열로 반환하는 함수를 얻게 된다.
https://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
|
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
return {}.toString.call(obj).split(' ')[1].slice(0, -1).toLowerCase();
}
// 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
var datatype = typeOf(data);
document.write(datatype);
|
cs |
데이터 타입을 확인하니 'string' 이라고 나온다.
JsonResponse 에서 " 문자를 이스케이프 처리하는게 확실히 문제는 문제인거 같다. 처음에는 json object 를 dict 로 변환해 볼까를 이리저리 궁리했지만 샘플의 json object 는 2차원이고, dict 는 1차원이라서 뭔가 억지로 변환하여 문제를 풀면 d3sample.html 코드도 이것저것 바꿔줘야 할 것 같아서 망설이다가 data 변수의 타입이 string 이지만 실제 내용은 json 데이터 형태인건 맞으니, string 을 json 객체로 변환하면 결과대로 나오지 않을까 하는 생각이 들었다. 구글에 'javascript string to json' 이라고 찾아서, 아래의 페이지를 찾는다. 해당 코드를 이용하여 변환 후에, 데이터 타입을 화면에 뿌려본다.
http://jekalmin.tistory.com/entry/string%EC%9D%84-json%EC%9C%BC%EB%A1%9C-json%EC%9D%84-string%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%99%98
|
// string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
// 확인 후 변환 코드는 남겨두어, 넘어온 data 값을 변환하여 사용함.
data = JSON.parse(data);
var datatype = typeOf(data);
document.write(datatype); |
cs |
위와 같이 변환한 후 데이터 형을 다시 확인해보니 array 라고 나온다. 지금 보니 이래서 구글에의 샘플 페이지의 view 함수의 JsonResponse 호출부분에서 데이터를 list 형태로 바꾼건가 싶기도 하다. 뭐 여튼 변환하는 코드만 주석 처리 하지 않고 남겨두어 사용해 본다.
최종 코드는 아래와 같고, 디버깅 코드 등으로 변경한 내용은 다른 색으로 표시해 놓았다. 브라우저의 개발자 도구를 잘쓰면 이렇게 덕지덕지 덜 넣어도 될듯하긴 하다. 보시면 결국 넘어온 데이터를 'data = JSON.parse(data);' 를 이용해 json array 형태로 바꾸어줌 간단히 해결나는 상황이다(밑의 typeof 함수는 디버깅 용이다). 해당 내용으로 d3sample.html 내용을 수정해 준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 |
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// 축 정의
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// 그래프 선 정의
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// 캔버스 객체 생성
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
// 에러추적 1 : 데이터를 뿌려보니 \ 문자도 제거되고, 정상적인 json 문법인거 같음
// document.write(data)
// 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
// var datatype = typeOf(data);
// document.write(datatype);
// string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
// 확인 후 변환 코드는 남겨 넘어온 data 값을 변환하여 사용함.
data = JSON.parse(data);
// var datatype = typeOf(data);
// document.write(datatype);
// 첨에 이런 에러가 남 (개체가 'forEach' 속성이나 메서드를 지원하지 않습니다.)
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// 실데이터에 맞춰 그래프 범위 지정
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// 선 그리기.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// x축 그리기?
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// y 축 그리기?
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
// flask 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}", callback);
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
return {}.toString.call(obj).split(' ')[1].slice(0, -1).toLowerCase();
}
</script>
</body>
|
cs |
이후 브라우저를 열어 http://127.0.0.1:8000/d3sample 를 호출하면 플라스크 때와 같이 그래프가 정상적으로 출력된다.
참고로 앞의 코드에서 d3sample.html 이 아닌, 뷰 파일 쪽을 고치려면, 딕션너리 데이터가 리스트 안에 담긴 형태로 만들어서, JsonResponse 함수로 전달하면 됩니다.
[마무리 하면서]
이렇게 해서 플라스크에서 구현했던 예제들을 장고에서 구현해 보면서, 장고란 프레임워크의 여러가지 면들에 대해 살펴보았다(덤으로 javascript 도 약간 배웠다). 생각보다는 장고는 DB에 마이그레이션도 해주고, 어드민도 자동 생성하는 듯, 마법사 모드에 가까운 부분도 있는것 같으며, 확실히 플라스크 보다는 체계적인 구조를 갖춘 듯 하다. 좀 뭐랄까 사용하는 규칙이 엄격하다고 할까... 장고를 사용하는게 괜찮아 보인다면 이제부터 메뉴얼을 찬찬히 훝어보거나, 관련 책을 하나 사서 보거나, 구글을 검색하면서 필요한 부분을 찾아 공부하면 될듯 싶다. 3섹션동안 웹 쪽을 다루긴 했지만 파이썬 기능에 초점을 두었기 때문에, 웹 프로그래밍 책들에서 많이 다루는 게시판이나, 로그인, 파일 업로드, CSS 등으로 화면 꾸미기와 같은 주제들은 다루지 않았기 때문에, 초보시라면 공부해야 될 부분들은 앞으로도 많을 것이다. 여기서는 legacy web, flask, django 세 가지가 각각 나름대로 비슷하면서도 다른 배경을 가지고 살아왔다는 것을 이해 한다면 성공일 것 같다. 이렇게 해서 legacy web 에서 시작하여 flask 와 django 를 살펴본 파이썬의 웹 프로그래밍에 대한 여정을 마치려 한다. 나름 처음 생각 했던것 보다는 잘 정리된 듯도 싶다 --;
[부록]
전체 코드의 트리는 아래와 같고 소스들은 따라하시다 꼬일때를 대비해서, 참고하시라고 압축 파일로 첨부했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
djangoweb/
manage.py
djangoweb/
__init__.py
settings.py
urls.py
wsgi.py
supermarket/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
templates/
index.html
super.html
d3sample.html
urls.py
views.py |
cs |
[첨부 zip 파일]
djangoweb.zip